Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -6,16 +6,12 @@ import time
|
|
6 |
from moviepy.video.io.VideoFileClip import VideoFileClip
|
7 |
|
8 |
# --- 1. CONFIGURATION & CONSTANTS ---
|
9 |
-
|
10 |
# Securely load API key from Hugging Face Space secrets.
|
11 |
-
ONE_API_KEY = os.environ.get("ONE_API_KEY", "268976:66f4f58a2a905") # Using your key for now,
|
12 |
-
|
13 |
# The custom endpoint for the one-api.ir service.
|
14 |
ONE_API_URL = "https://api.one-api.ir/chatbot/v1/gpt4o/"
|
15 |
|
16 |
# --- MASTER PROMPTS ---
|
17 |
-
|
18 |
-
# Prompt for Mode 1: "Viral Spot for Shorts (< 3 mins)"
|
19 |
PROMPT_SHORTS_MODE = """
|
20 |
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).
|
21 |
|
@@ -35,7 +31,6 @@ Your output MUST be a single, valid JSON object and nothing else. Do not include
|
|
35 |
}}
|
36 |
"""
|
37 |
|
38 |
-
# Prompt for Mode 2: "Viral Narrative Clip (5-12 mins)"
|
39 |
PROMPT_NARRATIVE_MODE = """
|
40 |
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.
|
41 |
|
@@ -55,10 +50,10 @@ Your output MUST be a single, valid JSON object and nothing else. Do not include
|
|
55 |
}}
|
56 |
"""
|
57 |
|
58 |
-
# --- 2. LLM AGENT WRAPPER ---
|
59 |
|
60 |
def call_gpt4o_oneapi(transcript_content, prompt_template):
|
61 |
-
"""Makes a custom API call to the one-api.ir service."""
|
62 |
if not ONE_API_KEY or not ONE_API_URL:
|
63 |
raise ValueError("ONE_API_KEY and ONE_API_URL secrets are not set correctly.")
|
64 |
|
@@ -74,14 +69,27 @@ def call_gpt4o_oneapi(transcript_content, prompt_template):
|
|
74 |
response.raise_for_status()
|
75 |
result = response.json()
|
76 |
|
77 |
-
|
78 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
79 |
if message_content:
|
80 |
return message_content
|
81 |
else:
|
82 |
-
return f"Error: 'content' key not found in API response
|
|
|
|
|
|
|
83 |
else:
|
84 |
-
|
|
|
85 |
|
86 |
except requests.exceptions.HTTPError as e:
|
87 |
return f"HTTP Error calling API: {e}\nResponse Body: {e.response.text}"
|
@@ -114,12 +122,15 @@ def generate_viral_clip(video_file, srt_file, analysis_mode, progress=gr.Progres
|
|
114 |
return llm_response_str, None
|
115 |
|
116 |
try:
|
117 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
118 |
parsed_response = json.loads(cleaned_response)
|
119 |
|
120 |
-
# --- ROBUSTNESS FIX APPLIED HERE ---
|
121 |
-
# Check if the parsed response is a dictionary. If not, the AI returned a simple string
|
122 |
-
# instead of the requested JSON object. Raise a TypeError to handle it as a parsing failure.
|
123 |
if not isinstance(parsed_response, dict):
|
124 |
raise TypeError(f"AI did not return a valid JSON object. It returned a {type(parsed_response).__name__}.")
|
125 |
|
@@ -134,7 +145,6 @@ def generate_viral_clip(video_file, srt_file, analysis_mode, progress=gr.Progres
|
|
134 |
f"Clipping video from {time.strftime('%H:%M:%S', time.gmtime(start_time))} to {time.strftime('%H:%M:%S', time.gmtime(end_time))}.")
|
135 |
|
136 |
except (json.JSONDecodeError, KeyError, TypeError) as e:
|
137 |
-
# The new TypeError will be caught here, providing a clear error message.
|
138 |
error_msg = f"Error: Failed to parse AI response. Details: {e}\n\nRaw AI Response:\n---\n{llm_response_str}"
|
139 |
return error_msg, None
|
140 |
|
@@ -152,10 +162,10 @@ def generate_viral_clip(video_file, srt_file, analysis_mode, progress=gr.Progres
|
|
152 |
return summary, output_filename
|
153 |
|
154 |
except Exception as e:
|
155 |
-
|
|
|
156 |
|
157 |
# --- 4. GRADIO UI DEFINITION ---
|
158 |
-
|
159 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
160 |
gr.Markdown(
|
161 |
"""
|
|
|
6 |
from moviepy.video.io.VideoFileClip import VideoFileClip
|
7 |
|
8 |
# --- 1. CONFIGURATION & CONSTANTS ---
|
|
|
9 |
# Securely load API key from Hugging Face Space secrets.
|
10 |
+
ONE_API_KEY = os.environ.get("ONE_API_KEY", "268976:66f4f58a2a905") # Using your key for now, secrets are better.
|
|
|
11 |
# The custom endpoint for the one-api.ir service.
|
12 |
ONE_API_URL = "https://api.one-api.ir/chatbot/v1/gpt4o/"
|
13 |
|
14 |
# --- MASTER PROMPTS ---
|
|
|
|
|
15 |
PROMPT_SHORTS_MODE = """
|
16 |
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).
|
17 |
|
|
|
31 |
}}
|
32 |
"""
|
33 |
|
|
|
34 |
PROMPT_NARRATIVE_MODE = """
|
35 |
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.
|
36 |
|
|
|
50 |
}}
|
51 |
"""
|
52 |
|
53 |
+
# --- 2. LLM AGENT WRAPPER (WITH DEFINITIVE FIX) ---
|
54 |
|
55 |
def call_gpt4o_oneapi(transcript_content, prompt_template):
|
56 |
+
"""Makes a robust, custom API call to the one-api.ir service."""
|
57 |
if not ONE_API_KEY or not ONE_API_URL:
|
58 |
raise ValueError("ONE_API_KEY and ONE_API_URL secrets are not set correctly.")
|
59 |
|
|
|
69 |
response.raise_for_status()
|
70 |
result = response.json()
|
71 |
|
72 |
+
# Check for the basic expected structure
|
73 |
+
if "result" not in result or not isinstance(result.get("result"), list) or len(result["result"]) == 0:
|
74 |
+
return f"Error: Unexpected JSON structure from API. 'result' list not found or empty.\nFull response: {json.dumps(result)}"
|
75 |
+
|
76 |
+
first_item_in_result = result["result"][0]
|
77 |
+
|
78 |
+
# --- THE CRITICAL FIX IS HERE ---
|
79 |
+
# The API can return a dict OR a string inside the list. We must handle both.
|
80 |
+
if isinstance(first_item_in_result, dict):
|
81 |
+
# This is the expected case: {"content": "..."}
|
82 |
+
message_content = first_item_in_result.get("content")
|
83 |
if message_content:
|
84 |
return message_content
|
85 |
else:
|
86 |
+
return f"Error: 'content' key not found in API response dictionary.\nFull response: {json.dumps(result)}"
|
87 |
+
elif isinstance(first_item_in_result, str):
|
88 |
+
# This is the alternate case: the string is the entire response.
|
89 |
+
return first_item_in_result
|
90 |
else:
|
91 |
+
# Handle any other unexpected type
|
92 |
+
return f"Error: Unknown item type in API 'result' list.\nFull response: {json.dumps(result)}"
|
93 |
|
94 |
except requests.exceptions.HTTPError as e:
|
95 |
return f"HTTP Error calling API: {e}\nResponse Body: {e.response.text}"
|
|
|
122 |
return llm_response_str, None
|
123 |
|
124 |
try:
|
125 |
+
# Clean potential markdown code fences from the AI response
|
126 |
+
cleaned_response = llm_response_str.strip()
|
127 |
+
if cleaned_response.startswith("```json"):
|
128 |
+
cleaned_response = cleaned_response[7:]
|
129 |
+
if cleaned_response.endswith("```"):
|
130 |
+
cleaned_response = cleaned_response[:-3]
|
131 |
+
|
132 |
parsed_response = json.loads(cleaned_response)
|
133 |
|
|
|
|
|
|
|
134 |
if not isinstance(parsed_response, dict):
|
135 |
raise TypeError(f"AI did not return a valid JSON object. It returned a {type(parsed_response).__name__}.")
|
136 |
|
|
|
145 |
f"Clipping video from {time.strftime('%H:%M:%S', time.gmtime(start_time))} to {time.strftime('%H:%M:%S', time.gmtime(end_time))}.")
|
146 |
|
147 |
except (json.JSONDecodeError, KeyError, TypeError) as e:
|
|
|
148 |
error_msg = f"Error: Failed to parse AI response. Details: {e}\n\nRaw AI Response:\n---\n{llm_response_str}"
|
149 |
return error_msg, None
|
150 |
|
|
|
162 |
return summary, output_filename
|
163 |
|
164 |
except Exception as e:
|
165 |
+
# This generic catch-all is our final safety net.
|
166 |
+
return f"An unexpected error occurred in the main process: {str(e)}", None
|
167 |
|
168 |
# --- 4. GRADIO UI DEFINITION ---
|
|
|
169 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
170 |
gr.Markdown(
|
171 |
"""
|