import subprocess
import time
class FFmpeg():
"""
时间转换
"""
@classmethod
def seconds_to_srt_time(cls, 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):
try:
ffprobe_cmd = [
"ffprobe",
"-i", video_url,
"-show_entries", "format=duration",
"-v", "quiet",
"-of", "csv=p=0"
]
output = subprocess.check_output(ffprobe_cmd).decode("utf-8").strip()
return float(output)
except Exception as e:
return 0
"""
获取视频文件的时长(秒)
"""
@classmethod
def get_videos_duration(cls, video_file):
result = subprocess.run(
["ffprobe", "-v", "error", "-show_entries", "format=duration",
"-of", "default=noprint_wrappers=1:nokey=1", video_file],
capture_output=True, text=True)
return float(result.stdout)
"""
视频裁剪
"""
@classmethod
def video_tailor(cls, video_url):
output_video_path = ''
try:
# 获取视频的原始宽高信息
width, height = cls.get_w_h_size(video_url)
# 计算裁剪后的高度
new_height = int(height * 0.8)
# 构建 FFmpeg 命令,裁剪视频高度为原始高度的70%,并将宽度缩放为320x480
ffmpeg_cmd = [
"ffmpeg",
"-i", video_url,
"-vf", f"crop={width}:{new_height},scale=320:480",
"-c:v", "libx264",
"-c:a", "aac",
"-y",
output_video_path
]
# 执行 FFmpeg 命令
subprocess.run(ffmpeg_cmd, check=True)
return output_video_path
except Exception as e:
return None
"""
获取视频宽高
"""
@classmethod
def get_w_h_size(cls, new_video_path):
try:
# 获取视频的原始宽高信息
ffprobe_cmd = f"ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 {new_video_path}"
ffprobe_process = subprocess.Popen(ffprobe_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
output, _ = ffprobe_process.communicate()
output_decoded = output.decode().strip()
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, new_video_path, video_path_url, pw_random_id):
crop_url = video_path_url + str(pw_random_id) + 'crop.mp4'
# 获取视频的原始宽高信息
ffprobe_cmd = f"ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 {new_video_path}"
ffprobe_process = subprocess.Popen(ffprobe_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
output, _ = ffprobe_process.communicate()
width, height = map(int, output.decode().strip().split(','))
# 计算裁剪后的高度
new_height = int(height * 0.8)
# 构建 FFmpeg 命令,裁剪视频高度为原始高度的80%
ffmpeg_cmd = [
"ffmpeg",
"-i", new_video_path,
"-vf", f"crop={width}:{new_height}",
"-c:v", "libx264",
"-c:a", "aac",
"-y",
crop_url
]
subprocess.run(ffmpeg_cmd)
return crop_url
"""
视频截断
"""
@classmethod
def video_ggduration(cls, new_video_path, video_path_url, pw_random_id, gg_duration_total):
gg_duration_url = video_path_url + str(pw_random_id) + 'gg_duration.mp4'
# 获取视频时长
total_duration = cls.get_video_duration(new_video_path)
if total_duration == 0:
return new_video_path
duration = int(total_duration) - int(gg_duration_total)
if int(total_duration) < int(gg_duration_total):
return new_video_path
ffmpeg_cmd = [
"ffmpeg",
"-i", new_video_path,
"-c:v", "libx264",
"-c:a", "aac",
"-t", str(duration),
"-y",
gg_duration_url
]
subprocess.run(ffmpeg_cmd)
return gg_duration_url
"""
截取原视频最后一帧
"""
@classmethod
def video_png(cls, new_video_path, video_path_url, pw_random_id):
"""
jpg_url 生成图片位置
:param new_video_path: 视频地址
:return:
"""
# 获取视频的原始宽高信息
jpg_url = video_path_url + str(pw_random_id) + 'png.jpg'
# 获取视频时长
total_duration = cls.get_video_duration(new_video_path)
if total_duration == 0:
return new_video_path
time_offset = total_duration - 1 # 提取倒数第一秒的帧
# 获取视频最后一秒,生成.jpg
subprocess.run(
['ffmpeg', '-ss', str(time_offset), '-i', new_video_path, '-t', str(total_duration), '-vf', 'fps=1,scale=360:640', "-y", jpg_url])
return jpg_url
"""
获取视频音频
"""
@classmethod
def get_video_mp3(cls, video_file, video_path_url, pw_random_id):
pw_mp3_path = video_path_url + str(pw_random_id) +'pw_video.mp3'
command = [
'ffmpeg',
'-i', video_file,
'-q:a', '0',
'-map', 'a',
# '-codec:a', 'libmp3lame', # 指定 MP3 编码器
pw_mp3_path
]
subprocess.run(command)
time.sleep(1)
return pw_mp3_path
"""横屏视频改为竖屏"""
@classmethod
def update_video_h_w(cls, new_video_path, video_path_url, pw_random_id):
video_h_w_path = video_path_url + str(pw_random_id) +'video_h_w_video.mp4'
ffmpeg_cmd = f"ffmpeg -i {new_video_path} -vf 'scale=640:ih*640/iw,pad=iw:iw*16/9:(ow-iw)/2:(oh-ih)/2' {video_h_w_path}"
subprocess.run(ffmpeg_cmd, shell=True)
return video_h_w_path
"""横屏视频顶部增加字幕"""
@classmethod
def add_video_zm(cls, 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'
duration = cls.get_video_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)
# 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")
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}"
ffmpeg_cmd = [
"ffmpeg",
"-f", "concat",
"-safe", "0",
"-i", single_video_txt,
"-c:v", "libx264",
"-c:a", "aac",
# '-vf', f"scale=640x360",
"-vf", draw,
"-y",
single_video
]
subprocess.run(ffmpeg_cmd)
return single_video
"""
生成片尾视频
"""
@classmethod
def pw_video(cls, jpg_url, video_path_url, pw_url, pw_srt, pw_random_id, pw_mp3_path):
# 添加音频到图片
"""
jpg_url 图片地址
pw_video 提供的片尾视频
pw_duration 提供的片尾视频时长
new_video_path 视频位置
subtitle_cmd 字幕
pw_url 生成视频地址
:return:
"""
pw_srt_path = video_path_url + str(pw_random_id) +'pw_video.srt'
# 创建临时字幕文件
with open(pw_srt_path, 'w') as f:
f.write(pw_srt)
# 片尾位置
pw_url_path = video_path_url + str(pw_random_id) + 'pw_video.mp4'
# 获取视频时长
pw_duration = cls.get_video_duration(pw_url)
if pw_duration == 0:
return pw_url_path
time.sleep(2)
# 添加字幕 wqy-zenhei Hiragino Sans GB
height = 1080
margin_v = int(height) // 8 # 可根据需要调整字幕和背景之间的距离
subtitle_cmd = f"subtitles={pw_srt_path}:force_style='Fontsize=13,Fontname=wqy-zenhei,Outline=0,PrimaryColour=&H000000,SecondaryColour=&H000000,Bold=1,MarginV={margin_v}'"
bg_position_offset = (int(360) - 360//8) / 1.75
background_cmd = f"drawbox=y=(ih-{int(360)}/2-{bg_position_offset}):color=yellow@1.0:width=iw:height={int(360)}/4:t=fill"
ffmpeg_cmd = [
'ffmpeg',
'-loop', '1',
'-i', jpg_url, # 输入的图片文件
'-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 # 输出的视频文件路径
]
subprocess.run(ffmpeg_cmd)
return pw_url_path
"""
设置统一格式拼接视频
"""
@classmethod
def concatenate_videos(cls, video_list, video_path_url):
concatenate_videos_url = video_path_url + 'concatenate_videos.mp4'
# 拼接视频
VIDEO_COUNTER = 0
FF_INPUT = ""
FF_SCALE = ""
FF_FILTER = ""
ffmpeg_cmd = ["ffmpeg"]
for videos in video_list:
# 添加输入文件
FF_INPUT += f" -i {videos}"
# 为每个视频文件统一长宽,并设置SAR(采样宽高比)
FF_SCALE += f"[{VIDEO_COUNTER}:v]scale=360x640,setsar=1[v{VIDEO_COUNTER}];"
# 为每个视频文件创建一个输入流,并添加到-filter_complex参数中
FF_FILTER += f"[v{VIDEO_COUNTER}][{VIDEO_COUNTER}:a]"
# 增加视频计数器
VIDEO_COUNTER += 1
# 构建最终的FFmpeg命令
ffmpeg_cmd.extend(FF_INPUT.split())
ffmpeg_cmd.extend(["-filter_complex", f"{FF_SCALE}{FF_FILTER}concat=n={VIDEO_COUNTER}:v=1:a=1[v][a]",
"-map", "[v]", "-map", "[a]", "-y", concatenate_videos_url])
# subprocess.run(ffmpeg_cmd)
try:
result = subprocess.run(ffmpeg_cmd, capture_output=True, text=True)
if result.returncode != 0:
# 打印错误信息并返回 None
print("ffmpeg 错误信息:", result.stderr)
return concatenate_videos_url
else:
return concatenate_videos_url
except Exception as e:
# 捕获其他异常并返回 None
print("处理视频时出现异常:", e)
return concatenate_videos_url
"""
单个视频拼接
"""
@classmethod
def single_video(cls, new_video_path, video_path_url, zm):
single_video_url = video_path_url + 'single_video.mp4'
single_video_srt = video_path_url + 'single_video.srt'
# 获取时长
duration = cls.get_video_duration(new_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)
single_video_txt = video_path_url + 'single_video.txt'
with open(single_video_txt, 'w') as f:
f.write(f"file '{new_video_path}'\n")
height = 1080
box_height = int(int(height) / 4) # 框的高度为视频高度的四分之一
# background_cmd = f"drawbox=y=ih-{70 + box_height}-{int(box_height / 20)}:color=yellow@1.0:width=iw:height={box_height}:t=fill"
if zm:
# with open(single_video_srt, 'w') as f:
# # f.write(f"1\n{start_time} --> {end_time}\n{zm}\n\n")
# f.write(f"1\n{start_time} --> {end_time}\n\u2764\uFE0F{zm}\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=10'"
with open(single_video_srt, 'w') as f:
# f.write(f"1\n{start_time} --> {end_time}\n\u2764\uFE0F{zm}\n\n")
f.write(f"1\n{start_time} --> {end_time}\n\u2764\uFE0F{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'"
draw = f"{subtitle_cmd}"
# 多线程数
num_threads = 5
# 构建 FFmpeg 命令,生成视频
ffmpeg_cmd_oss = [
"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", f"{draw}",
# '-fs', '15M',
"-y",
single_video_url
]
subprocess.run(ffmpeg_cmd_oss)
return single_video_url
if __name__ == '__main__':
new_video_path = '/Users/tzld/Desktop/video_rewriting/path/output1.mp4'
video_path_url = '/Users/tzld/Desktop/video_rewriting/path/'
zm = '温馨提示:下方按钮可分享到群'
FFmpeg.single_video(new_video_path, video_path_url, zm)