ffmpeg.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  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 get_video_mp3(cls, video_file, video_path_url, pw_random_id):
  101. pw_mp3_path = video_path_url + str(pw_random_id) +'pw_video.mp3'
  102. command = [
  103. 'ffmpeg',
  104. '-i', video_file,
  105. '-vn', # 禁用视频流
  106. '-q:a', '0', # 指定音频质量
  107. pw_mp3_path
  108. ]
  109. subprocess.run(command)
  110. time.sleep(1)
  111. return pw_mp3_path
  112. """
  113. 生成片尾视频
  114. """
  115. @classmethod
  116. def pw_video(cls, jpg_url, video_path_url, pw_url, pw_srt, pw_random_id, pw_mp3_path):
  117. # 添加音频到图片
  118. """
  119. jpg_url 图片地址
  120. pw_video 提供的片尾视频
  121. pw_duration 提供的片尾视频时长
  122. new_video_path 视频位置
  123. subtitle_cmd 字幕
  124. pw_url 生成视频地址
  125. :return:
  126. """
  127. pw_srt_path = video_path_url + str(pw_random_id) +'pw_video.srt'
  128. # 创建临时字幕文件
  129. with open(pw_srt_path, 'w') as f:
  130. f.write(pw_srt)
  131. # 片尾位置
  132. pw_url_path = video_path_url + str(pw_random_id) + 'pw_video.mp4'
  133. # 获取视频时长
  134. pw_duration = cls.get_video_duration(pw_url)
  135. time.sleep(2)
  136. # 添加字幕 wqy-zenhei Hiragino Sans GB
  137. subtitle_cmd = f"subtitles={pw_srt_path}:force_style='Fontsize=14,Fontname=wqy-zenhei,Outline=0,PrimaryColour=&H000000,SecondaryColour=&H000000,Bold=1,MarginV=155'"
  138. background_cmd = "drawbox=y=366/2:color=yellow@1.0:width=iw:height=50:t=fill"
  139. ffmpeg_cmd = [
  140. 'ffmpeg',
  141. '-loop', '1',
  142. '-i', jpg_url, # 输入的图片文件
  143. '-i', pw_mp3_path, # 输入的音频文件
  144. '-c:v', 'libx264', # 视频编码格式
  145. '-t', str(pw_duration), # 输出视频的持续时间,与音频持续时间相同
  146. '-pix_fmt', 'yuv420p', # 像素格式
  147. '-c:a', 'aac', # 音频编码格式
  148. '-strict', 'experimental', # 使用实验性编码器
  149. '-shortest', # 确保输出视频的长度与音频一致
  150. '-vf', f"scale=320x480,{background_cmd},{subtitle_cmd}", # 视频过滤器,设置分辨率和其他过滤器
  151. pw_url_path # 输出的视频文件路径
  152. ]
  153. subprocess.run(ffmpeg_cmd)
  154. return pw_url_path
  155. """
  156. 设置统一格式拼接视频
  157. """
  158. @classmethod
  159. def concatenate_videos(cls, video_list, video_path_url):
  160. concatenate_videos_url = video_path_url + 'concatenate_videos.mp4'
  161. # 拼接视频
  162. VIDEO_COUNTER = 0
  163. FF_INPUT = ""
  164. FF_SCALE = ""
  165. FF_FILTER = ""
  166. ffmpeg_cmd = ["ffmpeg"]
  167. for videos in video_list:
  168. # 添加输入文件
  169. FF_INPUT += f" -i {videos}"
  170. # 为每个视频文件统一长宽,并设置SAR(采样宽高比)
  171. FF_SCALE += f"[{VIDEO_COUNTER}:v]scale=320x480,setsar=1[v{VIDEO_COUNTER}];"
  172. # 为每个视频文件创建一个输入流,并添加到-filter_complex参数中
  173. FF_FILTER += f"[v{VIDEO_COUNTER}][{VIDEO_COUNTER}:a]"
  174. # 增加视频计数器
  175. VIDEO_COUNTER += 1
  176. # 构建最终的FFmpeg命令
  177. ffmpeg_cmd.extend(FF_INPUT.split())
  178. ffmpeg_cmd.extend(["-filter_complex", f"{FF_SCALE}{FF_FILTER}concat=n={VIDEO_COUNTER}:v=1:a=1[v][a]",
  179. "-map", "[v]", "-map", "[a]", "-y", concatenate_videos_url])
  180. subprocess.run(ffmpeg_cmd)
  181. return concatenate_videos_url
  182. """
  183. 单个视频拼接
  184. """
  185. @classmethod
  186. def single_video(cls, new_video_path, video_share, video_path_url, zm):
  187. single_video_url = video_path_url + 'single_video.mp4'
  188. single_video_srt = video_path_url + 'single_video.srt'
  189. # 获取时长
  190. duration = cls.get_video_duration(new_video_path)
  191. start_time = cls.seconds_to_srt_time(0)
  192. end_time = cls.seconds_to_srt_time(duration)
  193. single_video_txt = video_path_url + 'single_video.txt'
  194. with open(single_video_txt, 'w') as f:
  195. f.write(f"file '{new_video_path}'\n")
  196. with open(single_video_srt, 'w') as f:
  197. f.write(f"1\n{start_time} --> {end_time}\n\u2764\uFE0F{zm}\n\n")
  198. background_cmd = "drawbox=y=370/1:color=yellow@1.0:width=iw:height=70:t=fill"
  199. if video_share == '有':
  200. # 添加字幕 wqy-zenhei Hiragino Sans GB
  201. subtitle_cmd = f"subtitles={single_video_srt}:force_style='Fontsize=14,Fontname=wqy-zenhei,Outline=0,PrimaryColour=&H000000,SecondaryColour=&H000000,Bold=1,MarginV=30'"
  202. draw = f"{background_cmd},{subtitle_cmd}"
  203. else:
  204. subtitle_cmd = f"subtitles={single_video_srt}:force_style='Fontsize=14,Fontname=wqy-zenhei,Outline=2,PrimaryColour=&H00FFFF,SecondaryColour=&H000000,Bold=1,MarginV=30'"
  205. draw = f"{subtitle_cmd}"
  206. # 多线程数
  207. num_threads = 4
  208. # 构建 FFmpeg 命令,生成视频
  209. ffmpeg_cmd_oss = [
  210. "ffmpeg",
  211. "-f", "concat",
  212. "-safe", "0",
  213. "-i", f"{single_video_txt}",
  214. "-c:v", "libx264",
  215. "-c:a", "aac",
  216. "-threads", str(num_threads),
  217. "-vf", f"scale=320x480,{draw}",
  218. "-y",
  219. single_video_url
  220. ]
  221. subprocess.run(ffmpeg_cmd_oss)
  222. return single_video_url