Movie_Analytics / model.py
SumitP123's picture
Initial upload
6ccb752
"""
๐ŸŽญ Story DNA Analyzer โ€” model.py
=================================
Uses HuggingFace zero-shot NLI to dissect any story text into:
โ€ข Genre (8 categories)
โ€ข Narrative Tropes (12 tropes)
โ€ข Mood (6 tones)
โ€ข Target Audience (4 groups)
โ€ข Villain Archetype (6 types)
โ€ข Story Outcome Prediction (3 endings)
Model: cross-encoder/nli-MiniLM2-L6-H768 (~90 MB, fast CPU inference)
"""
import sys
from transformers import pipeline
from dataclasses import dataclass, field
from typing import Dict, List
import json
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# LABEL DEFINITIONS
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
GENRES = [
"epic fantasy with magic and mythical creatures",
"science fiction with technology and space",
"psychological horror and dread",
"slow-burn romance and emotional longing",
"crime thriller and detective mystery",
"historical drama set in the past",
"dark comedy and satirical humor",
"coming-of-age and personal growth",
]
TROPES = [
"the chosen one who must save the world",
"enemies who fall in love",
"a betrayal by a trusted ally",
"a hero's journey of self-discovery",
"found family replacing blood family",
"a dark secret hidden for years",
"the mentor who dies to motivate the hero",
"redemption arc for a former villain",
"love triangle causing conflict",
"an unreliable narrator hiding the truth",
"a dystopian society being overthrown",
"time travel or parallel universe twist",
]
MOODS = [
"dark, gritty and hopeless",
"warm, cozy and optimistic",
"tense, suspenseful and unpredictable",
"whimsical, playful and magical",
"melancholic, bittersweet and nostalgic",
"intense, passionate and dramatic",
]
AUDIENCES = [
"young children under age 10",
"teenagers and young adults",
"mature adults dealing with complex themes",
"general audiences of all ages",
]
VILLAIN_ARCHETYPES = [
"a power-hungry tyrant who craves control",
"a misunderstood anti-hero with tragic backstory",
"a charismatic manipulator hiding in plain sight",
"an ancient evil force or monster",
"a corrupt institution or faceless system",
"no clear villain โ€” internal struggle is the enemy",
]
ENDINGS = [
"a triumphant happy ending where good wins",
"a tragic ending with sacrifice and loss",
"an ambiguous open ending left to interpretation",
]
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# PRETTY LABEL MAPPING
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
GENRE_DISPLAY = {
"epic fantasy with magic and mythical creatures": "๐Ÿง™ Epic Fantasy",
"science fiction with technology and space": "๐Ÿš€ Science Fiction",
"psychological horror and dread": "๐Ÿ‘๏ธ Psychological Horror",
"slow-burn romance and emotional longing": "๐Ÿ’” Romance",
"crime thriller and detective mystery": "๐Ÿ” Crime Thriller",
"historical drama set in the past": "๐Ÿ›๏ธ Historical Drama",
"dark comedy and satirical humor": "๐ŸŽญ Dark Comedy",
"coming-of-age and personal growth": "๐ŸŒฑ Coming-of-Age",
}
TROPE_DISPLAY = {
"the chosen one who must save the world": "โšก The Chosen One",
"enemies who fall in love": "๐Ÿ’˜ Enemies to Lovers",
"a betrayal by a trusted ally": "๐Ÿ—ก๏ธ The Big Betrayal",
"a hero's journey of self-discovery": "๐Ÿ—บ๏ธ Hero's Journey",
"found family replacing blood family": "๐Ÿค Found Family",
"a dark secret hidden for years": "๐Ÿ”’ Hidden Dark Secret",
"the mentor who dies to motivate the hero": "๐Ÿ’€ Dead Mentor",
"redemption arc for a former villain": "๐ŸŒ… Redemption Arc",
"love triangle causing conflict": "๐Ÿ’ž Love Triangle",
"an unreliable narrator hiding the truth": "๐ŸŽญ Unreliable Narrator",
"a dystopian society being overthrown": "โœŠ Revolution Arc",
"time travel or parallel universe twist": "๐ŸŒ€ Time/Dimension Twist",
}
MOOD_DISPLAY = {
"dark, gritty and hopeless": "๐Ÿ–ค Dark & Gritty",
"warm, cozy and optimistic": "โ˜€๏ธ Warm & Hopeful",
"tense, suspenseful and unpredictable": "โšก Tense & Suspenseful",
"whimsical, playful and magical": "โœจ Whimsical & Magical",
"melancholic, bittersweet and nostalgic": "๐ŸŒง๏ธ Melancholic",
"intense, passionate and dramatic": "๐Ÿ”ฅ Passionate & Dramatic",
}
AUDIENCE_DISPLAY = {
"young children under age 10": "๐Ÿง’ Children (Under 10)",
"teenagers and young adults": "๐Ÿง‘ Young Adult (13โ€“25)",
"mature adults dealing with complex themes": "๐Ÿง” Adult (18+)",
"general audiences of all ages": "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ All Ages",
}
VILLAIN_DISPLAY = {
"a power-hungry tyrant who craves control": "๐Ÿ‘‘ The Tyrant",
"a misunderstood anti-hero with tragic backstory": "๐Ÿฅ€ The Tragic Anti-Hero",
"a charismatic manipulator hiding in plain sight": "๐ŸŽญ The Hidden Manipulator",
"an ancient evil force or monster": "๐Ÿ‰ Ancient Evil",
"a corrupt institution or faceless system": "๐Ÿ›๏ธ The System",
"no clear villain โ€” internal struggle is the enemy": "๐Ÿชž Internal Conflict",
}
ENDING_DISPLAY = {
"a triumphant happy ending where good wins": "๐Ÿ† Happy Ending",
"a tragic ending with sacrifice and loss": "๐Ÿ’ง Tragic Ending",
"an ambiguous open ending left to interpretation": "โ“ Ambiguous Ending",
}
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# RESULT DATACLASS
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@dataclass
class StoryDNA:
genre: Dict[str, float] = field(default_factory=dict)
top_tropes: List[Dict] = field(default_factory=list)
mood: Dict[str, float] = field(default_factory=dict)
audience: Dict[str, float] = field(default_factory=dict)
villain: Dict[str, float] = field(default_factory=dict)
predicted_ending:Dict[str, float] = field(default_factory=dict)
def top(self, d: dict, n=1):
return sorted(d.items(), key=lambda x: -x[1])[:n]
def summary(self) -> str:
lines = ["=" * 52, "๐ŸŽญ STORY DNA ANALYSIS", "=" * 52]
lines.append(f"\n๐Ÿ“š GENRE")
for label, score in self.top(self.genre, 3):
bar = "โ–ˆ" * int(score * 20)
lines.append(f" {label:<30} {bar} {score:.0%}")
lines.append(f"\n๐Ÿงฌ TOP NARRATIVE TROPES")
for t in self.top_tropes[:4]:
lines.append(f" {t['label']:<30} {t['score']:.0%}")
lines.append(f"\n๐ŸŽจ MOOD")
for label, score in self.top(self.mood, 2):
lines.append(f" {label:<30} {score:.0%}")
lines.append(f"\n๐Ÿ‘ฅ TARGET AUDIENCE")
for label, score in self.top(self.audience, 2):
lines.append(f" {label:<30} {score:.0%}")
lines.append(f"\n๐Ÿ˜ˆ VILLAIN ARCHETYPE")
for label, score in self.top(self.villain, 2):
lines.append(f" {label:<30} {score:.0%}")
lines.append(f"\n๐Ÿ”ฎ PREDICTED ENDING")
for label, score in self.top(self.predicted_ending, 2):
lines.append(f" {label:<30} {score:.0%}")
lines.append("=" * 52)
return "\n".join(lines)
def to_dict(self) -> dict:
return {
"genre": self.genre,
"top_tropes": self.top_tropes,
"mood": self.mood,
"audience": self.audience,
"villain": self.villain,
"predicted_ending": self.predicted_ending,
}
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# ANALYZER CLASS
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class StoryDNAAnalyzer:
"""
Zero-shot story analyzer using HuggingFace NLI.
No training needed โ€” works out of the box.
"""
MODEL_ID = "cross-encoder/nli-MiniLM2-L6-H768"
def __init__(self, device: int = -1):
print(f"โณ Loading model: {self.MODEL_ID} ...")
self.clf = pipeline(
"zero-shot-classification",
model=self.MODEL_ID,
device=device, # -1 = CPU, 0 = first GPU
)
print("โœ… Model ready!\n")
def _classify(self, text: str, labels: list, display_map: dict) -> dict:
out = self.clf(text, candidate_labels=labels, multi_label=False)
return {
display_map[label]: round(score, 4)
for label, score in zip(out["labels"], out["scores"])
}
def _classify_multi(self, text: str, labels: list, display_map: dict) -> list:
out = self.clf(text, candidate_labels=labels, multi_label=True)
results = [
{"label": display_map[label], "score": round(score, 4)}
for label, score in zip(out["labels"], out["scores"])
]
return sorted(results, key=lambda x: -x["score"])
def analyze(self, text: str) -> StoryDNA:
if len(text.strip()) < 20:
raise ValueError("Please provide at least 20 characters of story text.")
print("๐Ÿ”ฌ Analyzing Story DNA...")
dna = StoryDNA(
genre = self._classify(text, GENRES, GENRE_DISPLAY),
top_tropes = self._classify_multi(text, TROPES, TROPE_DISPLAY),
mood = self._classify(text, MOODS, MOOD_DISPLAY),
audience = self._classify(text, AUDIENCES, AUDIENCE_DISPLAY),
villain = self._classify(text, VILLAIN_ARCHETYPES,VILLAIN_DISPLAY),
predicted_ending = self._classify(text, ENDINGS, ENDING_DISPLAY),
)
return dna
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# QUICK TEST
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
if __name__ == "__main__":
analyzer = StoryDNAAnalyzer()
sample = """
In a crumbling empire ruled by an immortal king who feeds on fear,
a young blind girl discovers she can see the true faces of the dead.
Hunted by the king's shadow-wolves, she escapes into the forbidden forest
where a disgraced general with blood on his hands offers her an uneasy alliance.
Together they must unlock the secret buried beneath the throne โ€”
a truth that will either free the kingdom or doom it forever.
"""
result = analyzer.analyze(sample)
print(result.summary())
print("\n๐Ÿ“ฆ JSON output:")
print(json.dumps(result.to_dict(), indent=2))