Spaces:
Sleeping
Sleeping
File size: 4,754 Bytes
45b6e45 f64a0f3 45b6e45 09894b2 45b6e45 09894b2 45b6e45 15a5a43 45b6e45 347bbc1 45b6e45 09894b2 3d2f8d8 09894b2 3d2f8d8 15a5a43 09894b2 15a5a43 09894b2 3d2f8d8 15a5a43 8010e39 09894b2 8010e39 09894b2 8010e39 15a5a43 09894b2 15a5a43 45b6e45 09894b2 45b6e45 09894b2 45b6e45 3d2f8d8 09894b2 3d2f8d8 09894b2 45b6e45 09894b2 45b6e45 3d2f8d8 09894b2 45b6e45 09894b2 15a5a43 09894b2 15a5a43 09894b2 45b6e45 09894b2 45b6e45 15a5a43 45b6e45 3d2f8d8 09894b2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | import os
import uuid
import shutil
import subprocess
import gradio as gr
TEMP_DIR = os.path.abspath("temp")
OUTPUT_DIR = os.path.abspath("outputs")
os.makedirs(TEMP_DIR, exist_ok=True)
os.makedirs(OUTPUT_DIR, exist_ok=True)
def get_video_duration(video_path):
cmd = [
"ffprobe", "-v", "error",
"-show_entries", "format=duration",
"-of", "default=noprint_wrappers=1:nokey=1",
video_path
]
result = subprocess.check_output(cmd).decode().strip()
return float(result)
def create_loop(input_video, loop_method, fade_duration, transition, fps, crf):
if input_video is None:
raise gr.Error("Please upload a video.")
uid = str(uuid.uuid4())[:8]
input_path = os.path.join(TEMP_DIR, f"{uid}_input.mp4")
output_path = os.path.join(OUTPUT_DIR, f"{uid}_loop.mp4")
if isinstance(input_video, dict):
video_file = input_video["path"]
else:
video_file = input_video
shutil.copy(video_file, input_path)
duration = get_video_duration(input_path)
# Filter Anti-Aliasing & Standarisasi
sws_flags = "flags=lanczos+accurate_rnd+full_chroma_int"
# Filter dasar tanpa spasi/newline yang merusak sintaks FFmpeg
base = f"format=yuv420p,fps={fps}:round=near,scale=trunc(iw/2)*2:trunc(ih/2)*2:{sws_flags}"
if loop_method == "Crossfade (Standard)":
if duration <= fade_duration:
fade_duration = duration / 2
offset = duration - fade_duration
filter_complex = f"[0:v]{base},split[v1][v2];[v1][v2]xfade=transition={transition}:duration={fade_duration}:offset={offset},format=yuv420p[v]"
target_duration = str(duration - fade_duration)
elif loop_method == "Split & Swap (Premiere Style)":
midpoint = duration / 2.0
# Beri margin 0.2 detik agar tidak error exit status 234
if fade_duration >= midpoint:
fade_duration = midpoint - 0.2
offset = midpoint - fade_duration
filter_complex = (
f"[0:v]{base},split[v_src1][v_src2];"
f"[v_src1]trim=start=0:end={midpoint},setpts=PTS-STARTPTS[partA];"
f"[v_src2]trim=start={midpoint}:end={duration},setpts=PTS-STARTPTS[partB];"
f"[partB][partA]xfade=transition={transition}:duration={fade_duration}:offset={offset},format=yuv420p[v]"
)
target_duration = str(duration - fade_duration)
else:
# PING-PONG
filter_complex = f"[0:v]{base},split[fwd][rev_src];[rev_src]reverse[rev];[fwd][rev]concat=n=2:v=1:a=0,format=yuv420p[v]"
target_duration = str(duration * 2)
cmd = [
"ffmpeg", "-y", "-i", input_path,
"-filter_complex", filter_complex,
"-map", "[v]", "-t", target_duration, "-an",
"-c:v", "libx264", "-preset", "slower", "-crf", str(crf),
"-pix_fmt", "yuv420p", "-movflags", "+faststart",
output_path
]
try:
subprocess.run(cmd, check=True, capture_output=True)
except subprocess.CalledProcessError as e:
error_msg = e.stderr.decode()
raise gr.Error(f"FFmpeg Error: {error_msg}")
preview_html = f"""
<div style="border-radius:12px; overflow:hidden; background-color: #000;">
<video autoplay loop muted controls playsinline width="100%">
<source src="/file={output_path}" type="video/mp4">
</video>
</div>
"""
return output_path, preview_html
with gr.Blocks(theme=gr.themes.Soft()) as demo:
gr.Markdown("# 🎬 Seamless Loop Maker Pro")
with gr.Row():
with gr.Column():
input_video = gr.Video(label="Upload Video")
loop_method = gr.Radio(
choices=["Split & Swap (Premiere Style)", "Ping-Pong (No Ghosting)", "Crossfade (Standard)"],
value="Split & Swap (Premiere Style)", label="Loop Method"
)
fade_duration = gr.Slider(minimum=0.1, maximum=5.0, value=1.0, step=0.1, label="Fade Duration (sec)")
fps = gr.Dropdown(choices=[24, 30, 60], value=24, label="FPS")
transition = gr.Dropdown(choices=["dissolve", "fade", "smoothleft", "fadeblack"], value="dissolve", label="Transition")
crf = gr.Slider(minimum=12, maximum=28, value=18, step=1, label="Quality (CRF)")
generate_btn = gr.Button("🚀 Generate Loop", variant="primary")
with gr.Column():
preview = gr.HTML()
output_video = gr.Video(label="Download MP4")
generate_btn.click(
fn=create_loop,
inputs=[input_video, loop_method, fade_duration, transition, fps, crf],
outputs=[output_video, preview]
)
if __name__ == "__main__":
demo.launch(server_name="0.0.0.0", server_port=7860, allowed_paths=[OUTPUT_DIR]) |