Youtube-AI-Recomendations / src /script_dataset_builder.py
jesuszambrano13's picture
Upload 158 files
84ec0e0 verified
Raw
History Blame Contribute Delete
9.64 kB
"""Construcción ligera de patrones de guion a partir de transcripciones públicas.
Este módulo no descarga datos en runtime. Lee transcripciones ya cargadas en:
data/raw/youtube_transcripts_900/Transcripts/*.json
También deja preparado el punto de extensión para Hugging Face:
from datasets import load_dataset
dataset = load_dataset("ZelonPrograms/Youtube")
En la demo pública no se descarga ZelonPrograms/Youtube para evitar fallos por red,
tiempo o cambios externos. La referencia queda documentada y el pipeline funciona
con el dataset Kaggle de transcripciones incluido en el repositorio.
"""
from __future__ import annotations
import json
import re
from collections import Counter
from pathlib import Path
from typing import Any, Dict, Iterable, List
import pandas as pd
ROOT = Path(__file__).resolve().parents[1]
RAW_TRANSCRIPTS_DIR = ROOT / "data" / "raw" / "youtube_transcripts_900" / "Transcripts"
PROCESSED_DIR = ROOT / "data" / "processed"
FEATURES_CSV = PROCESSED_DIR / "youtube_transcripts_script_features.csv"
PATTERNS_JSON = PROCESSED_DIR / "script_patterns_reference.json"
CTA_TERMS = {
"subscribe", "like", "comment", "share", "click", "download", "visit", "buy", "try", "watch",
"learn more", "sign up", "follow", "save", "join", "comenta", "suscríbete", "comparte", "guarda",
"haz clic", "visita", "compra", "descarga", "regístrate", "sígueme", "conoce más",
}
BENEFIT_TERMS = {
"learn", "improve", "discover", "avoid", "reduce", "increase", "optimize", "how to", "why", "tips",
"strategy", "mistake", "secret", "guide", "aprende", "mejora", "descubre", "evita", "reduce",
"aumenta", "optimiza", "cómo", "por qué", "consejos", "estrategia", "errores", "guía",
}
URGENCY_TERMS = {
"now", "today", "limited", "urgent", "last chance", "hoy", "ahora", "urgente", "última oportunidad", "limitado"
}
TRUST_TERMS = {
"official", "verified", "study", "evidence", "expert", "safe", "guarantee", "case study", "oficial",
"verificado", "estudio", "evidencia", "experto", "seguro", "caso real",
}
EXAGGERATED_TERMS = {
"guaranteed", "100%", "without effort", "miracle", "instant", "get rich", "lose weight fast",
"garantizado", "100 %", "sin esfuerzo", "milagroso", "resultado inmediato", "gana dinero rápido",
"pierde peso rápido", "cura definitiva",
}
GENERIC_OPENINGS = {
"hello", "hi guys", "hey guys", "welcome", "today we are going", "in this video",
"hola", "hola amigos", "bienvenidos", "en este video", "hoy vamos",
}
def _normalize_text(text: str) -> str:
text = re.sub(r"\s+", " ", str(text or "")).strip()
return text
def _count_terms(text: str, terms: Iterable[str]) -> int:
low = text.lower()
return sum(1 for t in terms if t.lower() in low)
def _first_words(text: str, n: int = 30) -> str:
return " ".join(text.split()[:n])
def _read_transcript_json(path: Path) -> str:
try:
data = json.loads(path.read_text(encoding="utf-8"))
except Exception:
return ""
if isinstance(data, list):
return _normalize_text(" ".join(str(x.get("text", "")) if isinstance(x, dict) else str(x) for x in data))
if isinstance(data, dict):
if "transcript" in data:
return _normalize_text(data.get("transcript", ""))
if "text" in data:
return _normalize_text(data.get("text", ""))
if "segments" in data and isinstance(data["segments"], list):
return _normalize_text(" ".join(str(x.get("text", "")) for x in data["segments"] if isinstance(x, dict)))
return ""
def _script_quality_features(transcript: str) -> Dict[str, Any]:
transcript = _normalize_text(transcript)
words = transcript.split()
hook = _first_words(transcript, 30)
low_hook = hook.lower()
low_text = transcript.lower()
word_count = len(words)
sentence_parts = [s.strip() for s in re.split(r"[.!?]+", transcript) if s.strip()]
avg_sentence_len = (sum(len(s.split()) for s in sentence_parts) / max(len(sentence_parts), 1)) if sentence_parts else 0
hook_has_question = "?" in hook or any(low_hook.startswith(x) for x in ["how", "why", "what", "when", "where", "cómo", "por qué", "qué"])
hook_has_benefit = _count_terms(hook, BENEFIT_TERMS) > 0
generic_opening = any(p in low_hook[:160] for p in GENERIC_OPENINGS)
cta_count = _count_terms(transcript, CTA_TERMS)
benefit_count = _count_terms(transcript, BENEFIT_TERMS)
urgency_count = _count_terms(transcript, URGENCY_TERMS)
trust_count = _count_terms(transcript, TRUST_TERMS)
exaggerated_count = _count_terms(transcript, EXAGGERATED_TERMS)
hook_score = 50 + 20 * int(hook_has_question) + 25 * int(hook_has_benefit) - 20 * int(generic_opening)
hook_score = max(0, min(100, hook_score))
cta_score = max(0, min(100, 35 + min(cta_count, 3) * 20))
value_score = max(0, min(100, 45 + min(benefit_count, 4) * 12 + min(trust_count, 2) * 8))
clarity_score = 78
if word_count < 25:
clarity_score -= 15
if word_count > 2500:
clarity_score -= 12
if avg_sentence_len > 28:
clarity_score -= 15
if transcript.count("!") > 6:
clarity_score -= 6
clarity_score = max(0, min(100, clarity_score))
policy_claim_risk = "alto" if exaggerated_count >= 2 else ("medio" if exaggerated_count == 1 else "bajo")
script_quality_score = int(round(0.30 * hook_score + 0.25 * clarity_score + 0.25 * value_score + 0.20 * cta_score))
return {
"transcript_word_count": word_count,
"hook_first_30_words": hook,
"hook_has_question": int(hook_has_question),
"hook_has_benefit": int(hook_has_benefit),
"generic_opening": int(generic_opening),
"cta_count": cta_count,
"benefit_count": benefit_count,
"urgency_count": urgency_count,
"trust_count": trust_count,
"exaggerated_claim_count": exaggerated_count,
"avg_sentence_length": round(avg_sentence_len, 2),
"hook_score": hook_score,
"cta_score": cta_score,
"value_proposition_score": value_score,
"clarity_score": clarity_score,
"policy_claim_risk": policy_claim_risk,
"script_quality_score": script_quality_score,
}
def build_from_local_transcripts(raw_dir: Path = RAW_TRANSCRIPTS_DIR) -> pd.DataFrame:
rows: List[Dict[str, Any]] = []
for path in sorted(raw_dir.glob("*.json")):
transcript = _read_transcript_json(path)
if not transcript:
continue
feats = _script_quality_features(transcript)
rows.append({
"video_id": path.stem,
"source_dataset": "kaggle_youtube_trending_videos_transcripts_900",
"title": "",
"views": None,
"transcript": transcript,
**feats,
})
return pd.DataFrame(rows)
def build_patterns_reference(df: pd.DataFrame) -> Dict[str, Any]:
if df.empty:
return default_patterns_reference()
top = df.sort_values("script_quality_score", ascending=False).head(min(150, len(df)))
hook_terms = Counter()
cta_terms = Counter()
benefit_terms = Counter()
for _, row in top.iterrows():
hook = str(row.get("hook_first_30_words", "")).lower()
transcript = str(row.get("transcript", "")).lower()
for term in BENEFIT_TERMS:
if term in hook:
hook_terms[term] += 1
if term in transcript:
benefit_terms[term] += 1
for term in CTA_TERMS:
if term in transcript:
cta_terms[term] += 1
return {
"source": "youtube_trending_videos_transcripts_900",
"n_transcripts": int(len(df)),
"quality_score_mean": float(round(df["script_quality_score"].mean(), 2)),
"quality_score_p75": float(round(df["script_quality_score"].quantile(0.75), 2)),
"top_hook_patterns": [x for x, _ in hook_terms.most_common(20)] or sorted(list(BENEFIT_TERMS))[:20],
"top_cta_patterns": [x for x, _ in cta_terms.most_common(20)] or sorted(list(CTA_TERMS))[:20],
"top_benefit_patterns": [x for x, _ in benefit_terms.most_common(20)] or sorted(list(BENEFIT_TERMS))[:20],
"risk_terms": sorted(list(EXAGGERATED_TERMS)),
"generic_openings": sorted(list(GENERIC_OPENINGS)),
"notes": "Patrones extraídos de transcripciones públicas de videos populares. No garantizan viralidad; se usan como referencia semántica para mejorar guion.",
}
def default_patterns_reference() -> Dict[str, Any]:
return {
"source": "default_rules",
"n_transcripts": 0,
"quality_score_mean": 0,
"quality_score_p75": 70,
"top_hook_patterns": sorted(list(BENEFIT_TERMS))[:20],
"top_cta_patterns": sorted(list(CTA_TERMS))[:20],
"top_benefit_patterns": sorted(list(BENEFIT_TERMS))[:20],
"risk_terms": sorted(list(EXAGGERATED_TERMS)),
"generic_openings": sorted(list(GENERIC_OPENINGS)),
"notes": "Patrones por defecto; ejecutar script_dataset_builder.py para usar transcripciones reales.",
}
def build_and_save() -> Dict[str, Any]:
PROCESSED_DIR.mkdir(parents=True, exist_ok=True)
df = build_from_local_transcripts()
df.to_csv(FEATURES_CSV, index=False)
patterns = build_patterns_reference(df)
PATTERNS_JSON.write_text(json.dumps(patterns, ensure_ascii=False, indent=2), encoding="utf-8")
return {"rows": int(len(df)), "features_csv": str(FEATURES_CSV), "patterns_json": str(PATTERNS_JSON)}
if __name__ == "__main__":
print(json.dumps(build_and_save(), ensure_ascii=False, indent=2))