110 lines
4.1 KiB
Plaintext

import json
import os
import asyncio
import tempfile
segments = json.loads(params_kw.get('segments', '[]'))
transitions = params_kw.get('transitions', 'fade')
output_name = params_kw.get('output_name', 'merged_output.mp4')
output_dir = os.path.join('/tmp/ffmpeg_output', output_name)
os.makedirs(output_dir, exist_ok=True)
output_path = os.path.join(output_dir, 'output.mp4')
try:
if not segments or len(segments) == 0:
result = json.dumps({"status": "error", "error": "No segments provided"})
elif len(segments) == 1:
# Single segment, just copy
seg = segments[0]
proc = await asyncio.create_subprocess_exec(
'ffmpeg', '-y', '-i', seg['path'], '-c', 'copy', output_path,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await proc.communicate()
if proc.returncode == 0:
result = json.dumps({"status": "success", "output_path": output_path})
else:
result = json.dumps({"status": "error", "error": stderr.decode()})
elif transitions == 'none' or len(segments) == 1:
# Simple concat demuxer (no transitions)
concat_list = os.path.join(output_dir, 'concat_list.txt')
with open(concat_list, 'w') as f:
for seg in segments:
f.write(f"file '{seg['path']}'\n")
proc = await asyncio.create_subprocess_exec(
'ffmpeg', '-y', '-f', 'concat', '-safe', '0',
'-i', concat_list, '-c', 'copy', output_path,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await proc.communicate()
if proc.returncode == 0:
result = json.dumps({"status": "success", "output_path": output_path})
else:
result = json.dumps({"status": "error", "error": stderr.decode()})
else:
# Crossfade transitions using xfade filter
# Build complex xfade filter chain
n = len(segments)
fade_duration = 1.0 # 1 second crossfade
inputs = []
for seg in segments:
inputs.extend(['-i', seg['path']])
# Build xfade filter chain
filter_parts = []
# Calculate offsets: each offset is cumulative duration minus fade overlaps
offsets = []
cumulative = float(segments[0]['duration'])
for i in range(1, n):
offset = cumulative - fade_duration
offsets.append(offset)
cumulative = offset + float(segments[i]['duration'])
if n == 2:
filter_str = f'[0:v][1:v]xfade=transition=fade:duration={fade_duration}:offset={offsets[0]}[vout];[0:a][1:a]acrossfade=d={fade_duration}[aout]'
else:
# Chain xfade filters for multiple segments
prev_label = '0:v'
audio_prev = '0:a'
vfilters = []
afilters = []
for i in range(1, n):
out_label = f'v{i}' if i < n - 1 else 'vout'
audio_out = f'a{i}' if i < n - 1 else 'aout'
vfilters.append(f'[{prev_label}][{i}:v]xfade=transition=fade:duration={fade_duration}:offset={offsets[i-1]}[{out_label}]')
afilters.append(f'[{audio_prev}][{i}:a]acrossfade=d={fade_duration}[{audio_out}]')
prev_label = out_label
audio_prev = audio_out
filter_str = ';'.join(vfilters + afilters)
cmd = ['ffmpeg', '-y'] + inputs + [
'-filter_complex', filter_str,
'-map', '[vout]', '-map', '[aout]',
'-c:v', 'libx264', '-c:a', 'aac',
output_path
]
proc = await asyncio.create_subprocess_exec(
*cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await proc.communicate()
if proc.returncode == 0:
result = json.dumps({"status": "success", "output_path": output_path})
else:
result = json.dumps({"status": "error", "error": stderr.decode()})
except Exception as e:
result = json.dumps({"status": "error", "error": str(e)})
return result