import gradio as gr import os, json, subprocess, textwrap, uuid import spaces @spaces.GPU(duration=30) def render_video(video_file, srt_file, thumb_file, hermes_json, allowed_crop): try: uid = str(uuid.uuid4()) wrk = f"/tmp/{uid}" os.makedirs(wrk, exist_ok=True) out_path = f"{wrk}/final_short.mp4" data = json.loads(hermes_json) clips = data.get("clips", []) if not clips: return None def pt(ts): h,m,s = ts.replace(',','.').split(':') return float(h)*3600 + float(m)*60 + float(s) def ft(sec): h, rem = divmod(sec, 3600) m, s = divmod(rem, 60) return f"{int(h):02d}:{int(m):02d}:{int(s):02d},{int((s-int(s))*1000):03d}" raw_srt = open(srt_file).read().strip().split("\n\n") new_srt, srt_idx, run_t, offset = [], 1, 0.0, 1.5 filt, conc = [], [] for i, c in enumerate(clips): s, e = pt(c["start"]), pt(c["end"]) dur = e - s filt.append(f"[0:v]trim=start={s}:end={e},setpts=PTS-STARTPTS,fps=30[v{i}]; ") filt.append(f"[0:a]atrim=start={s}:end={e},asetpts=PTS-STARTPTS,aformat=sample_rates=48000:channel_layouts=stereo[a{i}]; ") conc.append(f"[v{i}][a{i}]") for blk in raw_srt: if not blk.strip(): continue lines = blk.strip().split("\n") if len(lines) >= 3: ts, te = pt(lines[1].split(" --> ")[0]), pt(lines[1].split(" --> ")[1]) if te > s and ts < e: ns, ne = max(ts, s), min(te, e) ns_adj, ne_adj = (ns - s) + run_t + offset, (ne - s) + run_t + offset text = " ".join(lines[2:]) words = text.split() dur_sub = ne - ns if dur_sub > 0 and (len(words) / dur_sub) > 6.0: continue max_w = 4 chunks = [" ".join(words[j:j+max_w]) for j in range(0, len(words), max_w)] if not chunks: continue c_dur = (ne_adj - ns_adj) / len(chunks) for k, c_txt in enumerate(chunks): c_st, c_en = ns_adj + k * c_dur, ns_adj + (k+1) * c_dur wrp = textwrap.fill(c_txt, width=22) new_srt.append(f"{srt_idx}\n{ft(c_st)} --> {ft(c_en)}\n{wrp}") srt_idx += 1 run_t += dur filt_str = "".join(filt) + "".join(conc) + f"concat=n={len(clips)}:v=1:a=1[raw_vid][raw_aud];\n" clip_srt_path = f"{wrk}/clip.srt" open(clip_srt_path, "w").write("\n\n".join(new_srt)) crop_filter = "crop=iw-200:ih:100:0," if allowed_crop == "true" else "" # PERUBAHAN HANYA DI SINI: Alignment=10 (Center-Top) & MarginV=260 (Turun pas di bawah video utama) full_filter = filt_str + f"""[raw_vid]split[raw_bg][raw_fg]; [raw_bg]{crop_filter}scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,boxblur=20:5,setsar=1,fps=30[bg]; [raw_fg]{crop_filter}scale=1080:1920:force_original_aspect_ratio=decrease,crop=1080:1920,setsar=1,fps=30[fg]; [bg][fg]overlay=(W-w)/2:(H-h)/2[mainvid]; [1:v]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,setsar=1,fps=30[thumbv]; [thumbv][mainvid]concat=n=2:v=1:a=0,subtitles={clip_srt_path}:force_style='Fontsize=15,Bold=1,PrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,BorderStyle=1,Outline=2.5,Shadow=1,Alignment=10,MarginV=260'[withsubs]; [2:a][raw_aud]concat=n=2:v=0:a=1[outa]""" ff_txt = f"{wrk}/full_filter.txt" open(ff_txt, "w").write(full_filter) subprocess.run([ "ffmpeg", "-y", "-i", video_file, "-loop", "1", "-t", "1.5", "-i", thumb_file, "-f", "lavfi", "-t", "1.5", "-i", "anullsrc=r=48000:cl=stereo", "-filter_complex_script", ff_txt, "-map", "[withsubs]", "-map", "[outa]", "-c:v", "libx264", "-preset", "veryfast", "-crf", "26", "-maxrate", "4M", "-bufsize", "8M", "-pix_fmt", "yuv420p", "-c:a", "aac", "-b:a", "128k", out_path ], check=True) return out_path except Exception as e: print(f"Error: {str(e)}") return None # Antarmuka resmi Gradio demo = gr.Interface( fn=render_video, inputs=[ gr.File(label="Video MP4", type="filepath"), gr.File(label="Subtitle SRT", type="filepath"), gr.File(label="Thumbnail JPG", type="filepath"), gr.Textbox(label="Hermes JSON"), gr.Textbox(label="Allowed Crop") ], outputs=gr.File(label="Output Video MP4") ) demo.launch()