import gradio as gr import requests import os import json import time # CORRECTED IMPORT FOR RESTRICTED ENVIRONMENTS: # We bypass the 'editor' module and import the class directly. # This works now because requirements.txt ensures a full installation. from moviepy.video.io.VideoFileClip import VideoFileClip import traceback # Import traceback for detailed error logging # --- 1. CONFIGURATION & CONSTANTS --- # Securely load API key from Hugging Face Space secrets. ONE_API_KEY = os.environ.get("ONE_API_KEY", "268976:66f4f58a2a905") # The custom endpoint for the one-api.ir service. ONE_API_URL = "https://api.one-api.ir/chatbot/v1/gpt4o/" # --- MASTER PROMPTS --- PROMPT_SHORTS_MODE = """ You are an expert producer of viral short-form content. Analyze the provided SRT transcript to find the single most impactful, hook-worthy segment for a video under 3 minutes (ideally 60-90 seconds). PRIORITIES: Strong Hook, Single Clear Point, High Energy, Clear Payoff. **Input SRT Content:** {transcript_content} **Instructions:** Your output MUST be a single, valid JSON object and nothing else. Do not include any text, code blocks, or explanations before or after the JSON object. {{ "clip_title_suggestion": "A catchy, clickbait-style title for this short clip.", "reasoning": "Briefly explain why this segment is perfect for a short video, referencing the hook and payoff.", "final_clip_start_seconds": , "final_clip_end_seconds": }} """ PROMPT_NARRATIVE_MODE = """ You are an expert video editor and storyteller. Analyze the provided transcript to find the most compelling narrative segment between 5 and 12 minutes long. METHODOLOGY: Identify the peak moment, then find the corresponding setup and resolution to create a complete narrative arc. **Input SRT Content:** {transcript_content} **Instructions:** Your output MUST be a single, valid JSON object and nothing else. Do not include any text, code blocks, or explanations before or after the JSON object. {{ "narrative_summary": "A one-sentence summary of the story told in the extracted clip.", "reasoning": "Explain why this segment works as a standalone narrative, mentioning the peak moment and how the start/end points provide a full arc.", "final_clip_start_seconds": , "final_clip_end_seconds": }} """ # --- 2. LLM AGENT WRAPPER --- def call_gpt4o_oneapi(transcript_content, prompt_template): """Makes a robust, custom API call to the one-api.ir service.""" if not ONE_API_KEY or not ONE_API_URL: raise ValueError("ONE_API_KEY and ONE_API_URL secrets are not set correctly.") headers = { "one-api-token": ONE_API_KEY, "Content-Type": "application/json" } final_prompt = prompt_template.format(transcript_content=transcript_content) payload = [{"role": "user", "content": final_prompt}] try: response = requests.post(ONE_API_URL, headers=headers, json=payload, timeout=180) response.raise_for_status() result = response.json() if "result" not in result or not isinstance(result.get("result"), list) or len(result["result"]) == 0: return f"Error: Unexpected JSON structure from API. 'result' list not found or empty.\nFull response: {json.dumps(result)}" first_item_in_result = result["result"][0] if isinstance(first_item_in_result, dict): message_content = first_item_in_result.get("content") if message_content: return message_content else: return f"Error: 'content' key not found in API response dictionary.\nFull response: {json.dumps(result)}" elif isinstance(first_item_in_result, str): return first_item_in_result else: return f"Error: Unknown item type in API 'result' list.\nFull response: {json.dumps(result)}" except requests.exceptions.HTTPError as e: return f"HTTP Error calling API: {e}\nResponse Body: {e.response.text}" except requests.exceptions.RequestException as e: return f"Error connecting to API: {str(e)}" except json.JSONDecodeError: return f"Error: Failed to decode JSON from API response.\nResponse Body: {response.text}" # --- 3. CORE ORCHESTRATOR FUNCTION --- def generate_viral_clip(video_file, srt_file, analysis_mode, progress=gr.Progress()): if not video_file or not srt_file: return "Error: Please upload both a video file and an SRT file.", None if not ONE_API_KEY or not ONE_API_URL: return "Error: API keys for OneAPI are not configured correctly in the Space secrets.", None video = None new_clip = None try: progress(0.1, desc="Reading SRT file...") with open(srt_file, 'r', encoding='utf-8') as f: transcript_content = f.read() progress(0.2, desc="Preparing analysis prompt...") prompt_template = PROMPT_SHORTS_MODE if analysis_mode == "Viral Spot for Shorts (< 3 mins)" else PROMPT_NARRATIVE_MODE progress(0.4, desc="Calling AI for analysis...") llm_response_str = call_gpt4o_oneapi(transcript_content, prompt_template) progress(0.7, desc="Parsing AI response...") if llm_response_str.startswith("Error") or llm_response_str.startswith("HTTP Error"): return llm_response_str, None try: cleaned_response = llm_response_str.strip() if cleaned_response.startswith("```json"): cleaned_response = cleaned_response[7:] if cleaned_response.endswith("```"): cleaned_response = cleaned_response[:-3] parsed_response = json.loads(cleaned_response) if not isinstance(parsed_response, dict): raise TypeError(f"AI did not return a valid JSON object. It returned a {type(parsed_response).__name__}.") start_time = float(parsed_response['final_clip_start_seconds']) end_time = float(parsed_response['final_clip_end_seconds']) reasoning = parsed_response.get('reasoning', 'No reasoning provided.') summary = (f"āœ… Analysis Complete!\n\n" f"Reasoning: {reasoning}\n\n" f"Title Suggestion: {parsed_response.get('clip_title_suggestion', 'N/A')}\n" f"Narrative Summary: {parsed_response.get('narrative_summary', 'N/A')}\n\n" f"Clipping video from {time.strftime('%H:%M:%S', time.gmtime(start_time))} to {time.strftime('%H:%M:%S', time.gmtime(end_time))}.") except (json.JSONDecodeError, KeyError, TypeError) as e: error_msg = f"Error: Failed to parse AI response. Details: {e}\n\nRaw AI Response:\n---\n{llm_response_str}" return error_msg, None progress(0.8, desc="Clipping video...") output_filename = "viral_clip.mp4" video = VideoFileClip(video_file) if end_time > video.duration: end_time = video.duration summary += f"\n\nāš ļø Warning: End time was beyond video duration, adjusted to {end_time:.2f}s." new_clip = video.subclipped(start_time, end_time) new_clip.write_videofile(output_filename, codec="libx264", audio_codec="aac") progress(1.0, desc="Done!") return summary, output_filename except Exception as e: tb_str = traceback.format_exc() return f"An unexpected error occurred in the main process: {str(e)}\n\nTraceback:\n{tb_str}", None finally: # Gracefully close the video clips to release file handles if new_clip: try: new_clip.close() except Exception: pass # Ignore errors on close if video: try: video.close() except Exception: pass # Ignore errors on close # --- 4. GRADIO UI DEFINITION --- with gr.Blocks(theme=gr.themes.Soft()) as demo: gr.Markdown( """ # šŸŽ¬ AI Viral Video Extractor This tool uses an AI agent to analyze a video transcript and automatically clip the most viral segment. **āš ļø Important Setup:** For best security, configure `ONE_API_KEY` in your Hugging Face **Space Settings > Secrets**. """ ) with gr.Row(): with gr.Column(scale=1): video_input = gr.Video(label="1. Upload Original Video") srt_input = gr.File(label="2. Upload English SRT File", file_types=['.srt']) mode_input = gr.Radio( label="3. Select Analysis Mode", choices=["Viral Spot for Shorts (< 3 mins)", "Viral Narrative Clip (5-12 mins)"], value="Viral Narrative Clip (5-12 mins)" ) submit_button = gr.Button("šŸš€ Generate Viral Clip", variant="primary") with gr.Column(scale=2): summary_output = gr.Textbox(label="Analysis Summary", lines=12, interactive=False) video_output = gr.Video(label="Generated Clip", interactive=False) submit_button.click( fn=generate_viral_clip, inputs=[video_input, srt_input, mode_input], outputs=[summary_output, video_output], ) if __name__ == "__main__": demo.launch(debug=True)