You've already forked FrameTour-RenderWorker
mypy
This commit is contained in:
141
util/ffmpeg.py
141
util/ffmpeg.py
@@ -7,11 +7,19 @@ from typing import Optional, IO
|
||||
|
||||
from opentelemetry.trace import Status, StatusCode
|
||||
|
||||
from entity.ffmpeg import FfmpegTask, ENCODER_ARGS, VIDEO_ARGS, AUDIO_ARGS, MUTE_AUDIO_INPUT, get_mp4toannexb_filter
|
||||
from entity.ffmpeg import (
|
||||
FfmpegTask,
|
||||
ENCODER_ARGS,
|
||||
VIDEO_ARGS,
|
||||
AUDIO_ARGS,
|
||||
MUTE_AUDIO_INPUT,
|
||||
get_mp4toannexb_filter,
|
||||
)
|
||||
from telemetry import get_tracer
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def re_encode_and_annexb(file):
|
||||
with get_tracer("ffmpeg").start_as_current_span("re_encode_and_annexb") as span:
|
||||
span.set_attribute("file.path", file)
|
||||
@@ -30,40 +38,69 @@ def re_encode_and_annexb(file):
|
||||
_encoder_args = tuple(os.getenv("RE_ENCODE_ENCODER_ARGS", "").split(" "))
|
||||
else:
|
||||
_encoder_args = ENCODER_ARGS
|
||||
ffmpeg_process = subprocess.run(["ffmpeg", "-y", "-hide_banner", "-i", file,
|
||||
*(set() if has_audio else MUTE_AUDIO_INPUT),
|
||||
"-fps_mode", "cfr",
|
||||
"-map", "0:v", "-map", "0:a" if has_audio else "1:a",
|
||||
*_video_args, "-bsf:v", get_mp4toannexb_filter(),
|
||||
*AUDIO_ARGS, "-bsf:a", "setts=pts=DTS",
|
||||
*_encoder_args, "-shortest", "-fflags", "+genpts",
|
||||
"-f", "mpegts", file + ".ts"])
|
||||
ffmpeg_process = subprocess.run(
|
||||
[
|
||||
"ffmpeg",
|
||||
"-y",
|
||||
"-hide_banner",
|
||||
"-i",
|
||||
file,
|
||||
*(set() if has_audio else MUTE_AUDIO_INPUT),
|
||||
"-fps_mode",
|
||||
"cfr",
|
||||
"-map",
|
||||
"0:v",
|
||||
"-map",
|
||||
"0:a" if has_audio else "1:a",
|
||||
*_video_args,
|
||||
"-bsf:v",
|
||||
get_mp4toannexb_filter(),
|
||||
*AUDIO_ARGS,
|
||||
"-bsf:a",
|
||||
"setts=pts=DTS",
|
||||
*_encoder_args,
|
||||
"-shortest",
|
||||
"-fflags",
|
||||
"+genpts",
|
||||
"-f",
|
||||
"mpegts",
|
||||
file + ".ts",
|
||||
]
|
||||
)
|
||||
logger.info(" ".join(ffmpeg_process.args))
|
||||
span.set_attribute("ffmpeg.args", json.dumps(ffmpeg_process.args))
|
||||
logger.info("ReEncodeAndAnnexb: %s, returned: %s", file, ffmpeg_process.returncode)
|
||||
logger.info(
|
||||
"ReEncodeAndAnnexb: %s, returned: %s", file, ffmpeg_process.returncode
|
||||
)
|
||||
span.set_attribute("ffmpeg.code", ffmpeg_process.returncode)
|
||||
if ffmpeg_process.returncode == 0:
|
||||
span.set_status(Status(StatusCode.OK))
|
||||
span.set_attribute("file.size", os.path.getsize(file+".ts"))
|
||||
span.set_attribute("file.size", os.path.getsize(file + ".ts"))
|
||||
# os.remove(file)
|
||||
return file+".ts"
|
||||
return file + ".ts"
|
||||
else:
|
||||
span.set_status(Status(StatusCode.ERROR))
|
||||
return file
|
||||
|
||||
|
||||
# start_render函数已迁移到services/render_service.py中的DefaultRenderService
|
||||
# 保留原有签名用于向后兼容,但建议使用新的服务架构
|
||||
|
||||
|
||||
def start_render(ffmpeg_task):
|
||||
"""
|
||||
已迁移到新架构,建议使用 DefaultRenderService.render()
|
||||
已迁移到新架构,建议使用 DefaultRenderService.render()
|
||||
保留用于向后兼容
|
||||
"""
|
||||
logger.warning("start_render is deprecated, use DefaultRenderService.render() instead")
|
||||
logger.warning(
|
||||
"start_render is deprecated, use DefaultRenderService.render() instead"
|
||||
)
|
||||
from services import DefaultRenderService
|
||||
|
||||
render_service = DefaultRenderService()
|
||||
return render_service.render(ffmpeg_task)
|
||||
|
||||
|
||||
def handle_ffmpeg_output(stdout: Optional[bytes]) -> str:
|
||||
out_time = "0:0:0.0"
|
||||
if stdout is None:
|
||||
@@ -81,7 +118,8 @@ def handle_ffmpeg_output(stdout: Optional[bytes]) -> str:
|
||||
if line.startswith(b"speed="):
|
||||
speed = line.replace(b"speed=", b"").decode().strip()
|
||||
print("[ ]Speed:", out_time, "@", speed)
|
||||
return out_time+"@"+speed
|
||||
return out_time + "@" + speed
|
||||
|
||||
|
||||
def duration_str_to_float(duration_str: str) -> float:
|
||||
_duration = datetime.strptime(duration_str, "%H:%M:%S.%f") - datetime(1900, 1, 1)
|
||||
@@ -94,8 +132,18 @@ def probe_video_info(video_file):
|
||||
span.set_attribute("video.file", video_file)
|
||||
# 获取宽度和高度
|
||||
result = subprocess.run(
|
||||
["ffprobe", '-v', 'error', '-select_streams', 'v:0', '-show_entries', 'stream=width,height:format=duration', '-of',
|
||||
'csv=s=x:p=0', video_file],
|
||||
[
|
||||
"ffprobe",
|
||||
"-v",
|
||||
"error",
|
||||
"-select_streams",
|
||||
"v:0",
|
||||
"-show_entries",
|
||||
"stream=width,height:format=duration",
|
||||
"-of",
|
||||
"csv=s=x:p=0",
|
||||
video_file,
|
||||
],
|
||||
stderr=subprocess.STDOUT,
|
||||
**subprocess_args(True)
|
||||
)
|
||||
@@ -104,14 +152,14 @@ def probe_video_info(video_file):
|
||||
if result.returncode != 0:
|
||||
span.set_status(Status(StatusCode.ERROR))
|
||||
return 0, 0, 0
|
||||
all_result = result.stdout.decode('utf-8').strip()
|
||||
all_result = result.stdout.decode("utf-8").strip()
|
||||
span.set_attribute("ffprobe.out", all_result)
|
||||
if all_result == '':
|
||||
if all_result == "":
|
||||
span.set_status(Status(StatusCode.ERROR))
|
||||
return 0, 0, 0
|
||||
span.set_status(Status(StatusCode.OK))
|
||||
wh, duration = all_result.split('\n')
|
||||
width, height = wh.strip().split('x')
|
||||
wh, duration = all_result.split("\n")
|
||||
width, height = wh.strip().split("x")
|
||||
return int(width), int(height), float(duration)
|
||||
|
||||
|
||||
@@ -119,8 +167,19 @@ def probe_video_audio(video_file, type=None):
|
||||
tracer = get_tracer(__name__)
|
||||
with tracer.start_as_current_span("probe_video_audio") as span:
|
||||
span.set_attribute("video.file", video_file)
|
||||
args = ["ffprobe", "-hide_banner", "-v", "error", "-select_streams", "a", "-show_entries", "stream=index", "-of", "csv=p=0"]
|
||||
if type == 'concat':
|
||||
args = [
|
||||
"ffprobe",
|
||||
"-hide_banner",
|
||||
"-v",
|
||||
"error",
|
||||
"-select_streams",
|
||||
"a",
|
||||
"-show_entries",
|
||||
"stream=index",
|
||||
"-of",
|
||||
"csv=p=0",
|
||||
]
|
||||
if type == "concat":
|
||||
args.append("-safe")
|
||||
args.append("0")
|
||||
args.append("-f")
|
||||
@@ -130,16 +189,16 @@ def probe_video_audio(video_file, type=None):
|
||||
result = subprocess.run(args, stderr=subprocess.STDOUT, **subprocess_args(True))
|
||||
span.set_attribute("ffprobe.args", json.dumps(result.args))
|
||||
span.set_attribute("ffprobe.code", result.returncode)
|
||||
logger.info("probe_video_audio: %s", result.stdout.decode('utf-8').strip())
|
||||
logger.info("probe_video_audio: %s", result.stdout.decode("utf-8").strip())
|
||||
if result.returncode != 0:
|
||||
return False
|
||||
if result.stdout.decode('utf-8').strip() == '':
|
||||
if result.stdout.decode("utf-8").strip() == "":
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
# 音频淡出2秒
|
||||
def fade_out_audio(file, duration, fade_out_sec = 2):
|
||||
def fade_out_audio(file, duration, fade_out_sec=2):
|
||||
if type(duration) == str:
|
||||
try:
|
||||
duration = float(duration)
|
||||
@@ -157,7 +216,25 @@ def fade_out_audio(file, duration, fade_out_sec = 2):
|
||||
os.remove(new_fn)
|
||||
logger.info("delete tmp file: " + new_fn)
|
||||
try:
|
||||
process = subprocess.run(["ffmpeg", "-i", file, "-c:v", "copy", "-c:a", "aac", "-af", "afade=t=out:st=" + str(duration - fade_out_sec) + ":d=" + str(fade_out_sec), "-y", new_fn], **subprocess_args(True))
|
||||
process = subprocess.run(
|
||||
[
|
||||
"ffmpeg",
|
||||
"-i",
|
||||
file,
|
||||
"-c:v",
|
||||
"copy",
|
||||
"-c:a",
|
||||
"aac",
|
||||
"-af",
|
||||
"afade=t=out:st="
|
||||
+ str(duration - fade_out_sec)
|
||||
+ ":d="
|
||||
+ str(fade_out_sec),
|
||||
"-y",
|
||||
new_fn,
|
||||
],
|
||||
**subprocess_args(True)
|
||||
)
|
||||
span.set_attribute("ffmpeg.args", json.dumps(process.args))
|
||||
logger.info(" ".join(process.args))
|
||||
if process.returncode != 0:
|
||||
@@ -173,7 +250,6 @@ def fade_out_audio(file, duration, fade_out_sec = 2):
|
||||
return file
|
||||
|
||||
|
||||
|
||||
# Create a set of arguments which make a ``subprocess.Popen`` (and
|
||||
# variants) call work with or without Pyinstaller, ``--noconsole`` or
|
||||
# not, on Windows and Linux. Typical use::
|
||||
@@ -186,7 +262,7 @@ def fade_out_audio(file, duration, fade_out_sec = 2):
|
||||
# **subprocess_args(False))
|
||||
def subprocess_args(include_stdout=True):
|
||||
# The following is true only on Windows.
|
||||
if hasattr(subprocess, 'STARTUPINFO'):
|
||||
if hasattr(subprocess, "STARTUPINFO"):
|
||||
# On Windows, subprocess calls will pop up a command window by default
|
||||
# when run from Pyinstaller with the ``--noconsole`` option. Avoid this
|
||||
# distraction.
|
||||
@@ -210,7 +286,7 @@ def subprocess_args(include_stdout=True):
|
||||
#
|
||||
# So, add it only if it's needed.
|
||||
if include_stdout:
|
||||
ret = {'stdout': subprocess.PIPE}
|
||||
ret = {"stdout": subprocess.PIPE}
|
||||
else:
|
||||
ret = {}
|
||||
|
||||
@@ -218,8 +294,5 @@ def subprocess_args(include_stdout=True):
|
||||
# with the ``--noconsole`` option requires redirecting everything
|
||||
# (stdin, stdout, stderr) to avoid an OSError exception
|
||||
# "[Error 6] the handle is invalid."
|
||||
ret.update({'stdin': subprocess.PIPE,
|
||||
'startupinfo': si,
|
||||
'env': env})
|
||||
ret.update({"stdin": subprocess.PIPE, "startupinfo": si, "env": env})
|
||||
return ret
|
||||
|
||||
|
||||
Reference in New Issue
Block a user