#!/usr/bin/env python3 """ MOMENT FINDER - Busca momentos específicos en transcripción guardada Uso: python3 moment_finder.py --transcription transcripcion_rage.json --type rage """ import json import re import argparse from pathlib import Path class MomentFinder: """Busca momentos específicos en una transcripción guardada.""" def __init__(self, transcription_file): with open(transcription_file, "r") as f: self.trans = json.load(f) print(f"Transcripción cargada: {len(self.trans['segments'])} segmentos") def find_rage_moments(self, skip_intro=455, min_score=5): """Busca momentos de rage, muertes y fails.""" patterns = { "EXTREME_RAGE": [ r"\bputa\w*", r"\bmadre\b", r"\bretrasad\w*", r"\bimbecil\w*", r"\bestupid\w*", r"\bidiota\w*", r"\bmierda\b", r"\bbasura\w*", r"\binutil\w*", r"\bmongol\w*", r"\bmaricon\w*", r"\bcallate\b", ], "DEATH": [ r"\bme mataron\b", r"\bme mori\b", r"\bme muero\b", r"\bmatenme\b", r"\bfeed\w*", r"\bme destrozaron\b", r"\bme comieron\b", r"\bme cargaron\b", r"\bme jodieron\b", ], "FAIL": [ r"\bla cague\b", r"\bla lie\b", r"\berror\b", r"\bfail\b", r"\bperdon\b", r"\bperd[oó]n\b", r"\blo siento\b", r"\bmala mia\b", r"\bfall[eé]\b", r"\bno puede ser\b", ], "TEAM_RAGE": [ r"\bequipo\b.*\b(mierda|basura|malos|peor)\b", r"\bteam\b.*\b(trash|bad|mierda)\b", r"\breport\w*", r"\btroll\w*", r"\binting\b", ], "FRUSTRATION": [ r"\b(nooo+|no no)\b", r"\bpor que\b", r"\bporque\b", r"\ben serio\b", r"\bno me jodas\b", r"\bque (haces|hace)\b", r"\bhostia\b", r"\bjoder\b", r"\bdios\b", ], } return self._find_moments(patterns, skip_intro, min_score) def find_epic_moments(self, skip_intro=455, min_score=5): """Busca jugadas épicas y celebraciones.""" patterns = { "EPIC_PLAY": [ r"\bpentakill\b", r"\bbaron\b", r"\bdrag[oó]n\b", r"\btriple\b", r"\bquadra\b", r"\bace\b", r"\bepico\b", r"\bgod\b", r"\binsane\b", r"\bclutch\b", ], "CELEBRATION": [ r"\bnice\b", r"\bgg\b", r"\bgood\b", r"\bwell\b", r"\bperfecto\b", r"\bexcelente\b", r"\bgenial\b", ], "LAUGHTER": [ r"\bjajaj\w*", r"\bjejej\w*", r"\brisas?\b", r"\bcarcajada\b", ], "SKILLS": [ r"\bulti\b", r"\bflash\b", r"\bignite\b", r"\bexhaust\b", ], } return self._find_moments(patterns, skip_intro, min_score) def find_reaction_moments(self, skip_intro=455, min_score=3): """Busca reacciones y momentos emotivos.""" patterns = { "SURPRISE": [ r"\bwo+w*\b", r"\bwhat\b", r"\bcomo\?\b", r"\ben serio\?\b", r"\bno puede ser\b", r"\bimpresionante\b", ], "HYPE": [ r"\bvamos\b", r"\bvamoo+s\b", r"\blet.s go\b", r"\bvamo+s\b", r"\bgg\b", r"\bnice\b", r"\bway\b", ], "EMOTION": [ r"\bomg\b", r"\boh dios\b", r"\bno lo creo\b", r"\bes increible\b", r"\bque locura\b", ], } return self._find_moments(patterns, skip_intro, min_score) def _find_moments(self, patterns, skip_intro, min_score): """Busca momentos basados en patrones.""" moments = [] for seg in self.trans.get("segments", []): if seg["start"] < skip_intro: continue text = seg["text"].lower() score = 0 reasons = [] for category, pattern_list in patterns.items(): for pattern in pattern_list: if re.search(pattern, text, re.IGNORECASE): # Puntuación por categoría if category in ["EXTREME_RAGE", "EPIC_PLAY"]: score += 10 elif category in ["DEATH", "TEAM_RAGE"]: score += 8 elif category in ["FAIL", "CELEBRATION"]: score += 6 else: score += 4 if category not in reasons: reasons.append(category) break if score >= min_score: moments.append( { "start": seg["start"], "end": seg["end"], "score": score, "text": seg["text"][:80], "reasons": reasons, } ) return moments def create_clips(self, moments, max_clips=15, extend_before=10, extend_after=20): """Crea clips a partir de momentos.""" # Ordenar por score moments.sort(key=lambda x: -x["score"]) # Crear clips extendidos clips = [] for m in moments[: max_clips * 2]: # Más candidatos start = max(455, int(m["start"]) - extend_before) end = min(8237, int(m["end"]) + extend_after) if end - start >= 12: clips.append( { "start": start, "end": end, "score": m["score"], "reasons": m["reasons"], "text": m["text"], } ) # Eliminar solapamientos clips.sort(key=lambda x: x["start"]) filtered = [] for clip in clips: if not filtered: filtered.append(clip) else: last = filtered[-1] if clip["start"] <= last["end"] + 3: # Fusionar last["end"] = max(last["end"], clip["end"]) last["score"] = max(last["score"], clip["score"]) last["reasons"] = list(set(last["reasons"] + clip["reasons"])) else: filtered.append(clip) # Tomar top clips filtered.sort(key=lambda x: -x["score"]) final = filtered[:max_clips] final.sort(key=lambda x: x["start"]) return final def save_clips(self, clips, output_file): """Guarda clips en formato JSON.""" highlights = [[c["start"], c["end"]] for c in clips] with open(output_file, "w") as f: json.dump(highlights, f) print(f"\nGuardado: {output_file}") print(f"Total: {len(clips)} clips") total_dur = sum(c["end"] - c["start"] for c in clips) print(f"Duración: {total_dur}s ({total_dur // 60}m {total_dur % 60}s)") def main(): parser = argparse.ArgumentParser(description="Find moments in saved transcription") parser.add_argument( "--transcription", required=True, help="Transcription JSON file" ) parser.add_argument( "--type", choices=["rage", "epic", "reaction", "all"], default="rage", help="Type of moments to find", ) parser.add_argument( "--output", default="highlights_moments.json", help="Output file" ) parser.add_argument("--max-clips", type=int, default=12, help="Max clips") args = parser.parse_args() finder = MomentFinder(args.transcription) print(f"\nBuscando momentos tipo: {args.type.upper()}") print("=" * 60) if args.type == "rage": moments = finder.find_rage_moments() elif args.type == "epic": moments = finder.find_epic_moments() elif args.type == "reaction": moments = finder.find_reaction_moments() else: # all rage = finder.find_rage_moments(min_score=4) epic = finder.find_epic_moments(min_score=4) reaction = finder.find_reaction_moments(min_score=3) moments = rage + epic + reaction # Eliminar duplicados seen = set() unique = [] for m in moments: key = int(m["start"]) if key not in seen: seen.add(key) unique.append(m) moments = unique print(f"Momentos encontrados: {len(moments)}") # Mostrar top 10 moments.sort(key=lambda x: -x["score"]) print("\nTop momentos:") for i, m in enumerate(moments[:10], 1): mins = int(m["start"]) // 60 secs = int(m["start"]) % 60 print( f"{i:2d}. {mins:02d}:{secs:02d} [Score: {m['score']:2d}] " f"{'/'.join(m['reasons'][:2])} - {m['text'][:50]}..." ) # Crear y guardar clips clips = finder.create_clips(moments, max_clips=args.max_clips) finder.save_clips(clips, args.output) print("\nTimeline final:") for i, c in enumerate(clips, 1): mins, secs = divmod(c["start"], 60) dur = c["end"] - c["start"] print(f"{i:2d}. {mins:02d}:{secs:02d} - {dur}s [{', '.join(c['reasons'][:2])}]") if __name__ == "__main__": main()