ffmpeg.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  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 get_video_duration(cls, video_url):
  20. ffprobe_cmd = [
  21. "ffprobe",
  22. "-i", video_url,
  23. "-show_entries", "format=duration",
  24. "-v", "quiet",
  25. "-of", "csv=p=0"
  26. ]
  27. output = subprocess.check_output(ffprobe_cmd).decode("utf-8").strip()
  28. return float(output)
  29. """
  30. 获取视频文件的时长(秒)
  31. """
  32. @classmethod
  33. def get_videos_duration(cls, video_file):
  34. result = subprocess.run(
  35. ["ffprobe", "-v", "error", "-show_entries", "format=duration",
  36. "-of", "default=noprint_wrappers=1:nokey=1", video_file],
  37. capture_output=True, text=True)
  38. return float(result.stdout)
  39. """
  40. 视频裁剪
  41. """
  42. @classmethod
  43. def video_tailor(cls, video_url):
  44. output_video_path = ''
  45. # 获取视频的原始宽高信息
  46. ffprobe_cmd = f"ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 {video_url}"
  47. ffprobe_process = subprocess.Popen(ffprobe_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
  48. output, _ = ffprobe_process.communicate()
  49. width, height = map(int, output.decode().strip().split(','))
  50. # 计算裁剪后的高度
  51. new_height = int(height * 0.7)
  52. # 构建 FFmpeg 命令,裁剪视频高度为原始高度的70%,并将宽度缩放为320x480
  53. ffmpeg_cmd = [
  54. "ffmpeg",
  55. "-i", video_url,
  56. "-vf", f"crop={width}:{new_height},scale=320:480",
  57. "-c:v", "libx264",
  58. "-c:a", "aac",
  59. "-y",
  60. output_video_path
  61. ]
  62. # 执行 FFmpeg 命令
  63. subprocess.run(ffmpeg_cmd)
  64. return output_video_path
  65. """
  66. 截取原视频最后一帧
  67. """
  68. @classmethod
  69. def video_png(cls,video_url):
  70. """
  71. png 生成图片位置
  72. :param video_url: 视频地址
  73. :return:
  74. """
  75. png = ''
  76. # 获取视频时长
  77. total_duration = cls.get_video_duration(video_url)
  78. time_offset = total_duration - 1 # 提取倒数第一秒的帧
  79. # 获取视频最后一秒,生成.jpg
  80. subprocess.run(
  81. ['ffmpeg', '-ss', str(time_offset), '-i', video_url, '-t', str(total_duration), '-vf', 'fps=1', png])
  82. return png
  83. """
  84. 生成片尾视频
  85. """
  86. @classmethod
  87. def new_pw_video(cls):
  88. # 添加音频到图片
  89. """
  90. png_path 图片地址
  91. pw_video 提供的片尾视频
  92. pw_duration 提供的片尾视频时长
  93. background_cmd 视频位置
  94. subtitle_cmd 字幕
  95. pw_path 生成视频地址
  96. :return:
  97. """
  98. ffmpeg_cmd = ['ffmpeg', '-loop', '1', '-i', png_path, '-i', pw_video, '-c:v', 'libx264', '-t',
  99. str(pw_duration), '-pix_fmt', 'yuv420p', '-c:a', 'aac', '-strict', 'experimental', '-shortest',
  100. '-vf', f"scale=320x480,{background_cmd},{subtitle_cmd}", pw_path]
  101. subprocess.run(ffmpeg_cmd)
  102. return pw_path
  103. """
  104. 设置统一格式拼接视频
  105. """
  106. @classmethod
  107. def concatenate_videos(cls, video_files, concatenated_video_path):
  108. # 拼接视频
  109. VIDEO_COUNTER = 0
  110. FF_INPUT = ""
  111. FF_SCALE = ""
  112. FF_FILTER = ""
  113. ffmpeg_cmd = ["ffmpeg"]
  114. for videos in video_files:
  115. # 添加输入文件
  116. FF_INPUT += f" -i {videos}"
  117. # 为每个视频文件统一长宽,并设置SAR(采样宽高比)
  118. FF_SCALE += f"[{VIDEO_COUNTER}:v]scale=320x480,setsar=1[v{VIDEO_COUNTER}];"
  119. # 为每个视频文件创建一个输入流,并添加到-filter_complex参数中
  120. FF_FILTER += f"[v{VIDEO_COUNTER}][{VIDEO_COUNTER}:a]"
  121. # 增加视频计数器
  122. VIDEO_COUNTER += 1
  123. # 构建最终的FFmpeg命令
  124. ffmpeg_cmd.extend(FF_INPUT.split())
  125. ffmpeg_cmd.extend(["-filter_complex", f"{FF_SCALE}{FF_FILTER}concat=n={VIDEO_COUNTER}:v=1:a=1[v][a]",
  126. "-map", "[v]", "-map", "[a]", concatenated_video_path])
  127. subprocess.run(ffmpeg_cmd)
  128. return concatenated_video_path
  129. """
  130. 单个视频拼接
  131. """
  132. @classmethod
  133. def single_video(cls):
  134. text_ptah = cls.bk_text_folders(mark)
  135. video_files = cls.concat_videos_with_subtitles(videos, audio_duration, platform, mark)
  136. with open(text_ptah, 'w') as f:
  137. for file in video_files:
  138. f.write(f"file '{file}'\n")
  139. if video_files == "":
  140. return ""
  141. if os.path.exists(s_path):
  142. # subtitle_cmd = f"subtitles={s_path}:force_style='Fontsize=11,Fontname=Hiragino Sans GB,Outline=0,PrimaryColour=&H000000,SecondaryColour=&H000000'"
  143. subtitle_cmd = f"subtitles={s_path}:force_style='Fontsize=12,Fontname=wqy-zenhei,Bold=1,Outline=0,PrimaryColour=&H000000,SecondaryColour=&H000000'"
  144. else:
  145. # subtitle_cmd = "drawtext=text='分享、转发给群友':fontsize=28:fontcolor=black:x=(w-text_w)/2:y=h-text_h-15"
  146. subtitle_cmd = "drawtext=text='分享、转发给群友':x=(w-text_w)/2:y=h-text_h-15:fontsize=28:fontcolor=black:fontfile=/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc"
  147. # 背景色参数
  148. background_cmd = "drawbox=y=ih-65:color=yellow@1.0:width=iw:height=0:t=fill"
  149. # 多线程数
  150. num_threads = 4
  151. # 构建 FFmpeg 命令,生成视频
  152. ffmpeg_cmd_oss = [
  153. "ffmpeg",
  154. "-f", "concat",
  155. "-safe", "0",
  156. "-i", f"{text_ptah}", # 视频文件列表
  157. "-i", audio_video, # 音频文件
  158. "-c:v", "libx264",
  159. "-c:a", "aac",
  160. "-threads", str(num_threads),
  161. "-vf", f"scale=320x480,{background_cmd},{subtitle_cmd}", # 添加背景色和字幕
  162. "-t", str(int(audio_duration)), # 保持与音频时长一致
  163. "-map", "0:v:0", # 映射第一个输入的视频流
  164. "-map", "1:a:0", # 映射第二个输入的音频流
  165. "-y", # 覆盖输出文件
  166. v_oss_path
  167. ]
  168. try:
  169. subprocess.run(ffmpeg_cmd_oss)
  170. print("视频处理完成!")
  171. if os.path.isfile(text_ptah):
  172. os.remove(text_ptah)
  173. except subprocess.CalledProcessError as e:
  174. print(f"视频处理失败:{e}")
  175. print(f"{mark}:视频拼接成功啦~~~")
  176. return video_files
  177. # 计算需要拼接的视频
  178. @classmethod
  179. def concat_videos_with_subtitles(cls, videos, audio_duration, platform, mark):
  180. # 计算视频文件列表总时长
  181. if platform == "baokuai":
  182. total_video_duration = sum(cls.get_video_duration(video_file) for video_file in videos)
  183. else:
  184. total_video_duration = sum(cls.get_video_duration(video_file[3]) for video_file in videos)
  185. if platform == "koubo" or platform == "zhannei" or platform == "baokuai":
  186. # 视频时长大于音频时长
  187. if total_video_duration > audio_duration:
  188. return videos
  189. # 计算音频秒数与视频秒数的比率,然后加一得到需要的视频数量
  190. video_audio_ratio = audio_duration / total_video_duration
  191. videos_needed = int(video_audio_ratio) + 2
  192. trimmed_video_list = videos * videos_needed
  193. return trimmed_video_list
  194. else:
  195. # 如果视频总时长小于音频时长,则不做拼接
  196. if total_video_duration < audio_duration:
  197. return ""
  198. # 如果视频总时长大于音频时长,则截断视频
  199. trimmed_video_list = []
  200. remaining_duration = audio_duration
  201. for video_file in videos:
  202. video_duration = cls.get_video_duration(video_file[3])
  203. if video_duration <= remaining_duration:
  204. # 如果视频时长小于或等于剩余时长,则将整个视频添加到列表中
  205. trimmed_video_list.append(video_file)
  206. remaining_duration -= video_duration
  207. else:
  208. trimmed_video_list.append(video_file)
  209. break
  210. return trimmed_video_list