ffmpeg.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. import configparser
  2. import glob
  3. import os
  4. import random
  5. import re
  6. import subprocess
  7. import sys
  8. import time
  9. import urllib.parse
  10. import json
  11. import requests
  12. from datetime import datetime, timedelta
  13. from urllib.parse import urlencode
  14. class FFmpeg():
  15. """
  16. 时间转换
  17. """
  18. @classmethod
  19. def seconds_to_srt_time(cls, seconds):
  20. hours = int(seconds // 3600)
  21. minutes = int((seconds % 3600) // 60)
  22. seconds = seconds % 60
  23. milliseconds = int((seconds - int(seconds)) * 1000)
  24. return f"{hours:02d}:{minutes:02d}:{int(seconds):02d},{milliseconds:03d}"
  25. """
  26. 获取单个视频时长
  27. """
  28. @classmethod
  29. def get_video_duration(cls, video_url):
  30. ffprobe_cmd = [
  31. "ffprobe",
  32. "-i", video_url,
  33. "-show_entries", "format=duration",
  34. "-v", "quiet",
  35. "-of", "csv=p=0"
  36. ]
  37. output = subprocess.check_output(ffprobe_cmd).decode("utf-8").strip()
  38. return float(output)
  39. """
  40. 获取视频文件的时长(秒)
  41. """
  42. @classmethod
  43. def get_videos_duration(cls, video_file):
  44. result = subprocess.run(
  45. ["ffprobe", "-v", "error", "-show_entries", "format=duration",
  46. "-of", "default=noprint_wrappers=1:nokey=1", video_file],
  47. capture_output=True, text=True)
  48. return float(result.stdout)
  49. """
  50. 视频裁剪
  51. """
  52. @classmethod
  53. def video_tailor(cls, video_url):
  54. output_video_path = ''
  55. try:
  56. # 获取视频的原始宽高信息
  57. ffprobe_cmd = f"ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 {video_url}"
  58. ffprobe_process = subprocess.Popen(ffprobe_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
  59. output, _ = ffprobe_process.communicate()
  60. width, height = map(int, output.decode().strip().split(','))
  61. # 计算裁剪后的高度
  62. new_height = int(height * 0.7)
  63. # 构建 FFmpeg 命令,裁剪视频高度为原始高度的70%,并将宽度缩放为320x480
  64. ffmpeg_cmd = [
  65. "ffmpeg",
  66. "-i", video_url,
  67. "-vf", f"crop={width}:{new_height},scale=320:480",
  68. "-c:v", "libx264",
  69. "-c:a", "aac",
  70. "-y",
  71. output_video_path
  72. ]
  73. # 执行 FFmpeg 命令
  74. subprocess.run(ffmpeg_cmd, check=True)
  75. return output_video_path
  76. except Exception as e:
  77. return None
  78. """
  79. 截取原视频最后一帧
  80. """
  81. @classmethod
  82. def video_png(cls, new_video_path, video_path_url, pw_random_id):
  83. """
  84. jpg_url 生成图片位置
  85. :param new_video_path: 视频地址
  86. :return:
  87. """
  88. jpg_url = video_path_url + str(pw_random_id) + 'png.jpg'
  89. # 获取视频时长
  90. total_duration = cls.get_video_duration(new_video_path)
  91. time_offset = total_duration - 1 # 提取倒数第一秒的帧
  92. # 获取视频最后一秒,生成.jpg
  93. subprocess.run(
  94. ['ffmpeg', '-ss', str(time_offset), '-i', new_video_path, '-t', str(total_duration), '-vf', 'fps=1', "-y", jpg_url])
  95. return jpg_url
  96. """
  97. 生成片尾视频
  98. """
  99. @classmethod
  100. def pw_video(cls, jpg_url, video_path_url, pw_url, pw_srt, pw_random_id):
  101. # 添加音频到图片
  102. """
  103. jpg_url 图片地址
  104. pw_video 提供的片尾视频
  105. pw_duration 提供的片尾视频时长
  106. new_video_path 视频位置
  107. subtitle_cmd 字幕
  108. pw_url 生成视频地址
  109. :return:
  110. """
  111. pw_srt_path = video_path_url + str(pw_random_id) +'pw_video.srt'
  112. # 创建临时字幕文件
  113. with open(pw_srt_path, 'w') as f:
  114. f.write(pw_srt)
  115. # 片尾位置
  116. pw_url_path = video_path_url + 'pw_video.mp4'
  117. # 获取视频时长
  118. pw_duration = cls.get_video_duration(pw_url)
  119. # 添加字幕 wqy-zenhei Hiragino Sans GB
  120. subtitle_cmd = f"subtitles={pw_srt_path}:force_style='Fontsize=14,Fontname=wqy-zenhei,Outline=0,PrimaryColour=&H000000,SecondaryColour=&H000000,Bold=1,MarginV=155'"
  121. background_cmd = "drawbox=y=366/2:color=yellow@1.0:width=iw:height=50:t=fill"
  122. ffmpeg_cmd = ['ffmpeg', '-loop', '1', '-i', jpg_url, '-i', pw_url, '-c:v', 'libx264', '-t',
  123. str(pw_duration), '-pix_fmt', 'yuv420p', '-c:a', 'aac', '-strict', 'experimental', '-shortest',
  124. '-vf', f"scale=320x480,{background_cmd},{subtitle_cmd}", pw_url_path]
  125. subprocess.run(ffmpeg_cmd)
  126. return pw_url_path
  127. """
  128. 设置统一格式拼接视频
  129. """
  130. @classmethod
  131. def concatenate_videos(cls, video_list, video_path_url):
  132. concatenate_videos_url = video_path_url + 'concatenate_videos.mp4'
  133. # 拼接视频
  134. VIDEO_COUNTER = 0
  135. FF_INPUT = ""
  136. FF_SCALE = ""
  137. FF_FILTER = ""
  138. ffmpeg_cmd = ["ffmpeg"]
  139. for videos in video_list:
  140. # 添加输入文件
  141. FF_INPUT += f" -i {videos}"
  142. # 为每个视频文件统一长宽,并设置SAR(采样宽高比)
  143. FF_SCALE += f"[{VIDEO_COUNTER}:v]scale=320x480,setsar=1[v{VIDEO_COUNTER}];"
  144. # 为每个视频文件创建一个输入流,并添加到-filter_complex参数中
  145. FF_FILTER += f"[v{VIDEO_COUNTER}][{VIDEO_COUNTER}:a]"
  146. # 增加视频计数器
  147. VIDEO_COUNTER += 1
  148. # 构建最终的FFmpeg命令
  149. ffmpeg_cmd.extend(FF_INPUT.split())
  150. ffmpeg_cmd.extend(["-filter_complex", f"{FF_SCALE}{FF_FILTER}concat=n={VIDEO_COUNTER}:v=1:a=1[v][a]",
  151. "-map", "[v]", "-map", "[a]", "-y", concatenate_videos_url])
  152. subprocess.run(ffmpeg_cmd)
  153. return concatenate_videos_url
  154. """
  155. 单个视频拼接
  156. """
  157. @classmethod
  158. def single_video(cls, new_video_path, video_share, video_path_url, zm):
  159. single_video_url = video_path_url + 'single_video.mp4'
  160. single_video_srt = video_path_url + 'single_video.srt'
  161. # 获取时长
  162. duration = cls.get_video_duration(new_video_path)
  163. start_time = cls.seconds_to_srt_time(0)
  164. end_time = cls.seconds_to_srt_time(duration)
  165. single_video_txt = video_path_url + 'single_video.txt'
  166. with open(single_video_txt, 'w') as f:
  167. f.write(f"file '{new_video_path}'\n")
  168. with open(single_video_srt, 'w') as f:
  169. f.write(f"1\n{start_time} --> {end_time}\n\u2764\uFE0F{zm}\n\n")
  170. background_cmd = "drawbox=y=370/1:color=yellow@1.0:width=iw:height=70:t=fill"
  171. if video_share == '有':
  172. # 添加字幕 wqy-zenhei Hiragino Sans GB
  173. subtitle_cmd = f"subtitles={single_video_srt}:force_style='Fontsize=14,Fontname=wqy-zenhei,Outline=0,PrimaryColour=&H000000,SecondaryColour=&H000000,Bold=1,MarginV=30'"
  174. draw = f"{background_cmd},{subtitle_cmd}"
  175. else:
  176. subtitle_cmd = f"subtitles={single_video_srt}:force_style='Fontsize=14,Fontname=wqy-zenhei,Outline=2,PrimaryColour=&H00FFFF,SecondaryColour=&H000000,Bold=1,MarginV=30'"
  177. draw = f"{subtitle_cmd}"
  178. # 多线程数
  179. num_threads = 4
  180. # 构建 FFmpeg 命令,生成视频
  181. ffmpeg_cmd_oss = [
  182. "ffmpeg",
  183. "-f", "concat",
  184. "-safe", "0",
  185. "-i", f"{single_video_txt}",
  186. "-c:v", "libx264",
  187. "-c:a", "aac",
  188. "-threads", str(num_threads),
  189. "-vf", f"scale=320x480,{draw}",
  190. "-y",
  191. single_video_url
  192. ]
  193. subprocess.run(ffmpeg_cmd_oss)
  194. return single_video_url