|
@@ -1,309 +1,376 @@
|
|
|
-import asyncio
|
|
|
import json
|
|
|
-import os
|
|
|
import time
|
|
|
-from typing import List
|
|
|
|
|
|
-import cv2
|
|
|
+import orjson
|
|
|
import requests
|
|
|
from loguru import logger
|
|
|
-from mutagen.mp3 import MP3
|
|
|
|
|
|
+from protos import task_pb2, task_pb2_grpc
|
|
|
|
|
|
|
|
|
-class FFmpeg():
|
|
|
+class FFmpeg(object):
|
|
|
+
|
|
|
+ def __init__(self, container_id: str, stub: task_pb2_grpc.TaskRunnerStub):
|
|
|
+ self.container_id = container_id
|
|
|
+ self.stub = stub
|
|
|
|
|
|
- """
|
|
|
- 时间转换
|
|
|
- """
|
|
|
- @classmethod
|
|
|
- def seconds_to_srt_time(cls, seconds):
|
|
|
+ def seconds_to_srt_time(self, seconds):
|
|
|
+ """
|
|
|
+ 时间转换
|
|
|
+ """
|
|
|
hours = int(seconds // 3600)
|
|
|
minutes = int((seconds % 3600) // 60)
|
|
|
seconds = seconds % 60
|
|
|
milliseconds = int((seconds - int(seconds)) * 1000)
|
|
|
return f"{hours:02d}:{minutes:02d}:{int(seconds):02d},{milliseconds:03d}"
|
|
|
|
|
|
- """
|
|
|
- 获取单个视频时长
|
|
|
- """
|
|
|
- @classmethod
|
|
|
- def get_video_duration(cls, video_url):
|
|
|
- cap = cv2.VideoCapture(video_url)
|
|
|
- if cap.isOpened():
|
|
|
- rate = cap.get(5)
|
|
|
- frame_num = cap.get(7)
|
|
|
- duration = int(frame_num / rate)
|
|
|
- return duration
|
|
|
- return 0
|
|
|
-
|
|
|
+ # def get_video_duration(self, video_url):
|
|
|
+ # """
|
|
|
+ # 获取单个视频时长
|
|
|
+ # """
|
|
|
+ # cap = cv2.VideoCapture(video_url)
|
|
|
+ # if cap.isOpened():
|
|
|
+ # rate = cap.get(5)
|
|
|
+ # frame_num = cap.get(7)
|
|
|
+ # duration = int(frame_num / rate)
|
|
|
+ # return duration
|
|
|
+ # return 0
|
|
|
|
|
|
# """
|
|
|
# 获取视频文件的时长(秒)
|
|
|
# """
|
|
|
- # @classmethod
|
|
|
- # def get_videos_duration(cls, video_file):
|
|
|
- # result = cls.asyncio_run_subprocess(["ffprobe", "-v", "error", "-show_entries", "format=duration",
|
|
|
+ # def get_videos_duration(self, video_file):
|
|
|
+ # result = self.asyncio_run_subprocess(["ffprobe", "-v", "error", "-show_entries", "format=duration",
|
|
|
# "-of", "default=noprint_wrappers=1:nokey=1", video_file], timeout=10)
|
|
|
# return float(result)
|
|
|
|
|
|
- """
|
|
|
- 获取视频宽高
|
|
|
- """
|
|
|
- @classmethod
|
|
|
- def get_w_h_size(cls, new_video_path):
|
|
|
+ def get_w_h_size(self, new_video_path):
|
|
|
+ """
|
|
|
+ 获取视频宽高
|
|
|
+ """
|
|
|
try:
|
|
|
# 获取视频的原始宽高信息
|
|
|
- ffprobe_cmd = cls.asyncio_run_subprocess(["ffprobe", "-v" ,"error" ,"-select_streams" ,"v:0" ,"-show_entries", "stream=width,height" ,"-of" ,"csv=p=0" ,new_video_path],timeout=10)
|
|
|
- output_decoded = ffprobe_cmd.strip()
|
|
|
+ request = task_pb2.RunCommandRequest(id=self.container_id, command=["ffprobe", "-v" ,"error" ,"-select_streams" ,"v:0" ,"-show_entries", "stream=width,height" ,"-of" ,"csv=p=0" ,new_video_path])
|
|
|
+ # ffprobe_cmd = self.asyncio_run_subprocess(["ffprobe", "-v" ,"error" ,"-select_streams" ,"v:0" ,"-show_entries", "stream=width,height" ,"-of" ,"csv=p=0" ,new_video_path],timeout=10)
|
|
|
+ # output_decoded = ffprobe_cmd.strip()
|
|
|
+ output_decoded = ''
|
|
|
+ for response in self.stub.RunCommand(request=request):
|
|
|
+ output_decoded += response.msg
|
|
|
split_output = [value for value in output_decoded.split(',') if value.strip()]
|
|
|
height, width = map(int, split_output)
|
|
|
return width, height
|
|
|
except ValueError as e:
|
|
|
return 1920, 1080
|
|
|
|
|
|
-
|
|
|
- """
|
|
|
- 视频裁剪
|
|
|
- """
|
|
|
- @classmethod
|
|
|
- def video_crop(cls, video_path, file_path):
|
|
|
+ def video_crop(self, video_path, file_path):
|
|
|
+ """
|
|
|
+ 视频裁剪
|
|
|
+ """
|
|
|
crop_url = file_path + 'crop.mp4'
|
|
|
try:
|
|
|
- # 获取视频的原始宽高信息
|
|
|
- ffprobe_cmd = cls.asyncio_run_subprocess(
|
|
|
- ["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=width,height", "-of",
|
|
|
- "csv=p=0", video_path], timeout=10)
|
|
|
- width, height = map(int, ffprobe_cmd.strip().split(','))
|
|
|
- # 计算裁剪后的高度
|
|
|
- new_height = int(height * 0.8)
|
|
|
+ # # 获取视频的原始宽高信息
|
|
|
+ # ffprobe_cmd = cls.asyncio_run_subprocess(
|
|
|
+ # ["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=width,height", "-of",
|
|
|
+ # "csv=p=0", video_path], timeout=10)
|
|
|
+ # width, height = map(int, ffprobe_cmd.strip().split(','))
|
|
|
+ # # 计算裁剪后的高度
|
|
|
+ # new_height = int(height * 0.8)
|
|
|
|
|
|
# 构建 FFmpeg 命令,裁剪视频高度为原始高度的80%
|
|
|
- cls.asyncio_run_subprocess(
|
|
|
- [
|
|
|
- "ffmpeg",
|
|
|
- "-i", video_path,
|
|
|
- "-vf", f"crop={width}:{new_height}",
|
|
|
- "-c:v", "libx264",
|
|
|
- "-c:a", "aac",
|
|
|
- "-y",
|
|
|
- crop_url
|
|
|
- ],timeout=240)
|
|
|
+ request = task_pb2.RunCommandRequest(id=self.container_id, command=["ffmpeg", "-i", video_path, "-vf", f"\"crop=in_w:in_h*0.8\"", "-c:v", "h264_nvenc", "-c:a", "aac", "-y", "-cq", "28", "-preset", "slow", crop_url])
|
|
|
+ for _ in self.stub.RunCommand(request=request):
|
|
|
+ pass
|
|
|
+ # self.asyncio_run_subprocess(
|
|
|
+ # [
|
|
|
+ # "ffmpeg",
|
|
|
+ # "-i", video_path,
|
|
|
+ # "-vf", f"crop={width}:{new_height}",
|
|
|
+ # "-c:v", "libx264",
|
|
|
+ # "-c:a", "aac",
|
|
|
+ # "-y",
|
|
|
+ # crop_url
|
|
|
+ # ],timeout=240)
|
|
|
return crop_url
|
|
|
except Exception as e:
|
|
|
return crop_url
|
|
|
|
|
|
- """
|
|
|
- 视频截断
|
|
|
- """
|
|
|
- @classmethod
|
|
|
- def video_ggduration(cls, video_path, file_path, gg_duration_total):
|
|
|
+ def video_ggduration(self, video_path, file_path, gg_duration_total):
|
|
|
+ """
|
|
|
+ 视频截断
|
|
|
+ """
|
|
|
gg_duration_url = file_path + 'gg_duration.mp4'
|
|
|
# 获取视频时长
|
|
|
try:
|
|
|
- total_duration = cls.get_video_duration(video_path)
|
|
|
+ total_duration = self.get_duration(video_path)
|
|
|
if total_duration == 0:
|
|
|
return gg_duration_url
|
|
|
duration = int(total_duration) - int(gg_duration_total)
|
|
|
if int(total_duration) < int(gg_duration_total):
|
|
|
return gg_duration_url
|
|
|
- cls.asyncio_run_subprocess([
|
|
|
- "ffmpeg",
|
|
|
- "-i", video_path,
|
|
|
- "-c:v", "libx264",
|
|
|
- "-c:a", "aac",
|
|
|
- "-t", str(duration),
|
|
|
- "-y",
|
|
|
- gg_duration_url
|
|
|
- ], timeout= 360)
|
|
|
+ request = task_pb2.RunCommandRequest(id=self.container_id, command=["ffmpeg", "-i", video_path, "-c:v", "h264_nvenc", "-c:a", "aac", "-t", str(duration), "-y", "-cq", "28", "-preset", "slow", gg_duration_url])
|
|
|
+ for _ in self.stub.RunCommand(request=request):
|
|
|
+ pass
|
|
|
+ # self.asyncio_run_subprocess([
|
|
|
+ # "ffmpeg",
|
|
|
+ # "-i", video_path,
|
|
|
+ # "-c:v", "libx264",
|
|
|
+ # "-c:a", "aac",
|
|
|
+ # "-t", str(duration),
|
|
|
+ # "-y",
|
|
|
+ # gg_duration_url
|
|
|
+ # ], timeout= 360)
|
|
|
return gg_duration_url
|
|
|
except Exception as e:
|
|
|
return gg_duration_url
|
|
|
|
|
|
- """
|
|
|
- 截取原视频最后一帧
|
|
|
- """
|
|
|
- @classmethod
|
|
|
- def video_png(cls, video_path, file_path):
|
|
|
+ def video_png(self, video_path, file_path):
|
|
|
+ """
|
|
|
+ 截取原视频最后一帧
|
|
|
+ """
|
|
|
# 获取视频的原始宽高信息
|
|
|
jpg_url = file_path + 'png.jpg'
|
|
|
try:
|
|
|
- cls.asyncio_run_subprocess(
|
|
|
- ["ffmpeg", "-sseof", "-1", '-i', video_path, '-frames:v', '1', "-y", jpg_url], timeout=120)
|
|
|
+ # self.asyncio_run_subprocess(
|
|
|
+ # ["ffmpeg", "-sseof", "-1", '-i', video_path, '-frames:v', '1', "-y", jpg_url], timeout=120)
|
|
|
+ request = task_pb2.RunCommandRequest(id=self.container_id, command=["ffmpeg", "-sseof", "-1", '-i', video_path, '-frames:v', '1', "-y", jpg_url])
|
|
|
+ for _ in self.stub.RunCommand(request=request):
|
|
|
+ pass
|
|
|
return jpg_url
|
|
|
except Exception as e:
|
|
|
return jpg_url
|
|
|
|
|
|
- """
|
|
|
- 获取视频音频
|
|
|
- """
|
|
|
- @classmethod
|
|
|
- def get_video_mp3(cls, video_file, video_path_url, pw_random_id):
|
|
|
+ def get_video_mp3(self, video_file, video_path_url, pw_random_id):
|
|
|
+ """
|
|
|
+ 获取视频音频
|
|
|
+ """
|
|
|
pw_mp3_path = video_path_url + str(pw_random_id) +'pw_video.mp3'
|
|
|
try:
|
|
|
- cls.asyncio_run_subprocess([
|
|
|
- 'ffmpeg',
|
|
|
- '-i', video_file,
|
|
|
- '-q:a', '0',
|
|
|
- '-map', 'a',
|
|
|
- pw_mp3_path
|
|
|
- ], timeout=120)
|
|
|
+ request = task_pb2.RunCommandRequest(id=self.container_id, command=['ffmpeg', '-i', video_file, '-q:a', '0', '-map', 'a', pw_mp3_path])
|
|
|
+ for _ in self.stub.RunCommand(request=request):
|
|
|
+ pass
|
|
|
+ # self.asyncio_run_subprocess([
|
|
|
+ # 'ffmpeg',
|
|
|
+ # '-i', video_file,
|
|
|
+ # '-q:a', '0',
|
|
|
+ # '-map', 'a',
|
|
|
+ # pw_mp3_path
|
|
|
+ # ], timeout=120)
|
|
|
time.sleep(1)
|
|
|
return pw_mp3_path
|
|
|
except Exception as e:
|
|
|
return pw_mp3_path
|
|
|
|
|
|
- @classmethod
|
|
|
- def get_pw_video_mp3(cls, video_file, video_path_url):
|
|
|
+ def get_pw_video_mp3(self, video_file, video_path_url):
|
|
|
bgm_pw_path_mp3 = video_file + 'bgm_pw.mp3'
|
|
|
try:
|
|
|
- cls.asyncio_run_subprocess([
|
|
|
- 'ffmpeg',
|
|
|
- '-i', video_path_url, # 输入的视频文件路径
|
|
|
- '-q:a', '0', # 设置音频质量为最佳
|
|
|
- '-map', 'a',
|
|
|
- '-y',
|
|
|
- bgm_pw_path_mp3
|
|
|
- ], timeout=120)
|
|
|
- time.sleep(1)
|
|
|
+ request = task_pb2.RunCommandRequest(id=self.container_id, command=['ffmpeg', '-i', video_path_url, '-q:a', '0', '-map', 'a', bgm_pw_path_mp3])
|
|
|
+ for _ in self.stub.RunCommand(request=request):
|
|
|
+ pass
|
|
|
+ # self.asyncio_run_subprocess([
|
|
|
+ # 'ffmpeg',
|
|
|
+ # '-i', video_path_url, # 输入的视频文件路径
|
|
|
+ # '-q:a', '0', # 设置音频质量为最佳
|
|
|
+ # '-map', 'a',
|
|
|
+ # '-y',
|
|
|
+ # bgm_pw_path_mp3
|
|
|
+ # ], timeout=120)
|
|
|
+ # time.sleep(1)
|
|
|
return bgm_pw_path_mp3
|
|
|
except Exception as e:
|
|
|
return bgm_pw_path_mp3
|
|
|
|
|
|
-
|
|
|
- """
|
|
|
- 片尾增加bgm
|
|
|
- """
|
|
|
- @classmethod
|
|
|
- def video_add_bgm(cls, video_file, bgm_path, video_path_url):
|
|
|
+ def video_add_bgm(self, video_file, bgm_path, video_path_url):
|
|
|
+ """
|
|
|
+ 片尾增加bgm
|
|
|
+ """
|
|
|
bgm_pw_path = video_path_url + 'bgm_pw.mp4'
|
|
|
- pw_duration = cls.get_video_duration(video_file)
|
|
|
+ pw_duration = self.get_duration(video_file)
|
|
|
|
|
|
try:
|
|
|
pw_path_txt = video_path_url + 'bgm_pw_video.txt'
|
|
|
- with open(pw_path_txt, 'w') as f:
|
|
|
- f.write(f"file '{video_file}'\n")
|
|
|
- cls.asyncio_run_subprocess([
|
|
|
- "ffmpeg",
|
|
|
- "-f", "concat",
|
|
|
- "-safe", "0",
|
|
|
- "-i", f"{pw_path_txt}", # 视频序列输入的文本文件
|
|
|
- "-i", bgm_path, # 音频文件
|
|
|
- "-c:v", "libx264", # 视频编码格式
|
|
|
- "-t", str(pw_duration), # 输出视频的持续时间
|
|
|
- '-c:a', 'aac', # 音频编码格式
|
|
|
- '-b:v', '260k', # 视频比特率
|
|
|
- '-b:a', '96k', # 音频比特率
|
|
|
- '-threads', '2', # 线程数
|
|
|
- # '-vf', f'{background_cmd},{subtitle_cmd}', # 视频过滤器(背景和字幕)
|
|
|
- "-filter_complex", "[1:a]volume=0.6[a1];[0:a][a1]amerge=inputs=2[aout]", # 混合音频流
|
|
|
- "-map", "0:v:0", # 映射视频流来自第一个输入文件(视频)
|
|
|
- "-map", "[aout]", # 映射混合后的音频流
|
|
|
- '-y', # 强制覆盖输出文件
|
|
|
- bgm_pw_path # 输出文件路径
|
|
|
- ], timeout=500)
|
|
|
- time.sleep(1)
|
|
|
+ # with open(pw_path_txt, 'w') as f:
|
|
|
+ # f.write(f"file '{video_file}'\n")
|
|
|
+ payload = f"file '{video_file}'\n".encode()
|
|
|
+ request = task_pb2.PutFileRequest(id=self.container_id, payload=payload, path=pw_path_txt)
|
|
|
+ self.stub.PutFile(request=request)
|
|
|
+ request = task_pb2.RunCommandRequest(id=self.container_id,
|
|
|
+ command=[
|
|
|
+ "ffmpeg",
|
|
|
+ "-f", "concat",
|
|
|
+ "-safe", "0",
|
|
|
+ "-i", f"{pw_path_txt}", # 视频序列输入的文本文件
|
|
|
+ "-i", bgm_path, # 音频文件
|
|
|
+ "-c:v", "h264_nvenc", # 视频编码格式
|
|
|
+ '-c:a', 'aac', # 音频编码格式
|
|
|
+ "-t", str(pw_duration), # 输出视频的持续时间
|
|
|
+ '-b:v', '260k', # 视频比特率
|
|
|
+ '-b:a', '96k', # 音频比特率
|
|
|
+ '-threads', '2', # 线程数
|
|
|
+ # '-vf', f'{background_cmd},{subtitle_cmd}', # 视频过滤器(背景和字幕)
|
|
|
+ "-filter_complex", "\"[1:a]volume=0.6[a1];[0:a][a1]amerge=inputs=2[aout]\"", # 混合音频流
|
|
|
+ "-map", "\"0:v:0\"", # 映射视频流来自第一个输入文件(视频)
|
|
|
+ "-map", "\"[aout]\"", # 映射混合后的音频流
|
|
|
+ '-y', # 强制覆盖输出文件
|
|
|
+ "-cq", "28",
|
|
|
+ "-preset", "slow",
|
|
|
+ bgm_pw_path # 输出文件路径
|
|
|
+ ])
|
|
|
+ for _ in self.stub.RunCommand(request=request):
|
|
|
+ pass
|
|
|
+ # self.asyncio_run_subprocess([
|
|
|
+ # "ffmpeg",
|
|
|
+ # "-f", "concat",
|
|
|
+ # "-safe", "0",
|
|
|
+ # "-i", f"{pw_path_txt}", # 视频序列输入的文本文件
|
|
|
+ # "-i", bgm_path, # 音频文件
|
|
|
+ # "-c:v", "libx264", # 视频编码格式
|
|
|
+ # "-t", str(pw_duration), # 输出视频的持续时间
|
|
|
+ # '-c:a', 'aac', # 音频编码格式
|
|
|
+ # '-b:v', '260k', # 视频比特率
|
|
|
+ # '-b:a', '96k', # 音频比特率
|
|
|
+ # '-threads', '2', # 线程数
|
|
|
+ # # '-vf', f'{background_cmd},{subtitle_cmd}', # 视频过滤器(背景和字幕)
|
|
|
+ # "-filter_complex", "[1:a]volume=0.6[a1];[0:a][a1]amerge=inputs=2[aout]", # 混合音频流
|
|
|
+ # "-map", "0:v:0", # 映射视频流来自第一个输入文件(视频)
|
|
|
+ # "-map", "[aout]", # 映射混合后的音频流
|
|
|
+ # '-y', # 强制覆盖输出文件
|
|
|
+ # bgm_pw_path # 输出文件路径
|
|
|
+ # ], timeout=500)
|
|
|
+ # time.sleep(1)
|
|
|
return bgm_pw_path
|
|
|
except Exception as e:
|
|
|
return bgm_pw_path
|
|
|
|
|
|
- """横屏视频改为竖屏"""
|
|
|
- @classmethod
|
|
|
- def update_video_h_w(cls, video_path, file_path):
|
|
|
+ def update_video_h_w(self, video_path, file_path):
|
|
|
+ """横屏视频改为竖屏"""
|
|
|
video_h_w_path = file_path +'video_h_w_video.mp4'
|
|
|
try:
|
|
|
- cls.asyncio_run_subprocess(["ffmpeg" ,"-i" ,video_path ,"-vf" ,"scale=640:ih*640/iw,pad=iw:iw*16/9:(ow-iw)/2:(oh-ih)/2" ,video_h_w_path],timeout=420)
|
|
|
+ request = task_pb2.RunCommandRequest(id=self.container_id, command=["ffmpeg" ,"-i" ,video_path ,"-vf" ,"\"scale=640:ih*640/iw,pad=iw:iw*16/9:(ow-iw)/2:(oh-ih)/2\"" ,video_h_w_path])
|
|
|
+ for _ in self.stub.RunCommand(request=request):
|
|
|
+ pass
|
|
|
+ # self.asyncio_run_subprocess(["ffmpeg" ,"-i" ,video_path ,"-vf" ,"scale=640:ih*640/iw,pad=iw:iw*16/9:(ow-iw)/2:(oh-ih)/2" ,video_h_w_path],timeout=420)
|
|
|
return video_h_w_path
|
|
|
except Exception as e:
|
|
|
return video_h_w_path
|
|
|
|
|
|
- """视频转为640像素"""
|
|
|
- @classmethod
|
|
|
- def video_640(cls, video_path, file_path):
|
|
|
+ def video_640(self, video_path, file_path):
|
|
|
+ """视频转为640像素"""
|
|
|
video_url = file_path + 'pixelvideo.mp4'
|
|
|
try:
|
|
|
- cls.asyncio_run_subprocess(["ffmpeg" ,"-i" ,video_path ,"-vf" ,"scale=360:640" ,video_url],timeout=420)
|
|
|
+ request = task_pb2.RunCommandRequest(id=self.container_id, command=["ffmpeg" ,"-i" ,video_path ,"-vf" ,"\"scale=360:640\"" ,video_url])
|
|
|
+ for _ in self.stub.RunCommand(request=request):
|
|
|
+ pass
|
|
|
+ # self.asyncio_run_subprocess(["ffmpeg" ,"-i" ,video_path ,"-vf" ,"scale=360:640" ,video_url],timeout=420)
|
|
|
return video_url
|
|
|
except Exception as e:
|
|
|
return video_url
|
|
|
|
|
|
- @classmethod
|
|
|
- def concatenate_videos(cls, videos_paths, file_path):
|
|
|
+ def concatenate_videos(self, videos_paths, file_path):
|
|
|
video_url = file_path + 'rg_pw.mp4'
|
|
|
list_filename = file_path + 'rg_pw.txt'
|
|
|
- with open(list_filename, "w") as f:
|
|
|
- for video_path in videos_paths:
|
|
|
- f.write(f"file '{video_path}'\n")
|
|
|
+ # with open(list_filename, "w") as f:
|
|
|
+ # for video_path in videos_paths:
|
|
|
+ # f.write(f"file '{video_path}'\n")
|
|
|
+ payload = b''
|
|
|
+ for video_path in videos_paths:
|
|
|
+ payload += f"file '{video_path}'\n".encode()
|
|
|
+ request = task_pb2.PutFileRequest(id=self.container_id, payload=payload, path=list_filename)
|
|
|
+ self.stub.PutFile(request=request)
|
|
|
try:
|
|
|
- cls.asyncio_run_subprocess(
|
|
|
- ["ffmpeg", "-f", "concat", "-safe", "0", "-i", list_filename, "-c", "copy", video_url], timeout=420)
|
|
|
+ request = task_pb2.RunCommandRequest(id=self.container_id, command=["ffmpeg", "-f", "concat", "-safe", "0", "-i", list_filename, "-c", "copy", video_url])
|
|
|
+ for _ in self.stub.RunCommand(request=request):
|
|
|
+ pass
|
|
|
+ # self.asyncio_run_subprocess(
|
|
|
+ # ["ffmpeg", "-f", "concat", "-safe", "0", "-i", list_filename, "-c", "copy", video_url], timeout=420)
|
|
|
logger.info(f"[+] 视频转为640像素成功")
|
|
|
return video_url
|
|
|
except Exception as e:
|
|
|
return video_url
|
|
|
|
|
|
- """视频拼接到一起"""
|
|
|
- @classmethod
|
|
|
- def h_b_video(cls, video_path, pw_path, file_path):
|
|
|
+ def h_b_video(self, video_path, pw_path, file_path):
|
|
|
+ """视频拼接到一起"""
|
|
|
video_url = file_path + 'hbvideo.mp4'
|
|
|
try:
|
|
|
- cls.asyncio_run_subprocess(["ffmpeg","-i", video_path, "-i", pw_path, "-filter_complex" ,"[0:v]scale=360:640[v1]; [1:v]scale=360:640[v2]; [v1][0:a][v2][1:a]concat=n=2:v=1:a=1[outv][outa]" ,"-map" ,"[outv]" ,"-map" ,"[outa]" ,video_url],timeout=500)
|
|
|
+ request = task_pb2.RunCommandRequest(id=self.container_id, command=["ffmpeg","-i", video_path, "-i", pw_path, "-filter_complex" ,"\"[0:v]scale=360:640[v1]; [1:v]scale=360:640[v2]; [v1][0:a][v2][1:a]concat=n=2:v=1:a=1[outv][outa]\"" ,"-map" ,"[outv]" ,"-map" ,"[outa]" ,video_url])
|
|
|
+ for _ in self.stub.RunCommand(request=request):
|
|
|
+ pass
|
|
|
+ # self.asyncio_run_subprocess(["ffmpeg","-i", video_path, "-i", pw_path, "-filter_complex" ,"[0:v]scale=360:640[v1]; [1:v]scale=360:640[v2]; [v1][0:a][v2][1:a]concat=n=2:v=1:a=1[outv][outa]" ,"-map" ,"[outv]" ,"-map" ,"[outa]" ,video_url],timeout=500)
|
|
|
return video_url
|
|
|
except Exception as e:
|
|
|
return video_url
|
|
|
|
|
|
- """横屏视频顶部增加字幕"""
|
|
|
- @classmethod
|
|
|
- def add_video_zm(cls, new_video_path, video_path_url, pw_random_id, new_text):
|
|
|
+ def add_video_zm(self, new_video_path, video_path_url, pw_random_id, new_text):
|
|
|
+ """横屏视频顶部增加字幕"""
|
|
|
single_video_srt = video_path_url + str(pw_random_id) +'video_zm.srt'
|
|
|
single_video_txt = video_path_url + str(pw_random_id) +'video_zm.txt'
|
|
|
single_video = video_path_url + str(pw_random_id) +'video_zm.mp4'
|
|
|
try:
|
|
|
- duration = cls.get_video_duration(new_video_path)
|
|
|
+ duration = self.get_duration(new_video_path)
|
|
|
if duration == 0:
|
|
|
return new_video_path
|
|
|
- start_time = cls.seconds_to_srt_time(0)
|
|
|
- end_time = cls.seconds_to_srt_time(duration)
|
|
|
+ start_time = self.seconds_to_srt_time(0)
|
|
|
+ end_time = self.seconds_to_srt_time(duration)
|
|
|
# zm = '致敬伟大的教员,为整个民族\n感谢老人家历史向一代伟人'
|
|
|
- with open(single_video_txt, 'w') as f:
|
|
|
- f.write(f"file '{new_video_path}'\n")
|
|
|
- with open(single_video_srt, 'w') as f:
|
|
|
- f.write(f"1\n{start_time} --> {end_time}\n{new_text}\n\n")
|
|
|
+ payload = f"file '{new_video_path}'\n".encode()
|
|
|
+ request = task_pb2.PutFileRequest(id=self.container_id, payload=payload, path=single_video_txt)
|
|
|
+ self.stub.PutFile(request)
|
|
|
+ # with open(single_video_txt, 'w') as f:
|
|
|
+ # f.write(f"file '{new_video_path}'\n")
|
|
|
+ payload = f"1\n{start_time} --> {end_time}\n{new_text}\n\n".encode()
|
|
|
+ request = task_pb2.PutFileRequest(id=self.container_id, payload=payload, path=single_video_srt)
|
|
|
+ self.stub.PutFile(request)
|
|
|
+ # with open(single_video_srt, 'w') as f:
|
|
|
+ # f.write(f"1\n{start_time} --> {end_time}\n{new_text}\n\n")
|
|
|
subtitle_cmd = f"subtitles={single_video_srt}:force_style='Fontsize=12,Fontname=wqy-zenhei,Outline=2,PrimaryColour=&H00FFFF,SecondaryColour=&H000000,Bold=1,MarginV=225'"
|
|
|
- draw = f"{subtitle_cmd}"
|
|
|
- cls.asyncio_run_subprocess([
|
|
|
- "ffmpeg",
|
|
|
- "-f", "concat",
|
|
|
- "-safe", "0",
|
|
|
- "-i", single_video_txt,
|
|
|
- "-c:v", "libx264",
|
|
|
- "-c:a", "aac",
|
|
|
- "-vf", draw,
|
|
|
- "-y",
|
|
|
- single_video
|
|
|
- ],timeout=500)
|
|
|
+ request = task_pb2.RunCommandRequest(id=self.container_id,
|
|
|
+ command=[
|
|
|
+ "ffmpeg",
|
|
|
+ "-f", "concat",
|
|
|
+ "-safe", "0",
|
|
|
+ "-i", single_video_txt,
|
|
|
+ "-c:v", "h264_nvenc",
|
|
|
+ "-c:a", "aac",
|
|
|
+ "-vf", f"\"{subtitle_cmd}\"",
|
|
|
+ "-y",
|
|
|
+ "-cq", "28",
|
|
|
+ "-preset", "slow",
|
|
|
+ single_video
|
|
|
+ ])
|
|
|
+ for _ in self.stub.RunCommand(request=request):
|
|
|
+ pass
|
|
|
+ # self.asyncio_run_subprocess([
|
|
|
+ # "ffmpeg",
|
|
|
+ # "-f", "concat",
|
|
|
+ # "-safe", "0",
|
|
|
+ # "-i", single_video_txt,
|
|
|
+ # "-c:v", "libx264",
|
|
|
+ # "-c:a", "aac",
|
|
|
+ # "-vf", draw,
|
|
|
+ # "-y",
|
|
|
+ # single_video
|
|
|
+ # ],timeout=500)
|
|
|
# subprocess.run(ffmpeg_cmd)
|
|
|
return single_video
|
|
|
except Exception as e:
|
|
|
return single_video
|
|
|
|
|
|
- """获取mp3时长"""
|
|
|
- @classmethod
|
|
|
- def get_mp3_duration(cls, file_path):
|
|
|
- audio = MP3(file_path)
|
|
|
- duration = audio.info.length
|
|
|
- if duration:
|
|
|
- return int(duration)
|
|
|
- return 0
|
|
|
-
|
|
|
-
|
|
|
- """
|
|
|
- 生成片尾视频
|
|
|
- """
|
|
|
- @classmethod
|
|
|
- def pw_video(cls, jpg_path, file_path, pw_mp3_path, pw_srt):
|
|
|
- # 添加音频到图片
|
|
|
+ def get_duration(self, file_path):
|
|
|
+ """获取mp3时长"""
|
|
|
+ # audio = MP3(file_path)
|
|
|
+ # duration = audio.info.length
|
|
|
+ # if duration:
|
|
|
+ # return int(duration)
|
|
|
+ request = task_pb2.RunCommandRequest(id=self.container_id, command=['ffprobe', '-show_format', '-show_streams', '-of', 'json', '-v', 'quiet', '-hide_banner', file_path])
|
|
|
+ msg = ''
|
|
|
+ for response in self.stub.RunCommand(request):
|
|
|
+ msg += response.msg
|
|
|
+ obj = orjson.loads(msg)
|
|
|
+ return int(float(obj['format']['duration']))
|
|
|
+
|
|
|
+ def pw_video(self, jpg_path, file_path, pw_mp3_path, pw_srt):
|
|
|
"""
|
|
|
+ 生成片尾视频
|
|
|
+
|
|
|
jpg_url 图片地址
|
|
|
pw_video 提供的片尾视频
|
|
|
pw_duration 提供的片尾视频时长
|
|
@@ -312,12 +379,16 @@ class FFmpeg():
|
|
|
pw_url 生成视频地址
|
|
|
:return:
|
|
|
"""
|
|
|
+ # 添加音频到图片
|
|
|
pw_srt_path = file_path +'pw_video.srt'
|
|
|
- with open(pw_srt_path, 'w') as f:
|
|
|
- f.write(pw_srt)
|
|
|
+ payload = pw_srt.encode()
|
|
|
+ request = task_pb2.PutFileRequest(id=self.container_id, payload=payload, path=pw_srt_path)
|
|
|
+ self.stub.PutFile(request=request)
|
|
|
+ # with open(pw_srt_path, 'w') as f:
|
|
|
+ # f.write(pw_srt)
|
|
|
pw_url_path = file_path + 'pw_video.mp4'
|
|
|
try:
|
|
|
- pw_duration = cls.get_mp3_duration(pw_mp3_path)
|
|
|
+ pw_duration = self.get_duration(pw_mp3_path)
|
|
|
if pw_duration == 0:
|
|
|
return pw_url_path
|
|
|
time.sleep(2)
|
|
@@ -329,117 +400,182 @@ class FFmpeg():
|
|
|
background_cmd = f"drawbox=y=(ih-{int(360)}/2-{bg_position_offset}):color=yellow@1.0:width=iw:height={int(360)}/4:t=fill"
|
|
|
if "mp4" in jpg_path:
|
|
|
pw_path_txt = file_path + 'pw_path_video.txt'
|
|
|
- with open(pw_path_txt, 'w') as f:
|
|
|
- f.write(f"file '{jpg_path}'\n")
|
|
|
- cls.asyncio_run_subprocess([
|
|
|
- "ffmpeg",
|
|
|
- "-f", "concat",
|
|
|
- "-safe", "0",
|
|
|
- "-i", f"{pw_path_txt}", # 视频序列输入的文本文件
|
|
|
- "-i", pw_mp3_path, # 音频文件
|
|
|
- "-c:v", "libx264", # 视频编码格式
|
|
|
- "-t", str(pw_duration), # 输出视频的持续时间
|
|
|
- "-c:a", "aac", # 音频编码格式
|
|
|
- "-b:v", "260k", # 视频比特率
|
|
|
- "-b:a", "96k", # 音频比特率
|
|
|
- "-threads", "2", # 线程数
|
|
|
- "-vf", f"{background_cmd},{subtitle_cmd}", # 视频过滤器(背景和字幕)
|
|
|
- "-map", "0:v:0", # 映射视频流来自第一个输入文件(视频)
|
|
|
- "-map", "1:a:0", # 映射音频流来自第二个输入文件(音频)
|
|
|
- "-y", # 强制覆盖输出文件
|
|
|
- pw_url_path # 输出文件路径
|
|
|
- ], timeout=500)
|
|
|
+ payload = f"file '{jpg_path}'\n".encode()
|
|
|
+ request = task_pb2.PutFileRequest(id=self.container_id, payload=payload, path=pw_path_txt)
|
|
|
+ self.stub.PutFile(request=request)
|
|
|
+ # with open(pw_path_txt, 'w') as f:
|
|
|
+ # f.write(f"file '{jpg_path}'\n")
|
|
|
+ request = task_pb2.RunCommandRequest(id=self.container_id,
|
|
|
+ command=[
|
|
|
+ "ffmpeg",
|
|
|
+ "-f", "concat",
|
|
|
+ "-safe", "0",
|
|
|
+ "-i", f"{pw_path_txt}", # 视频序列输入的文本文件
|
|
|
+ "-i", pw_mp3_path, # 音频文件
|
|
|
+ "-c:v", "h264_nvenc", # 视频编码格式
|
|
|
+ "-t", str(pw_duration), # 输出视频的持续时间
|
|
|
+ "-c:a", "aac", # 音频编码格式
|
|
|
+ "-b:v", "260k", # 视频比特率
|
|
|
+ "-b:a", "96k", # 音频比特率
|
|
|
+ "-threads", "2", # 线程数
|
|
|
+ "-vf", f"\"{background_cmd},{subtitle_cmd}\"", # 视频过滤器(背景和字幕)
|
|
|
+ "-map", "0:v:0", # 映射视频流来自第一个输入文件(视频)
|
|
|
+ "-map", "1:a:0", # 映射音频流来自第二个输入文件(音频)
|
|
|
+ "-y", # 强制覆盖输出文件
|
|
|
+ "-cq", "28",
|
|
|
+ "-preset", "slow",
|
|
|
+ pw_url_path # 输出文件路径
|
|
|
+ ])
|
|
|
+ for _ in self.stub.RunCommand(request=request):
|
|
|
+ pass
|
|
|
+ # self.asyncio_run_subprocess([
|
|
|
+ # "ffmpeg",
|
|
|
+ # "-f", "concat",
|
|
|
+ # "-safe", "0",
|
|
|
+ # "-i", f"{pw_path_txt}", # 视频序列输入的文本文件
|
|
|
+ # "-i", pw_mp3_path, # 音频文件
|
|
|
+ # "-c:v", "libx264", # 视频编码格式
|
|
|
+ # "-t", str(pw_duration), # 输出视频的持续时间
|
|
|
+ # "-c:a", "aac", # 音频编码格式
|
|
|
+ # "-b:v", "260k", # 视频比特率
|
|
|
+ # "-b:a", "96k", # 音频比特率
|
|
|
+ # "-threads", "2", # 线程数
|
|
|
+ # "-vf", f"{background_cmd},{subtitle_cmd}", # 视频过滤器(背景和字幕)
|
|
|
+ # "-map", "0:v:0", # 映射视频流来自第一个输入文件(视频)
|
|
|
+ # "-map", "1:a:0", # 映射音频流来自第二个输入文件(音频)
|
|
|
+ # "-y", # 强制覆盖输出文件
|
|
|
+ # pw_url_path # 输出文件路径
|
|
|
+ # ], timeout=500)
|
|
|
else:
|
|
|
- cls.asyncio_run_subprocess([
|
|
|
- 'ffmpeg',
|
|
|
- '-loop', '1',
|
|
|
- '-i', jpg_path, # 输入的图片文件
|
|
|
- '-i', pw_mp3_path, # 输入的音频文件
|
|
|
- '-c:v', 'libx264', # 视频编码格式
|
|
|
- '-t', str(pw_duration), # 输出视频的持续时间,与音频持续时间相同
|
|
|
- '-pix_fmt', 'yuv420p', # 像素格式
|
|
|
- '-c:a', 'aac', # 音频编码格式
|
|
|
- '-strict', 'experimental', # 使用实验性编码器
|
|
|
- '-shortest', # 确保输出视频的长度与音频一致
|
|
|
- '-vf', f"{background_cmd},{subtitle_cmd}", # 视频过滤器,设置分辨率和其他过滤器
|
|
|
- pw_url_path # 输出的视频文件路径
|
|
|
- ], timeout=500)
|
|
|
- if os.path.exists(pw_srt_path):
|
|
|
- os.remove(pw_srt_path)
|
|
|
+ request = task_pb2.RunCommandRequest(id=self.container_id,
|
|
|
+ command=[
|
|
|
+ 'ffmpeg',
|
|
|
+ '-loop', '1',
|
|
|
+ '-i', jpg_path, # 输入的图片文件
|
|
|
+ '-i', pw_mp3_path, # 输入的音频文件
|
|
|
+ '-c:v', 'h264_nvenc', # 视频编码格式
|
|
|
+ '-t', str(pw_duration), # 输出视频的持续时间,与音频持续时间相同
|
|
|
+ '-pix_fmt', 'yuv420p', # 像素格式
|
|
|
+ '-c:a', 'aac', # 音频编码格式
|
|
|
+ '-strict', 'experimental', # 使用实验性编码器
|
|
|
+ '-shortest', # 确保输出视频的长度与音频一致
|
|
|
+ '-vf', f"\"{background_cmd},{subtitle_cmd}\"", # 视频过滤器,设置分辨率和其他过滤器
|
|
|
+ "-cq", "28",
|
|
|
+ "-preset", "slow",
|
|
|
+ pw_url_path # 输出的视频文件路径
|
|
|
+ ])
|
|
|
+ for _ in self.stub.RunCommand(request=request):
|
|
|
+ pass
|
|
|
+ # self.asyncio_run_subprocess([
|
|
|
+ # 'ffmpeg',
|
|
|
+ # '-loop', '1',
|
|
|
+ # '-i', jpg_path, # 输入的图片文件
|
|
|
+ # '-i', pw_mp3_path, # 输入的音频文件
|
|
|
+ # '-c:v', 'libx264', # 视频编码格式
|
|
|
+ # '-t', str(pw_duration), # 输出视频的持续时间,与音频持续时间相同
|
|
|
+ # '-pix_fmt', 'yuv420p', # 像素格式
|
|
|
+ # '-c:a', 'aac', # 音频编码格式
|
|
|
+ # '-strict', 'experimental', # 使用实验性编码器
|
|
|
+ # '-shortest', # 确保输出视频的长度与音频一致
|
|
|
+ # '-vf', f"{background_cmd},{subtitle_cmd}", # 视频过滤器,设置分辨率和其他过滤器
|
|
|
+ # pw_url_path # 输出的视频文件路径
|
|
|
+ # ], timeout=500)
|
|
|
+ # if os.path.exists(pw_srt_path):
|
|
|
+ # os.remove(pw_srt_path)
|
|
|
return pw_url_path
|
|
|
except Exception as e:
|
|
|
return pw_url_path
|
|
|
|
|
|
-
|
|
|
- """
|
|
|
- 单个视频拼接
|
|
|
- """
|
|
|
- @classmethod
|
|
|
- def single_video(cls, video_path, file_path, zm):
|
|
|
+ def single_video(self, video_path, file_path, zm):
|
|
|
+ """
|
|
|
+ 单个视频拼接
|
|
|
+ """
|
|
|
single_video_url = file_path + 'single_video.mp4'
|
|
|
single_video_srt = file_path + 'single_video.srt'
|
|
|
# 获取时长
|
|
|
try:
|
|
|
- duration = cls.get_video_duration(video_path)
|
|
|
+ duration = self.get_duration(video_path)
|
|
|
if duration == 0:
|
|
|
return single_video_url
|
|
|
- start_time = cls.seconds_to_srt_time(2)
|
|
|
- end_time = cls.seconds_to_srt_time(duration)
|
|
|
+ start_time = self.seconds_to_srt_time(2)
|
|
|
+ end_time = self.seconds_to_srt_time(duration)
|
|
|
single_video_txt = file_path + 'single_video.txt'
|
|
|
- with open(single_video_txt, 'w') as f:
|
|
|
- f.write(f"file '{video_path}'\n")
|
|
|
+ # with open(single_video_txt, 'w') as f:
|
|
|
+ # f.write(f"file '{video_path}'\n")
|
|
|
+ payload = f"file '{video_path}'\n".encode()
|
|
|
+ request = task_pb2.PutFileRequest(id=self.container_id, payload=payload, path=single_video_txt)
|
|
|
+ self.stub.PutFile(request=request)
|
|
|
if zm:
|
|
|
- with open(single_video_srt, 'w') as f:
|
|
|
- f.write(f"1\n{start_time} --> {end_time}\n<font color=\"red\">\u2764\uFE0F</font>{zm}\n\n")
|
|
|
+ payload = f"1\n{start_time} --> {end_time}\n<font color=\"red\">\u2764\uFE0F</font>{zm}\n\n".encode()
|
|
|
+ request = task_pb2.PutFileRequest(id=self.container_id, payload=payload, path=single_video_srt)
|
|
|
+ self.stub.PutFile(request=request)
|
|
|
+ # with open(single_video_srt, 'w') as f:
|
|
|
+ # f.write(f"1\n{start_time} --> {end_time}\n<font color=\"red\">\u2764\uFE0F</font>{zm}\n\n")
|
|
|
subtitle_cmd = f"subtitles={single_video_srt}:force_style='Fontsize=14,Fontname=wqy-zenhei,Outline=2,PrimaryColour=&H00FFFF,SecondaryColour=&H000000,Bold=1,MarginV=20'"
|
|
|
else:
|
|
|
subtitle_cmd = f"force_style='Fontsize=14,Fontname=wqy-zenhei,Outline=2,PrimaryColour=&H00FFFF,SecondaryColour=&H000000,Bold=1,MarginV=20'"
|
|
|
# 多线程数
|
|
|
num_threads = 5
|
|
|
# 构建 FFmpeg 命令,生成视频
|
|
|
- cls.asyncio_run_subprocess([
|
|
|
- "ffmpeg",
|
|
|
- "-f", "concat",
|
|
|
- "-safe", "0",
|
|
|
- "-i", f"{single_video_txt}",
|
|
|
- "-c:v", "libx264",
|
|
|
- "-c:a", "aac",
|
|
|
- '-b:v', '260k',
|
|
|
- "-b:a", "96k",
|
|
|
- "-threads", str(num_threads),
|
|
|
- "-vf", subtitle_cmd,
|
|
|
- "-y",
|
|
|
- single_video_url
|
|
|
- ], timeout=400)
|
|
|
- if os.path.exists(single_video_srt):
|
|
|
- os.remove(single_video_srt)
|
|
|
+ request = task_pb2.RunCommandRequest(id=self.container_id,
|
|
|
+ command=[
|
|
|
+ "ffmpeg",
|
|
|
+ "-f", "concat",
|
|
|
+ "-safe", "0",
|
|
|
+ "-i", f"{single_video_txt}",
|
|
|
+ "-c:v", "h264_nvenc",
|
|
|
+ "-c:a", "aac",
|
|
|
+ '-b:v', '260k',
|
|
|
+ "-b:a", "96k",
|
|
|
+ "-threads", str(num_threads),
|
|
|
+ "-vf", f"\"{subtitle_cmd}\"",
|
|
|
+ "-y",
|
|
|
+ "-cq", "28",
|
|
|
+ "-preset", "slow",
|
|
|
+ single_video_url
|
|
|
+ ])
|
|
|
+ for _ in self.stub.RunCommand(request=request):
|
|
|
+ pass
|
|
|
+ # self.asyncio_run_subprocess([
|
|
|
+ # "ffmpeg",
|
|
|
+ # "-f", "concat",
|
|
|
+ # "-safe", "0",
|
|
|
+ # "-i", f"{single_video_txt}",
|
|
|
+ # "-c:v", "libx264",
|
|
|
+ # "-c:a", "aac",
|
|
|
+ # '-b:v', '260k',
|
|
|
+ # "-b:a", "96k",
|
|
|
+ # "-threads", str(num_threads),
|
|
|
+ # "-vf", subtitle_cmd,
|
|
|
+ # "-y",
|
|
|
+ # single_video_url
|
|
|
+ # ], timeout=400)
|
|
|
+ # if os.path.exists(single_video_srt):
|
|
|
+ # os.remove(single_video_srt)
|
|
|
return single_video_url
|
|
|
except Exception as e:
|
|
|
return single_video_url
|
|
|
|
|
|
- @classmethod
|
|
|
- def asyncio_run_subprocess(cls, params: List[str], timeout: int = 30) -> str:
|
|
|
- async def run_subprocess():
|
|
|
- process = await asyncio.create_subprocess_exec(
|
|
|
- params[0],
|
|
|
- *params[1:],
|
|
|
- stdout=asyncio.subprocess.PIPE,
|
|
|
- stderr=asyncio.subprocess.PIPE,
|
|
|
- )
|
|
|
- try:
|
|
|
- out, err = await asyncio.wait_for(process.communicate(), timeout=timeout)
|
|
|
- if process.returncode != 0:
|
|
|
- raise IOError(err)
|
|
|
- return out.decode()
|
|
|
- except asyncio.TimeoutError:
|
|
|
- process.kill()
|
|
|
- out, err = await process.communicate()
|
|
|
- raise IOError(err)
|
|
|
- return asyncio.run(run_subprocess())
|
|
|
-
|
|
|
-
|
|
|
- @classmethod
|
|
|
- def get_http_duration(cls, videos_path):
|
|
|
+ # def asyncio_run_subprocess(self, params: List[str], timeout: int = 30) -> str:
|
|
|
+ # async def run_subprocess():
|
|
|
+ # process = await asyncio.create_subprocess_exec(
|
|
|
+ # params[0],
|
|
|
+ # *params[1:],
|
|
|
+ # stdout=asyncio.subprocess.PIPE,
|
|
|
+ # stderr=asyncio.subprocess.PIPE,
|
|
|
+ # )
|
|
|
+ # try:
|
|
|
+ # out, err = await asyncio.wait_for(process.communicate(), timeout=timeout)
|
|
|
+ # if process.returncode != 0:
|
|
|
+ # raise IOError(err)
|
|
|
+ # return out.decode()
|
|
|
+ # except asyncio.TimeoutError:
|
|
|
+ # process.kill()
|
|
|
+ # out, err = await process.communicate()
|
|
|
+ # raise IOError(err)
|
|
|
+ # return asyncio.run(run_subprocess())
|
|
|
+
|
|
|
+ def get_http_duration(self, videos_path):
|
|
|
total_duration = 0
|
|
|
for video_path in videos_path:
|
|
|
url = "http://101.37.24.17:5555/api/v1/ffmpeg/get_meta"
|
|
@@ -546,5 +682,3 @@ if __name__ == '__main__':
|
|
|
00:00:32,659 --> 00:00:32,860
|
|
|
吧"""
|
|
|
FFmpeg.pw_video(jpg_path, file_path, mp3_path, pw_srt)
|
|
|
-
|
|
|
-
|