import gradio as gr import subprocess import json import os import requests import urllib.parse import time from huggingface_hub import HfApi, InferenceClient HF_TOKEN = os.getenv("HF_WRITE_TOKEN") api = HfApi(token=HF_TOKEN) DATASET_ID = "oviet711/render-factory" def generate_ai_image(keyword, chunk_id): print(f"🎨 [TIER 1] Meminta Visual dari Pollinations: {keyword}") encoded_prompt = urllib.parse.quote(f"cinematic photography, ultra realistic, highly detailed, {keyword}, dark moody lighting, vertical") url = f"https://image.pollinations.ai/prompt/{encoded_prompt}?width=1080&height=1920&nologo=true" # TIER 1: Coba Pollinations AI for attempt in range(2): try: response = requests.get(url, stream=True, timeout=10) if response.status_code == 200: bg_filename = f"bg_{chunk_id}.png" with open(bg_filename, 'wb') as f: for chunk in response.iter_content(1024): f.write(chunk) return bg_filename except Exception as e: print(f"⚠️ Pollinations batuk (Percobaan {attempt+1}): {e}") time.sleep(1) # TIER 2: Jika Pollinations Mati, otomatis pakai Hugging Face FLUX print("🔄 [TIER 2] Beralih ke Hugging Face FLUX API...") try: client = InferenceClient(model="black-forest-labs/FLUX.1-schnell", token=HF_TOKEN) prompt = f"cinematic shot, photorealistic, vertical 9:16, {keyword}, dark and moody lighting, masterpiece" image = client.text_to_image(prompt) bg_filename = f"bg_{chunk_id}.png" image.save(bg_filename) return bg_filename except Exception as e: print(f"⚠️ TIER 2 Gagal (Server HF Penuh): {e}") # TIER 3: Jika semua gagal, return None (FFmpeg akan buat layar hitam) return None # HAPUS BARIS INI: import spaces (di bagian atas file) # HAPUS BARIS INI: @spaces.GPU def render_worker(json_data): try: data = json.loads(json_data) chunk_id = data.get('chunk_id', '000') text = data.get('text', 'Teks kosong') keyword = data.get('keyword', 'fresh juice') # ... (Sisa kode di bawahnya tetap sama persis, jangan diubah) ... # LOGIKA DINAMIS: Kamera & Warna berganti berdasarkan ID potongan cid = int(chunk_id) # 1. Rotasi Efek Kamera (Zoompan) camera_effects = [ "zoompan=z='min(zoom+0.0015,1.15)':d=500:s=1080x1920", # Maju perlahan ke tengah "zoompan=z='min(zoom+0.0015,1.15)':y='0':d=500:s=1080x1920", # Maju perlahan ke atas "zoompan=z='min(zoom+0.0015,1.15)':y='ih':d=500:s=1080x1920" # Maju perlahan ke bawah ] selected_camera = camera_effects[cid % len(camera_effects)] # 2. Rotasi Warna Subtitle (Format BGR: Kuning, Cyan, Putih) text_colors = ["&H00FFFF", "&HFFFF00", "&HFFFFFF"] selected_color = text_colors[cid % len(text_colors)] # Audio audio_file = f"audio_{chunk_id}.mp3" vtt_file = f"sub_{chunk_id}.vtt" subprocess.run([ "edge-tts", "--voice", "id-ID-GadisNeural", "--rate", "+15%", "--text", text, "--write-media", audio_file, "--write-subtitles", vtt_file ], check=True) # Visual bg_image = generate_ai_image(keyword, chunk_id) output_filename = f"chunk_{chunk_id}.mp4" if bg_image: cmd = [ "ffmpeg", "-y", "-loop", "1", "-framerate", "30", "-i", bg_image, "-i", audio_file, # Memasukkan Kamera Dinamis dan Warna Dinamis! "-vf", f"scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,setsar=1:1,{selected_camera},subtitles={vtt_file}:force_style='FontSize=20,PrimaryColour={selected_color},OutlineColour=&H000000,BorderStyle=1,Outline=2,Shadow=1,Alignment=2,MarginV=450'", "-c:v", "libx264", "-c:a", "aac", "-shortest", "-pix_fmt", "yuv420p", output_filename ] else: cmd = [ "ffmpeg", "-y", "-f", "lavfi", "-i", "color=c=black:s=1080x1920:d=30", "-i", audio_file, "-vf", f"subtitles={vtt_file}:force_style='FontSize=20,PrimaryColour={selected_color},OutlineColour=&H000000,BorderStyle=1,Outline=2,Shadow=1,Alignment=2,MarginV=450'", "-c:v", "libx264", "-c:a", "aac", "-shortest", output_filename ] subprocess.run(cmd, check=True) api.upload_file( path_or_fileobj=output_filename, path_in_repo=f"chunks/{output_filename}", repo_id=DATASET_ID, repo_type="dataset" ) return f"✅ SUKSES Chunk {chunk_id} (Efek Kamera: {cid%3})" except Exception as e: return f"❌ ERROR: {str(e)}" demo = gr.Interface(fn=render_worker, inputs="text", outputs="text") demo.launch()