Spaces:
Sleeping
Sleeping
Update core/analyze.py
Browse files- core/analyze.py +94 -56
core/analyze.py
CHANGED
|
@@ -13,91 +13,121 @@ logger = logging.getLogger(__name__)
|
|
| 13 |
|
| 14 |
# Configure Groq Client
|
| 15 |
api_key = os.getenv("GROQ_API_KEY")
|
| 16 |
-
MODEL_NAME = os.getenv("GROQ_MODEL", "
|
| 17 |
|
| 18 |
client = Groq(api_key=api_key)
|
| 19 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
def analyze_transcript(transcript):
|
| 22 |
-
"""Analyze transcript using Groq API."""
|
| 23 |
|
| 24 |
prompt = f"""
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
{{
|
| 43 |
-
"
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
"description": "<string, brief summary of the clip content 10 words max>",
|
| 49 |
-
"viral_score": <float, score from 0-10 indicating viral potential>,
|
| 50 |
-
"reason": "<string, explanation of why this segment is engaging>"
|
| 51 |
-
}}
|
| 52 |
-
]
|
| 53 |
}}
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
|
|
|
| 64 |
|
| 65 |
max_retries = 3
|
| 66 |
base_delay = 5
|
| 67 |
-
content = None
|
| 68 |
|
| 69 |
for attempt in range(max_retries):
|
| 70 |
try:
|
| 71 |
response = client.chat.completions.create(
|
| 72 |
model=MODEL_NAME,
|
| 73 |
messages=[
|
| 74 |
-
{
|
|
|
|
|
|
|
|
|
|
| 75 |
{"role": "user", "content": prompt}
|
| 76 |
],
|
| 77 |
-
temperature=0.
|
| 78 |
)
|
| 79 |
|
| 80 |
-
content = response.choices[0].message.content
|
| 81 |
-
print(f"🤖 AI Raw Response (First
|
| 82 |
|
| 83 |
-
#
|
| 84 |
if "```json" in content:
|
| 85 |
content = content.split("```json")[1].split("```")[0].strip()
|
| 86 |
elif "```" in content:
|
| 87 |
content = content.split("```")[1].split("```")[0].strip()
|
| 88 |
|
| 89 |
-
#
|
| 90 |
data = json.loads(content)
|
| 91 |
-
|
| 92 |
-
|
|
|
|
| 93 |
|
|
|
|
|
|
|
| 94 |
return {"content": content}
|
| 95 |
|
| 96 |
except Exception as e:
|
| 97 |
-
print(f"❌ Error in Groq analysis: {e}")
|
| 98 |
if attempt < max_retries - 1:
|
| 99 |
wait_time = base_delay * (2 ** attempt)
|
| 100 |
-
print(f"⚠️ Retrying
|
| 101 |
time.sleep(wait_time)
|
| 102 |
else:
|
| 103 |
break
|
|
@@ -108,8 +138,7 @@ def analyze_transcript(transcript):
|
|
| 108 |
|
| 109 |
def smart_chunk_transcript(transcript, max_tokens=4000):
|
| 110 |
"""
|
| 111 |
-
Split transcript into coherent chunks at sentence boundaries
|
| 112 |
-
while preserving context and meaning.
|
| 113 |
"""
|
| 114 |
sentences = transcript.replace('\n', ' ').split('. ')
|
| 115 |
chunks = []
|
|
@@ -158,7 +187,7 @@ def analyze_transcript_with_chunking(transcript):
|
|
| 158 |
continue
|
| 159 |
|
| 160 |
if all_segments:
|
| 161 |
-
|
| 162 |
unique_segments = []
|
| 163 |
seen_times = set()
|
| 164 |
|
|
@@ -183,6 +212,10 @@ if __name__ == "__main__":
|
|
| 183 |
[45.0 - 60.0] This has saved me hours every week. I batch my emails, phone calls, and even errands.
|
| 184 |
[60.0 - 90.0] The third hack is the Pomodoro Technique. Work for 25 minutes, then take a 5-minute break.
|
| 185 |
[90.0 - 120.0] This technique helps me stay focused and avoid burnout. I get more done in less time.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 186 |
"""
|
| 187 |
|
| 188 |
logger.info("🧪 Testing AI Analysis...")
|
|
@@ -191,11 +224,16 @@ if __name__ == "__main__":
|
|
| 191 |
try:
|
| 192 |
data = json.loads(result['content'])
|
| 193 |
segments = data.get('segments', [])
|
| 194 |
-
logger.info(f"✅ Found {len(segments)}
|
| 195 |
|
| 196 |
for i, seg in enumerate(segments):
|
| 197 |
-
|
| 198 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
except Exception as e:
|
| 200 |
logger.error(f"❌ Error parsing result: {e}")
|
| 201 |
logger.info(f"Raw result: {result}")
|
|
|
|
| 13 |
|
| 14 |
# Configure Groq Client
|
| 15 |
api_key = os.getenv("GROQ_API_KEY")
|
| 16 |
+
MODEL_NAME = os.getenv("GROQ_MODEL", "openai/gpt-oss-120b")
|
| 17 |
|
| 18 |
client = Groq(api_key=api_key)
|
| 19 |
|
| 20 |
+
MIN_DURATION = 60 # seconds
|
| 21 |
+
MAX_DURATION = 180 # seconds
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def validate_segments(segments):
|
| 25 |
+
"""
|
| 26 |
+
Remove segments that don't meet duration requirements (60-180 seconds).
|
| 27 |
+
Recalculates duration from start/end times — never trusts AI's duration field.
|
| 28 |
+
"""
|
| 29 |
+
valid = []
|
| 30 |
+
for seg in segments:
|
| 31 |
+
start = seg.get("start_time", 0)
|
| 32 |
+
end = seg.get("end_time", 0)
|
| 33 |
+
duration = end - start
|
| 34 |
+
|
| 35 |
+
if duration < MIN_DURATION:
|
| 36 |
+
print(f"⚠️ Skipped short segment: {duration:.1f}s → [{start}s–{end}s] ({seg.get('description', '')})")
|
| 37 |
+
continue
|
| 38 |
+
|
| 39 |
+
if duration > MAX_DURATION:
|
| 40 |
+
print(f"⚠️ Skipped long segment: {duration:.1f}s → [{start}s–{end}s] ({seg.get('description', '')})")
|
| 41 |
+
continue
|
| 42 |
+
|
| 43 |
+
seg["duration"] = round(duration, 2)
|
| 44 |
+
valid.append(seg)
|
| 45 |
+
|
| 46 |
+
print(f"✅ Valid segments after duration filter: {len(valid)}/{len(segments)}")
|
| 47 |
+
return valid
|
| 48 |
+
|
| 49 |
|
| 50 |
def analyze_transcript(transcript):
|
| 51 |
+
"""Analyze transcript using Groq API — minimal output, maximum quality."""
|
| 52 |
|
| 53 |
prompt = f"""
|
| 54 |
+
You are an expert viral video editor. Analyze this transcript and find the best self-contained segments for TikTok/Reels/YouTube Shorts.
|
| 55 |
+
|
| 56 |
+
SEGMENT RULES (STRICT):
|
| 57 |
+
- Duration MUST be 60–180 seconds (end_time - start_time >= 60)
|
| 58 |
+
- Must be a COMPLETE thought: natural start, full story/point, satisfying end
|
| 59 |
+
- Must make sense WITHOUT watching anything before or after it
|
| 60 |
+
- NO mid-sentence cuts, NO abrupt endings
|
| 61 |
+
|
| 62 |
+
QUALITY CRITERIA (pick segments that have ALL of these):
|
| 63 |
+
- Strong hook in the first few seconds that grabs attention
|
| 64 |
+
- One clear emotional core: funny / surprising / inspiring / controversial
|
| 65 |
+
- Builds naturally to a satisfying conclusion
|
| 66 |
+
- Highly shareable standalone moment
|
| 67 |
+
|
| 68 |
+
OUTPUT — return ONLY this JSON, nothing else:
|
| 69 |
+
{{
|
| 70 |
+
"segments": [
|
| 71 |
{{
|
| 72 |
+
"start_time": <float>,
|
| 73 |
+
"end_time": <float>,
|
| 74 |
+
"title": "<YouTube-ready title, punchy, max 60 chars>",
|
| 75 |
+
"description": "<YouTube description, 1-2 sentences, include relevant keywords>",
|
| 76 |
+
"reason": "<why this specific segment is complete and viral-worthy, max 20 words>"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
}}
|
| 78 |
+
]
|
| 79 |
+
}}
|
| 80 |
+
|
| 81 |
+
RULES:
|
| 82 |
+
- end_time - start_time MUST be >= 60, skip any segment shorter than that
|
| 83 |
+
- Return empty segments array if nothing qualifies: {{"segments": []}}
|
| 84 |
+
- Raw JSON only, no markdown, no explanation
|
| 85 |
+
|
| 86 |
+
TRANSCRIPT:
|
| 87 |
+
{transcript}
|
| 88 |
+
"""
|
| 89 |
|
| 90 |
max_retries = 3
|
| 91 |
base_delay = 5
|
|
|
|
| 92 |
|
| 93 |
for attempt in range(max_retries):
|
| 94 |
try:
|
| 95 |
response = client.chat.completions.create(
|
| 96 |
model=MODEL_NAME,
|
| 97 |
messages=[
|
| 98 |
+
{
|
| 99 |
+
"role": "system",
|
| 100 |
+
"content": "You are a JSON-only assistant. Output raw JSON with no markdown, no explanation, no code blocks."
|
| 101 |
+
},
|
| 102 |
{"role": "user", "content": prompt}
|
| 103 |
],
|
| 104 |
+
temperature=0.3,
|
| 105 |
)
|
| 106 |
|
| 107 |
+
content = response.choices[0].message.content.strip()
|
| 108 |
+
print(f"🤖 AI Raw Response (First 300 chars): {content[:300]}...")
|
| 109 |
|
| 110 |
+
# Strip markdown if model ignores system prompt
|
| 111 |
if "```json" in content:
|
| 112 |
content = content.split("```json")[1].split("```")[0].strip()
|
| 113 |
elif "```" in content:
|
| 114 |
content = content.split("```")[1].split("```")[0].strip()
|
| 115 |
|
| 116 |
+
# Parse and validate
|
| 117 |
data = json.loads(content)
|
| 118 |
+
raw_segments = data.get("segments", [])
|
| 119 |
+
valid_segments = validate_segments(raw_segments)
|
| 120 |
+
data["segments"] = valid_segments
|
| 121 |
|
| 122 |
+
content = json.dumps(data)
|
| 123 |
+
print(f"🤖 Parsed: {len(raw_segments)} raw → {len(valid_segments)} valid segments.")
|
| 124 |
return {"content": content}
|
| 125 |
|
| 126 |
except Exception as e:
|
| 127 |
+
print(f"❌ Error in Groq analysis (attempt {attempt + 1}): {e}")
|
| 128 |
if attempt < max_retries - 1:
|
| 129 |
wait_time = base_delay * (2 ** attempt)
|
| 130 |
+
print(f"⚠️ Retrying in {wait_time}s...")
|
| 131 |
time.sleep(wait_time)
|
| 132 |
else:
|
| 133 |
break
|
|
|
|
| 138 |
|
| 139 |
def smart_chunk_transcript(transcript, max_tokens=4000):
|
| 140 |
"""
|
| 141 |
+
Split transcript into coherent chunks at sentence boundaries.
|
|
|
|
| 142 |
"""
|
| 143 |
sentences = transcript.replace('\n', ' ').split('. ')
|
| 144 |
chunks = []
|
|
|
|
| 187 |
continue
|
| 188 |
|
| 189 |
if all_segments:
|
| 190 |
+
# Sort by viral quality (reason length as proxy, or add viral_score back if needed)
|
| 191 |
unique_segments = []
|
| 192 |
seen_times = set()
|
| 193 |
|
|
|
|
| 212 |
[45.0 - 60.0] This has saved me hours every week. I batch my emails, phone calls, and even errands.
|
| 213 |
[60.0 - 90.0] The third hack is the Pomodoro Technique. Work for 25 minutes, then take a 5-minute break.
|
| 214 |
[90.0 - 120.0] This technique helps me stay focused and avoid burnout. I get more done in less time.
|
| 215 |
+
[120.0 - 150.0] The fourth hack is to eliminate distractions completely. Turn off notifications, close tabs, and focus.
|
| 216 |
+
[150.0 - 180.0] When you eliminate distractions, your productivity skyrockets. I finish in 2 hours what used to take 6.
|
| 217 |
+
[180.0 - 210.0] The fifth and final hack is to review your day every evening. Spend 5 minutes planning tomorrow.
|
| 218 |
+
[210.0 - 240.0] This evening review changed everything for me. I wake up knowing exactly what to do and I never waste morning time figuring out priorities.
|
| 219 |
"""
|
| 220 |
|
| 221 |
logger.info("🧪 Testing AI Analysis...")
|
|
|
|
| 224 |
try:
|
| 225 |
data = json.loads(result['content'])
|
| 226 |
segments = data.get('segments', [])
|
| 227 |
+
logger.info(f"✅ Found {len(segments)} valid segments:\n")
|
| 228 |
|
| 229 |
for i, seg in enumerate(segments):
|
| 230 |
+
duration = seg['end_time'] - seg['start_time']
|
| 231 |
+
logger.info(
|
| 232 |
+
f"#{i+1} [{seg['start_time']:.0f}s – {seg['end_time']:.0f}s] ({duration:.0f}s)\n"
|
| 233 |
+
f" 📌 Title: {seg.get('title', 'N/A')}\n"
|
| 234 |
+
f" 📝 Description: {seg.get('description', 'N/A')}\n"
|
| 235 |
+
f" 💡 Reason: {seg.get('reason', 'N/A')}\n"
|
| 236 |
+
)
|
| 237 |
except Exception as e:
|
| 238 |
logger.error(f"❌ Error parsing result: {e}")
|
| 239 |
logger.info(f"Raw result: {result}")
|