ffmpeg.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  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):
  83. """
  84. jpg_url 生成图片位置
  85. :param new_video_path: 视频地址
  86. :return:
  87. """
  88. try:
  89. jpg_url = video_path_url + 'png.jpg'
  90. # 获取视频时长
  91. total_duration = cls.get_video_duration(new_video_path)
  92. time_offset = total_duration - 1 # 提取倒数第一秒的帧
  93. # 获取视频最后一秒,生成.jpg
  94. subprocess.run(
  95. ['ffmpeg', '-ss', str(time_offset), '-i', new_video_path, '-t', str(total_duration), '-vf', 'fps=1', "-y", jpg_url])
  96. return jpg_url
  97. except Exception as e:
  98. return None
  99. """
  100. 生成片尾视频
  101. """
  102. @classmethod
  103. def pw_video(cls, jpg_url, video_path_url, pw_url, pw_srt):
  104. # 添加音频到图片
  105. """
  106. jpg_url 图片地址
  107. pw_video 提供的片尾视频
  108. pw_duration 提供的片尾视频时长
  109. new_video_path 视频位置
  110. subtitle_cmd 字幕
  111. pw_url 生成视频地址
  112. :return:
  113. """
  114. pw_srt_path = video_path_url + 'pw_video.srt'
  115. # 创建临时字幕文件
  116. with open(pw_srt_path, 'w') as f:
  117. f.write(pw_srt)
  118. # 片尾位置
  119. pw_url_path = video_path_url + 'pw_video.mp4'
  120. # 获取视频时长
  121. pw_duration = cls.get_video_duration(pw_url)
  122. # 添加字幕 wqy-zenhei Hiragino Sans GB
  123. subtitle_cmd = f"subtitles={pw_srt_path}:force_style='Fontsize=14,Fontname=wqy-zenhei,Outline=0,PrimaryColour=&H000000,SecondaryColour=&H000000,Bold=1,MarginV=155'"
  124. background_cmd = "drawbox=y=366/2:color=yellow@1.0:width=iw:height=50:t=fill"
  125. ffmpeg_cmd = ['ffmpeg', '-loop', '1', '-i', jpg_url, '-i', pw_url, '-c:v', 'libx264', '-t',
  126. str(pw_duration), '-pix_fmt', 'yuv420p', '-c:a', 'aac', '-strict', 'experimental', '-shortest',
  127. '-vf', f"scale=320x480,{background_cmd},{subtitle_cmd}", pw_url_path]
  128. subprocess.run(ffmpeg_cmd)
  129. return pw_url_path
  130. """
  131. 设置统一格式拼接视频
  132. """
  133. @classmethod
  134. def concatenate_videos(cls, video_list, video_path_url):
  135. concatenate_videos_url = video_path_url + 'concatenate_videos.mp4'
  136. # 拼接视频
  137. VIDEO_COUNTER = 0
  138. FF_INPUT = ""
  139. FF_SCALE = ""
  140. FF_FILTER = ""
  141. ffmpeg_cmd = ["ffmpeg"]
  142. for videos in video_list:
  143. # 添加输入文件
  144. FF_INPUT += f" -i {videos}"
  145. # 为每个视频文件统一长宽,并设置SAR(采样宽高比)
  146. FF_SCALE += f"[{VIDEO_COUNTER}:v]scale=320x480,setsar=1[v{VIDEO_COUNTER}];"
  147. # 为每个视频文件创建一个输入流,并添加到-filter_complex参数中
  148. FF_FILTER += f"[v{VIDEO_COUNTER}][{VIDEO_COUNTER}:a]"
  149. # 增加视频计数器
  150. VIDEO_COUNTER += 1
  151. # 构建最终的FFmpeg命令
  152. ffmpeg_cmd.extend(FF_INPUT.split())
  153. ffmpeg_cmd.extend(["-filter_complex", f"{FF_SCALE}{FF_FILTER}concat=n={VIDEO_COUNTER}:v=1:a=1[v][a]",
  154. "-map", "[v]", "-map", "[a]", "-y", concatenate_videos_url])
  155. subprocess.run(ffmpeg_cmd)
  156. return concatenate_videos_url
  157. """
  158. 单个视频拼接
  159. """
  160. @classmethod
  161. def single_video(cls, new_video_path, video_share, video_path_url, zm):
  162. single_video_url = video_path_url + 'single_video.mp4'
  163. single_video_srt = video_path_url + 'single_video.srt'
  164. # 获取时长
  165. duration = cls.get_video_duration(new_video_path)
  166. start_time = cls.seconds_to_srt_time(0)
  167. end_time = cls.seconds_to_srt_time(duration)
  168. single_video_txt = video_path_url + 'single_video.txt'
  169. with open(single_video_txt, 'w') as f:
  170. f.write(f"file '{new_video_path}'\n")
  171. with open(single_video_srt, 'w') as f:
  172. f.write(f"1\n{start_time} --> {end_time}\n\u2764\uFE0F{zm}\n\n")
  173. background_cmd = "drawbox=y=370/1:color=yellow@1.0:width=iw:height=70:t=fill"
  174. if video_share == '有':
  175. # 添加字幕 wqy-zenhei Hiragino Sans GB
  176. subtitle_cmd = f"subtitles={single_video_srt}:force_style='Fontsize=14,Fontname=wqy-zenhei,Outline=0,PrimaryColour=&H000000,SecondaryColour=&H000000,Bold=1,MarginV=30'"
  177. draw = f"{background_cmd},{subtitle_cmd}"
  178. else:
  179. subtitle_cmd = f"subtitles={single_video_srt}:force_style='Fontsize=14,Fontname=wqy-zenhei,Outline=2,PrimaryColour=&H00FFFF,SecondaryColour=&H000000,Bold=1,MarginV=30'"
  180. draw = f"{subtitle_cmd}"
  181. # 多线程数
  182. num_threads = 4
  183. # 构建 FFmpeg 命令,生成视频
  184. ffmpeg_cmd_oss = [
  185. "ffmpeg",
  186. "-f", "concat",
  187. "-safe", "0",
  188. "-i", f"{single_video_txt}",
  189. "-c:v", "libx264",
  190. "-c:a", "aac",
  191. "-threads", str(num_threads),
  192. "-vf", f"scale=320x480,{draw}",
  193. "-y",
  194. single_video_url
  195. ]
  196. subprocess.run(ffmpeg_cmd_oss)
  197. return single_video_url