Movie_Analytics / app.py
SumitP123's picture
Initial upload
6ccb752
"""
๐ŸŽญ Story DNA Analyzer โ€” app.py
================================
Gradio web UI โ€” deploy directly to HuggingFace Spaces.
Run locally: python app.py
"""
import gradio as gr
import json
from model import StoryDNAAnalyzer, StoryDNA
# Load model once at startup
analyzer = StoryDNAAnalyzer()
GRADIO_MAJOR = int(gr.__version__.split(".", 1)[0])
APP_THEME = gr.themes.Soft(primary_hue="purple", neutral_hue="slate")
APP_CSS = """
.title { text-align: center; }
.subtitle { text-align: center; color: #888; }
footer { display: none !important; }
"""
EXAMPLES = [
["A young orphan discovers he is a wizard and is accepted into a magical school, "
"where he makes lifelong friends, faces a dark wizard who killed his parents, "
"and learns that love is the most powerful magic of all."],
["Detective Sarah Lund receives a call at 2 AM. A senator's daughter is missing. "
"As she digs deeper, the trail leads to the highest levels of government. "
"Everyone is lying. The killer is someone she trusts."],
["In 2157, humanity's last colony ship drifts through space after Earth's destruction. "
"When the AI pilot starts making strange decisions, engineer Mara must choose between "
"following orders and saving 4,000 sleeping passengers from a fate worse than death."],
["Two rival bakers in a small Italian village have hated each other for forty years. "
"When their grandchildren fall in love, old secrets surface โ€” and so do feelings "
"they buried long ago. The village holds its breath."],
["The kingdom was built on a lie. When seventeen-year-old Asha uncovers the truth "
"beneath the palace โ€” that the king sacrifices a child every decade to maintain power โ€” "
"she must decide how far she will go to burn it all down."],
]
def format_results(dna: StoryDNA) -> tuple:
"""Convert StoryDNA into nicely formatted Gradio outputs."""
def bar(score: float) -> str:
filled = int(score * 10)
return "โ–ˆ" * filled + "โ–‘" * (10 - filled)
# โ”€โ”€ Genre โ”€โ”€
genre_md = "### ๐Ÿ“š Genre\n"
for label, score in sorted(dna.genre.items(), key=lambda x: -x[1])[:4]:
genre_md += f"`{bar(score)}` **{score:.0%}** โ€” {label}\n\n"
# โ”€โ”€ Tropes โ”€โ”€
tropes_md = "### ๐Ÿงฌ Top Narrative Tropes\n"
for t in dna.top_tropes[:5]:
tropes_md += f"`{bar(t['score'])}` **{t['score']:.0%}** โ€” {t['label']}\n\n"
# โ”€โ”€ Mood โ”€โ”€
mood_md = "### ๐ŸŽจ Story Mood\n"
for label, score in sorted(dna.mood.items(), key=lambda x: -x[1])[:3]:
mood_md += f"`{bar(score)}` **{score:.0%}** โ€” {label}\n\n"
# โ”€โ”€ Audience โ”€โ”€
audience_md = "### ๐Ÿ‘ฅ Target Audience\n"
for label, score in sorted(dna.audience.items(), key=lambda x: -x[1])[:3]:
audience_md += f"`{bar(score)}` **{score:.0%}** โ€” {label}\n\n"
# โ”€โ”€ Villain โ”€โ”€
villain_md = "### ๐Ÿ˜ˆ Villain Archetype\n"
for label, score in sorted(dna.villain.items(), key=lambda x: -x[1])[:3]:
villain_md += f"`{bar(score)}` **{score:.0%}** โ€” {label}\n\n"
# โ”€โ”€ Ending prediction โ”€โ”€
ending_md = "### ๐Ÿ”ฎ Predicted Ending\n"
for label, score in sorted(dna.predicted_ending.items(), key=lambda x: -x[1]):
ending_md += f"`{bar(score)}` **{score:.0%}** โ€” {label}\n\n"
return genre_md, tropes_md, mood_md, audience_md, villain_md, ending_md
def analyze(text: str):
if not text or len(text.strip()) < 20:
empty = "*Please enter a story description (at least 20 characters).*"
return empty, empty, empty, empty, empty, empty
try:
dna = analyzer.analyze(text)
return format_results(dna)
except Exception as e:
err = f"โŒ Error: {str(e)}"
return err, err, err, err, err, err
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# GRADIO UI
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
blocks_kwargs = {"title": "๐ŸŽญ Story DNA Analyzer"}
if GRADIO_MAJOR < 6:
blocks_kwargs.update(theme=APP_THEME, css=APP_CSS)
with gr.Blocks(**blocks_kwargs) as demo:
gr.Markdown(
"# ๐ŸŽญ Story DNA Analyzer\n"
"### Paste any story snippet, book blurb, or movie description โ€” get its full narrative DNA.\n"
"*Powered by HuggingFace zero-shot NLI ยท No training required*",
elem_classes=["title"]
)
with gr.Row():
with gr.Column(scale=2):
text_input = gr.Textbox(
label="๐Ÿ“– Story Text",
placeholder="Paste any story, book blurb, movie synopsis, or description here...",
lines=7,
max_lines=20,
)
analyze_btn = gr.Button("๐Ÿ”ฌ Analyze Story DNA", variant="primary", size="lg")
gr.Examples(examples=EXAMPLES, inputs=text_input, label="๐Ÿ“ Try these examples")
with gr.Column(scale=3):
with gr.Row():
genre_out = gr.Markdown(label="Genre")
tropes_out = gr.Markdown(label="Tropes")
with gr.Row():
mood_out = gr.Markdown(label="Mood")
audience_out= gr.Markdown(label="Audience")
with gr.Row():
villain_out = gr.Markdown(label="Villain")
ending_out = gr.Markdown(label="Ending")
analyze_btn.click(
fn=analyze,
inputs=text_input,
outputs=[genre_out, tropes_out, mood_out, audience_out, villain_out, ending_out],
)
gr.Markdown(
"---\n"
"**Model:** `cross-encoder/nli-MiniLM2-L6-H768` via zero-shot classification \n"
"**No fine-tuning needed** โ€” works on any story in any language."
)
if __name__ == "__main__":
launch_kwargs = {"share": False}
if GRADIO_MAJOR >= 6:
launch_kwargs.update(theme=APP_THEME, css=APP_CSS)
demo.launch(**launch_kwargs)