新增video-loop-fill接口
* FIX : 修复ffmpeg_concat_medias没有添加inputs的bug * FIX: ffmpeg_slice_media和ffmpeg_slice_stream_media新增marker时间范围检查,如切割点不在视频时长范围内会报错 * 新增video-loop-fill接口 --------- Merge request URL: https://g-ldyi2063.coding.net/p/dev/d/modalDeploy/git/merge/4740 Co-authored-by: shuohigh@gmail.com
This commit is contained in:
@@ -162,99 +162,37 @@ with ffmpeg_worker_image.imports():
|
||||
async def ffmpeg_slice_process(media_source: MediaSource, media_markers: List[FFMpegSliceSegment],
|
||||
fn_id: str) -> List[str]:
|
||||
cache_filepath = f"{s3_mount}/{media_source.cache_filepath}"
|
||||
logger.info(cache_filepath)
|
||||
outputs: List[str] = []
|
||||
ffmpeg_cmd = VideoUtils.async_ffmpeg_init()
|
||||
ffmpeg_cmd.input(cache_filepath)
|
||||
filter_complex: List[str] = []
|
||||
for index, marker in enumerate(media_markers):
|
||||
filter_complex.extend(
|
||||
[
|
||||
f"[v:0]trim=start={marker.start.total_seconds()}:end={marker.end.total_seconds()},setpts=PTS-STARTPTS[cut{index}]",
|
||||
f"[a:0]atrim=start={marker.start.total_seconds()}:end={marker.end.total_seconds()},asetpts=PTS-STARTPTS[acut{index}]",
|
||||
]
|
||||
)
|
||||
ffmpeg_cmd.option('filter_complex', ';'.join(filter_complex))
|
||||
|
||||
for i, marker in enumerate(media_markers):
|
||||
filename = FileUtils.file_path_extend(os.path.basename(cache_filepath), str(i))
|
||||
output_filepath = f"{output_path_prefix}/slice/outputs/{fn_id}/{filename}"
|
||||
output_filepath = output_filepath.replace('//', '/')
|
||||
workdir = os.path.dirname(output_filepath)
|
||||
os.makedirs(workdir, exist_ok=True)
|
||||
outputs.append(output_filepath)
|
||||
ffmpeg_cmd.output(output_filepath,
|
||||
map=[f"[cut{i}]", f"[acut{i}]"],
|
||||
reset_timestamps="1",
|
||||
sc_threshold="0",
|
||||
g="1",
|
||||
force_key_frames="expr:gte(t,n_forced*1)",
|
||||
vcodec="libx264",
|
||||
acodec="aac",
|
||||
crf=16,
|
||||
r=30, )
|
||||
|
||||
await ffmpeg_cmd.execute()
|
||||
|
||||
s3_outputs = local_copy_to_s3(outputs)
|
||||
|
||||
logger.info(f"从{media_source.urn}切割")
|
||||
for marker in media_markers:
|
||||
logger.info(f"{marker.start.toFormatStr()} --> {marker.end.toFormatStr()}")
|
||||
segments = await VideoUtils.ffmpeg_slice_media(media_path=cache_filepath,
|
||||
media_markers=media_markers,
|
||||
output_path=f"{output_path_prefix}/{config.modal_environment}/slice/outputs/{fn_id}/output.mp4")
|
||||
s3_outputs = local_copy_to_s3(segments)
|
||||
return s3_outputs
|
||||
|
||||
@SentryUtils.sentry_tracker(name="视频切割任务", op="ffmpeg.slice", fn_id=fn_id,
|
||||
sentry_trace_id=sentry_trace.x_trace_id if sentry_trace else None,
|
||||
sentry_baggage=sentry_trace.x_baggage if sentry_trace else None)
|
||||
async def ffmpeg_hls_slice_process(media_source: MediaSource, media_markers: List[FFMpegSliceSegment],
|
||||
async def ffmpeg_hls_slice_process(media_source: MediaSource,
|
||||
media_markers: List[FFMpegSliceSegment],
|
||||
fn_id: str) -> List[str]:
|
||||
hls_m3u8_url = media_source.path
|
||||
logger.info(hls_m3u8_url)
|
||||
outputs: List[str] = []
|
||||
ffmpeg_cmd = VideoUtils.async_ffmpeg_init()
|
||||
ffmpeg_cmd.input(hls_m3u8_url,
|
||||
protocol_whitelist="file,http,https,tcp,tls",
|
||||
reconnect="1", # 自动重连
|
||||
reconnect_streamed="1",
|
||||
reconnect_delay_max="5")
|
||||
filter_complex: List[str] = []
|
||||
for index, marker in enumerate(media_markers):
|
||||
filter_complex.extend(
|
||||
[
|
||||
f"[v:0]trim=start={marker.start.total_seconds()}:end={marker.end.total_seconds()},setpts=PTS-STARTPTS[cut{index}]",
|
||||
f"[a:0]atrim=start={marker.start.total_seconds()}:end={marker.end.total_seconds()},asetpts=PTS-STARTPTS[acut{index}]",
|
||||
]
|
||||
)
|
||||
ffmpeg_cmd.option('filter_complex', ';'.join(filter_complex))
|
||||
for i, marker in enumerate(media_markers):
|
||||
paths = hls_m3u8_url.split(".")[:-1]
|
||||
paths.append('mp4')
|
||||
output_filepath = '.'.join(paths)
|
||||
filename = FileUtils.file_path_extend(os.path.basename(output_filepath), str(i))
|
||||
output_filepath = f"{output_path_prefix}/slice/outputs/{fn_id}/{filename}"
|
||||
output_filepath = output_filepath.replace('//', '/')
|
||||
workdir = os.path.dirname(output_filepath)
|
||||
os.makedirs(workdir, exist_ok=True)
|
||||
outputs.append(output_filepath)
|
||||
ffmpeg_cmd.output(output_filepath,
|
||||
map=[f"[cut{i}]", f"[acut{i}]"],
|
||||
reset_timestamps="1",
|
||||
sc_threshold="0",
|
||||
g="1",
|
||||
force_key_frames="expr:gte(t,n_forced*1)",
|
||||
vcodec="libx264",
|
||||
acodec="aac",
|
||||
crf=16,
|
||||
r=30, )
|
||||
|
||||
await ffmpeg_cmd.execute()
|
||||
|
||||
s3_outputs = local_copy_to_s3(outputs)
|
||||
|
||||
segments = await VideoUtils.ffmpeg_slice_stream_media(media_path=hls_m3u8_url,
|
||||
media_markers=media_markers,
|
||||
output_path=f"{output_path_prefix}/{config.modal_environment}/slice/outputs/{fn_id}/output.mp4")
|
||||
s3_outputs = local_copy_to_s3(segments)
|
||||
return s3_outputs
|
||||
|
||||
match media.protocol:
|
||||
case MediaProtocol.hls:
|
||||
outputs = await ffmpeg_hls_slice_process(media_source=media, media_markers=markers, fn_id=fn_id)
|
||||
outputs = await ffmpeg_hls_slice_process(media_source=media,
|
||||
media_markers=markers,
|
||||
fn_id=fn_id)
|
||||
case _:
|
||||
outputs = await ffmpeg_slice_process(media_source=media, media_markers=markers, fn_id=fn_id)
|
||||
outputs = await ffmpeg_slice_process(media_source=media,
|
||||
media_markers=markers,
|
||||
fn_id=fn_id)
|
||||
|
||||
return outputs, sentry_trace
|
||||
|
||||
@@ -277,14 +215,14 @@ with ffmpeg_worker_image.imports():
|
||||
|
||||
fn_id = current_function_call_id()
|
||||
|
||||
@SentryUtils.sentry_tracker(name="视频切割任务", op="ffmpeg.extract.audio", fn_id=fn_id,
|
||||
@SentryUtils.sentry_tracker(name="音频提取任务", op="ffmpeg.extract.audio", fn_id=fn_id,
|
||||
sentry_trace_id=sentry_trace.x_trace_id if sentry_trace else None,
|
||||
sentry_baggage=sentry_trace.x_baggage if sentry_trace else None)
|
||||
@SentryUtils.webhook_handler(webhook=webhook, func_id=fn_id)
|
||||
async def ffmpeg_process(media: MediaSource, fn_id: str) -> str:
|
||||
cache_filepath = f"{s3_mount}/{media.cache_filepath}"
|
||||
output_path = f"{output_path_prefix}/{config.modal_environment}/extract_audio/outputs/{fn_id}/output.wav"
|
||||
await VideoUtils.ffmpeg_extract_audio_async(cache_filepath, output_path)
|
||||
output_path = await VideoUtils.ffmpeg_extract_audio_async(cache_filepath, output_path)
|
||||
s3_outputs = local_copy_to_s3([output_path])
|
||||
return s3_outputs[0]
|
||||
|
||||
@@ -497,3 +435,41 @@ with ffmpeg_worker_image.imports():
|
||||
sentry_trace = SentryTransactionInfo(x_trace_id=sentry_sdk.get_traceparent(),
|
||||
x_baggage=sentry_sdk.get_baggage())
|
||||
return result, sentry_trace
|
||||
|
||||
|
||||
@app.function(timeout=600, cloud="aws",
|
||||
max_containers=config.ffmpeg_worker_concurrency,
|
||||
volumes={
|
||||
s3_mount: modal.CloudBucketMount(
|
||||
bucket_name=config.S3_bucket_name,
|
||||
secret=modal.Secret.from_name("aws-s3-secret",
|
||||
environment_name=config.modal_environment),
|
||||
),
|
||||
}, )
|
||||
@modal.concurrent(max_inputs=1)
|
||||
async def ffmpeg_loop_fill(media: MediaSource, audio: MediaSource,
|
||||
sentry_trace: Optional[SentryTransactionInfo] = None,
|
||||
webhook: Optional[WebhookNotify] = None):
|
||||
fn_id = current_function_call_id()
|
||||
|
||||
@SentryUtils.sentry_tracker(name="视频匹配音频长度", op="ffmpeg.loop.fill", fn_id=fn_id,
|
||||
sentry_trace_id=sentry_trace.x_trace_id if sentry_trace else None,
|
||||
sentry_baggage=sentry_trace.x_baggage if sentry_trace else None)
|
||||
@SentryUtils.webhook_handler(webhook=webhook, func_id=fn_id)
|
||||
async def ffmpeg_process(media: MediaSource, audio: MediaSource, func_id: str) -> str:
|
||||
video_path = f"{s3_mount}/{media.cache_filepath}"
|
||||
audio_path = f"{s3_mount}/{audio.cache_filepath}"
|
||||
|
||||
local_output = await VideoUtils.ffmpeg_fill_longest(video_path=video_path,
|
||||
audio_path=audio_path,
|
||||
output_path=f"{output_path_prefix}/{config.modal_environment}/loop_fill/{func_id}/output.mp4")
|
||||
s3_outputs = local_copy_to_s3([local_output])
|
||||
return s3_outputs[0]
|
||||
|
||||
output = await ffmpeg_process(media=media, audio=audio, func_id=fn_id)
|
||||
|
||||
if not sentry_trace:
|
||||
sentry_trace = SentryTransactionInfo(x_trace_id=sentry_sdk.get_traceparent(),
|
||||
x_baggage=sentry_sdk.get_baggage())
|
||||
|
||||
return output, sentry_trace
|
||||
|
||||
Reference in New Issue
Block a user