Spaces:
Sleeping
Sleeping
File size: 9,479 Bytes
c157612 b12b399 c157612 b12b399 c157612 c83ff8b b12b399 32860f3 b12b399 32860f3 b12b399 32860f3 b12b399 32860f3 b12b399 32860f3 b12b399 32860f3 b12b399 21e5026 b12b399 efe809b b12b399 2daed77 b12b399 32860f3 6daf130 c83ff8b b12b399 c83ff8b b12b399 c83ff8b b12b399 c157612 b12b399 c157612 eaad797 |
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 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
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": <The precise start time in total seconds from the SRT>,
"final_clip_end_seconds": <The precise end time in total seconds from the SRT>
}}
"""
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": <The precise start time in total seconds from the SRT>,
"final_clip_end_seconds": <The precise end time in total seconds from the SRT>
}}
"""
# --- 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)
|