import os import requests import json import time import subprocess import gradio as gr import uuid from dotenv import load_dotenv from urllib.parse import urlparse # Load environment variables load_dotenv() # API Keys B_KEY = os.getenv("B_KEY") # URLs API_URL = os.getenv("API_URL") UPLOAD_URL = os.getenv("UPLOAD_URL") # Make sure to add this to your .env file def lipsync_api_call(video_url, audio_url): headers = { "Content-Type": "application/json", "x-api-key": B_KEY } data = { "audioUrl": audio_url, "videoUrl": video_url, "maxCredits": 1000, "model": "sync-1.7.1-beta", "synergize": True, "pads": [0, 5, 0, 0], "synergizerStrength": 1 } response = requests.post(API_URL, headers=headers, data=json.dumps(data)) return response.json() def check_job_status(job_id): headers = {"x-api-key": B_KEY} max_attempts = 3000 # Limit the number of attempts for _ in range(max_attempts): response = requests.get(f"{API_URL}/{job_id}", headers=headers) data = response.json() if data["status"] == "COMPLETED": return data["videoUrl"] elif data["status"] == "FAILED": return None time.sleep(10) return None def get_media_duration(file_path): cmd = ['ffprobe', '-v', 'error', '-show_entries', 'format=duration', '-of', 'default=noprint_wrappers=1:nokey=1', file_path] result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) return float(result.stdout.strip()) def combine_audio_video(video_path, audio_path, output_path): video_duration = get_media_duration(video_path) audio_duration = get_media_duration(audio_path) if video_duration > audio_duration: cmd = [ 'ffmpeg', '-i', video_path, '-i', audio_path, '-t', str(audio_duration), '-map', '0:v', '-map', '1:a', '-c:v', 'copy', '-c:a', 'aac', '-y', output_path ] else: loop_count = int(audio_duration // video_duration) + 1 cmd = [ 'ffmpeg', '-stream_loop', str(loop_count), '-i', video_path, '-i', audio_path, '-t', str(audio_duration), '-map', '0:v', '-map', '1:a', '-c:v', 'copy', '-c:a', 'aac', '-shortest', '-y', output_path ] subprocess.run(cmd, check=True) def is_image_url(url): parsed = urlparse(url) path = parsed.path.lower() return path.endswith(('.png', '.jpg', '.jpeg', '.gif', '.bmp', '.tiff', '.webp', '.heic', '.svg', '.ico')) def create_video_from_image(image_url, output_path, duration=10): # Download the image response = requests.get(image_url) if response.status_code != 200: raise Exception("Failed to download the image") temp_image_path = f"temp_image_{uuid.uuid4()}.jpg" with open(temp_image_path, 'wb') as f: f.write(response.content) # Create a 10-second video from the image cmd = [ 'ffmpeg', '-loop', '1', '-i', temp_image_path, '-c:v', 'libx264', '-t', str(duration), '-pix_fmt', 'yuv420p', '-vf', 'scale=trunc(iw/2)*2:trunc(ih/2)*2', '-y', output_path ] subprocess.run(cmd, check=True) # Clean up the temporary image file os.remove(temp_image_path) return output_path def upload_file(file_path): with open(file_path, 'rb') as file: files = {'fileToUpload': (os.path.basename(file_path), file)} data = {'reqtype': 'fileupload'} response = requests.post(UPLOAD_URL, files=files, data=data) if response.status_code == 200: return response.text.strip() return None def process_video(video_url, audio_url, progress=gr.Progress()): if not audio_url: return None, "No audio URL provided" if not video_url: return None, "No video URL provided" session_id = str(uuid.uuid4()) progress(0.2, desc="Processing media...") try: if is_image_url(video_url): progress(0.3, desc="Converting image to video...") video_path = f"temp_video_{session_id}.mp4" create_video_from_image(video_url, video_path) progress(0.4, desc="Uploading converted video...") video_url = upload_file(video_path) if not video_url: raise Exception("Failed to upload converted video") os.remove(video_path) progress(0.5, desc="Initiating lipsync...") job_data = lipsync_api_call(video_url, audio_url) if "error" in job_data or "message" in job_data: raise Exception(job_data.get("error", job_data.get("message", "Unknown error"))) job_id = job_data["id"] progress(0.6, desc="Processing lipsync...") result_url = check_job_status(job_id) if result_url: progress(0.9, desc="Downloading result...") response = requests.get(result_url) output_path = f"output_{session_id}.mp4" with open(output_path, "wb") as f: f.write(response.content) progress(1.0, desc="Complete!") return output_path, "Lipsync completed successfully!" else: raise Exception("Lipsync processing failed or timed out") except Exception as e: progress(0.8, desc="Falling back to simple combination...") try: video_response = requests.get(video_url) temp_video_path = f"temp_video_{session_id}.mp4" with open(temp_video_path, "wb") as f: f.write(video_response.content) audio_response = requests.get(audio_url) temp_audio_path = f"temp_audio_{session_id}.mp3" with open(temp_audio_path, "wb") as f: f.write(audio_response.content) output_path = f"output_{session_id}.mp4" combine_audio_video(temp_video_path, temp_audio_path, output_path) os.remove(temp_video_path) os.remove(temp_audio_path) progress(1.0, desc="Complete!") return output_path, f"Used fallback method. Original error: {str(e)}" except Exception as fallback_error: return None, f"All methods failed. Error: {str(fallback_error)}" def create_interface(): css = """ #component-0 > :not(.prose) {display: none !important;} footer {display: none !important;} """ with gr.Blocks(css=css) as app: gr.Markdown("# JSON 2") with gr.Row(): with gr.Column(): video_url_input = gr.Textbox(label="Video or Image URL") audio_url_input = gr.Textbox(label="Audio URL") generate_btn = gr.Button("Generate Video") with gr.Column(): video_output = gr.Video(label="Generated Video") status_output = gr.Textbox(label="Status", interactive=False) generate_btn.click( fn=process_video, inputs=[video_url_input, audio_url_input], outputs=[video_output, status_output] ) return app if __name__ == "__main__": app = create_interface() app.launch()