ffmpeg.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  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_crop(cls, new_video_path, video_path_url, pw_random_id):
  83. crop_url = video_path_url + str(pw_random_id) + 'crop.mp4'
  84. # 获取视频的原始宽高信息
  85. ffprobe_cmd = f"ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 {new_video_path}"
  86. ffprobe_process = subprocess.Popen(ffprobe_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
  87. output, _ = ffprobe_process.communicate()
  88. width, height = map(int, output.decode().strip().split(','))
  89. # 计算裁剪后的高度
  90. new_height = int(height * 0.8)
  91. # 构建 FFmpeg 命令,裁剪视频高度为原始高度的70%,并将宽度缩放为320x480
  92. ffmpeg_cmd = [
  93. "ffmpeg",
  94. "-i", new_video_path,
  95. "-vf", f"crop={width}:{new_height},scale=320:480",
  96. "-c:v", "libx264",
  97. "-c:a", "aac",
  98. "-y",
  99. crop_url
  100. ]
  101. subprocess.run(ffmpeg_cmd)
  102. return crop_url
  103. """
  104. 视频裁剪
  105. """
  106. @classmethod
  107. def video_ggduration(cls, new_video_path, video_path_url, pw_random_id, gg_duration_total):
  108. gg_duration_url = video_path_url + str(pw_random_id) + 'gg_duration.mp4'
  109. # 获取视频时长
  110. total_duration = cls.get_video_duration(new_video_path)
  111. duration = int(total_duration) - int(gg_duration_total)
  112. ffmpeg_cmd = [
  113. "ffmpeg",
  114. "-i", new_video_path,
  115. "-c:v", "libx264",
  116. "-c:a", "aac",
  117. "-t", str(duration),
  118. "-y",
  119. gg_duration_url
  120. ]
  121. subprocess.run(ffmpeg_cmd)
  122. return gg_duration_url
  123. """
  124. 截取原视频最后一帧
  125. """
  126. @classmethod
  127. def video_png(cls, new_video_path, video_path_url, pw_random_id):
  128. """
  129. jpg_url 生成图片位置
  130. :param new_video_path: 视频地址
  131. :return:
  132. """
  133. jpg_url = video_path_url + str(pw_random_id) + 'png.jpg'
  134. # 获取视频时长
  135. total_duration = cls.get_video_duration(new_video_path)
  136. time_offset = total_duration - 1 # 提取倒数第一秒的帧
  137. # 获取视频最后一秒,生成.jpg
  138. subprocess.run(
  139. ['ffmpeg', '-ss', str(time_offset), '-i', new_video_path, '-t', str(total_duration), '-vf', 'fps=1', "-y", jpg_url])
  140. return jpg_url
  141. """
  142. 获取视频音频
  143. """
  144. @classmethod
  145. def get_video_mp3(cls, video_file, video_path_url, pw_random_id):
  146. pw_mp3_path = video_path_url + str(pw_random_id) +'pw_video.mp3'
  147. command = [
  148. 'ffmpeg',
  149. '-i', video_file,
  150. '-q:a', '0',
  151. '-map', 'a',
  152. # '-codec:a', 'libmp3lame', # 指定 MP3 编码器
  153. pw_mp3_path
  154. ]
  155. subprocess.run(command)
  156. time.sleep(1)
  157. return pw_mp3_path
  158. """
  159. 生成片尾视频
  160. """
  161. @classmethod
  162. def pw_video(cls, jpg_url, video_path_url, pw_url, pw_srt, pw_random_id, pw_mp3_path):
  163. # 添加音频到图片
  164. """
  165. jpg_url 图片地址
  166. pw_video 提供的片尾视频
  167. pw_duration 提供的片尾视频时长
  168. new_video_path 视频位置
  169. subtitle_cmd 字幕
  170. pw_url 生成视频地址
  171. :return:
  172. """
  173. pw_srt_path = video_path_url + str(pw_random_id) +'pw_video.srt'
  174. # 创建临时字幕文件
  175. with open(pw_srt_path, 'w') as f:
  176. f.write(pw_srt)
  177. # 片尾位置
  178. pw_url_path = video_path_url + str(pw_random_id) + 'pw_video.mp4'
  179. # 获取视频时长
  180. pw_duration = cls.get_video_duration(pw_url)
  181. time.sleep(2)
  182. # 添加字幕 wqy-zenhei Hiragino Sans GB
  183. subtitle_cmd = f"subtitles={pw_srt_path}:force_style='Fontsize=14,Fontname=wqy-zenhei,Outline=0,PrimaryColour=&H000000,SecondaryColour=&H000000,Bold=1,MarginV=155'"
  184. background_cmd = "drawbox=y=366/2:color=yellow@1.0:width=iw:height=50:t=fill"
  185. ffmpeg_cmd = [
  186. 'ffmpeg',
  187. '-loop', '1',
  188. '-i', jpg_url, # 输入的图片文件
  189. '-i', pw_mp3_path, # 输入的音频文件
  190. '-c:v', 'libx264', # 视频编码格式
  191. '-t', str(pw_duration), # 输出视频的持续时间,与音频持续时间相同
  192. '-pix_fmt', 'yuv420p', # 像素格式
  193. '-c:a', 'aac', # 音频编码格式
  194. '-strict', 'experimental', # 使用实验性编码器
  195. '-shortest', # 确保输出视频的长度与音频一致
  196. '-vf', f"scale=320x480,{background_cmd},{subtitle_cmd}", # 视频过滤器,设置分辨率和其他过滤器
  197. pw_url_path # 输出的视频文件路径
  198. ]
  199. subprocess.run(ffmpeg_cmd)
  200. return pw_url_path
  201. """
  202. 设置统一格式拼接视频
  203. """
  204. @classmethod
  205. def concatenate_videos(cls, video_list, video_path_url):
  206. concatenate_videos_url = video_path_url + 'concatenate_videos.mp4'
  207. # 拼接视频
  208. VIDEO_COUNTER = 0
  209. FF_INPUT = ""
  210. FF_SCALE = ""
  211. FF_FILTER = ""
  212. ffmpeg_cmd = ["ffmpeg"]
  213. for videos in video_list:
  214. # 添加输入文件
  215. FF_INPUT += f" -i {videos}"
  216. # 为每个视频文件统一长宽,并设置SAR(采样宽高比)
  217. FF_SCALE += f"[{VIDEO_COUNTER}:v]scale=320x480,setsar=1[v{VIDEO_COUNTER}];"
  218. # 为每个视频文件创建一个输入流,并添加到-filter_complex参数中
  219. FF_FILTER += f"[v{VIDEO_COUNTER}][{VIDEO_COUNTER}:a]"
  220. # 增加视频计数器
  221. VIDEO_COUNTER += 1
  222. # 构建最终的FFmpeg命令
  223. ffmpeg_cmd.extend(FF_INPUT.split())
  224. ffmpeg_cmd.extend(["-filter_complex", f"{FF_SCALE}{FF_FILTER}concat=n={VIDEO_COUNTER}:v=1:a=1[v][a]",
  225. "-map", "[v]", "-map", "[a]", "-y", concatenate_videos_url])
  226. subprocess.run(ffmpeg_cmd)
  227. return concatenate_videos_url
  228. """
  229. 单个视频拼接
  230. """
  231. @classmethod
  232. def single_video(cls, new_video_path, video_share, video_path_url, zm):
  233. single_video_url = video_path_url + 'single_video.mp4'
  234. single_video_srt = video_path_url + 'single_video.srt'
  235. # 获取时长
  236. duration = cls.get_video_duration(new_video_path)
  237. start_time = cls.seconds_to_srt_time(0)
  238. end_time = cls.seconds_to_srt_time(duration)
  239. single_video_txt = video_path_url + 'single_video.txt'
  240. with open(single_video_txt, 'w') as f:
  241. f.write(f"file '{new_video_path}'\n")
  242. with open(single_video_srt, 'w') as f:
  243. f.write(f"1\n{start_time} --> {end_time}\n\u2764\uFE0F{zm}\n\n")
  244. background_cmd = "drawbox=y=370/1:color=yellow@1.0:width=iw:height=70:t=fill"
  245. if video_share == '有':
  246. # 添加字幕 wqy-zenhei Hiragino Sans GB
  247. subtitle_cmd = f"subtitles={single_video_srt}:force_style='Fontsize=14,Fontname=wqy-zenhei,Outline=0,PrimaryColour=&H000000,SecondaryColour=&H000000,Bold=1,MarginV=30'"
  248. draw = f"{background_cmd},{subtitle_cmd}"
  249. else:
  250. subtitle_cmd = f"subtitles={single_video_srt}:force_style='Fontsize=14,Fontname=wqy-zenhei,Outline=2,PrimaryColour=&H00FFFF,SecondaryColour=&H000000,Bold=1,MarginV=30'"
  251. draw = f"{subtitle_cmd}"
  252. # 多线程数
  253. num_threads = 4
  254. # 构建 FFmpeg 命令,生成视频
  255. ffmpeg_cmd_oss = [
  256. "ffmpeg",
  257. "-f", "concat",
  258. "-safe", "0",
  259. "-i", f"{single_video_txt}",
  260. "-c:v", "libx264",
  261. "-c:a", "aac",
  262. "-threads", str(num_threads),
  263. "-vf", f"scale=320x480,{draw}",
  264. "-y",
  265. single_video_url
  266. ]
  267. subprocess.run(ffmpeg_cmd_oss)
  268. return single_video_url