Metadata-Version: 2.4
Name: tvplotlines
Version: 0.1.0
Summary: Extract plotlines from TV series synopses using LLM
Project-URL: Repository, https://github.com/BirdInTheTree/tvplotlines
Project-URL: Documentation, https://github.com/BirdInTheTree/tvplotlines/tree/main/docs
Author-email: BirdInTheTree <51715433+BirdInTheTree@users.noreply.github.com>
License-Expression: MIT
License-File: LICENSE
Keywords: llm,narrative,narrative-analysis,plot-extraction,plotline,synopsis,tv-series
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Text Processing :: Linguistic
Requires-Python: >=3.11
Requires-Dist: anthropic>=0.40
Requires-Dist: openai>=1.50
Requires-Dist: scikit-learn>=1.4
Provides-Extra: dev
Requires-Dist: beautifulsoup4>=4.12; extra == 'dev'
Requires-Dist: httpx>=0.27; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: python-dotenv>=1.0; extra == 'dev'
Provides-Extra: dotenv
Requires-Dist: python-dotenv>=1.0; extra == 'dotenv'
Provides-Extra: writer
Requires-Dist: beautifulsoup4>=4.12; extra == 'writer'
Requires-Dist: httpx>=0.27; extra == 'writer'
Description-Content-Type: text/markdown

# tvplotlines

[![PyPI](https://img.shields.io/pypi/v/tvplotlines)](https://pypi.org/project/tvplotlines/)
[![License](https://img.shields.io/github/license/BirdInTheTree/tvplotlines)](LICENSE)
[![Python](https://img.shields.io/pypi/pyversions/tvplotlines)](https://pypi.org/project/tvplotlines/)

a Python library to extract plotlines from a season of TV synopses using LLMs.

<p align="center">
  <a href="https://birdinthetree.github.io/plotter-app/">
    <img src="docs/images/app-grid.png" alt="tvplotlines output — plotline×episode grid for Breaking Bad S01">
  </a>
  <br>
  <em>tvplotlines output for Breaking Bad S01 in <a href="https://birdinthetree.github.io/plotter-app/">tvplot app</a></em>
</p>

In our benchmarks, a naive LLM prompt covers 5–12% of a season's source material. tvplotlines covers 78–91% — by separating what the model looks for (narrative theory in prompts) from how the result is organized (code).

One function call takes a season of episode synopses and returns structured data: plotlines with cast, Story DNA (hero, goal, obstacle, stakes), A/B/C ranking, and per-episode events.

## Input

One `.txt` file per episode. Include the season and episode number in the filename as `S01E01`, `S01E02`, etc. — any prefix works. 

Each file is a plain-text synopsis of one episode — a few paragraphs covering the main events. See `examples/synopses/breaking-bad/` for  reference.

Put all files in one folder. The folder name becomes the show title (`breaking-bad` → "Breaking Bad"):
```
breaking-bad/
├── S01E01.txt
├── S01E02.txt
├── ...
└── S01E07.txt
```

Install, set your API key, and run:
```bash
pip install tvplotlines
export ANTHROPIC_API_KEY=sk-ant-…

tvplotlines run breaking-bad/
```

Use `--show` to set the title manually:

```bash
tvplotlines run got/ --show "Game of Thrones"
```
## Output

The result is a single JSON file per season. Breaking Bad, Season 1 (truncated):

```json
{
  "context": {
    "format": "serial",
    "story_engine": "A high school teacher builds a drug empire, testing how far he'll go for family and survival"
  },
  "cast": [
    {"id": "walt", "name": "Walter White", "aliases": ["Walt", "Heisenberg", "Mr. White"]},
    {"id": "jesse", "name": "Jesse Pinkman", "aliases": ["Jesse"]}
  ],
  "plotlines": [
    {
      "id": "empire",
      "name": "Walt: Empire",
      "hero": "walt",
      "goal": "build a drug business to secure his family's financial future",
      "obstacle": "inexperience with criminal world, violent dealers like Tuco, moral boundaries",
      "stakes": "death, loss of family, imprisonment",
      "rank": "A",
      "span": ["S01E01", "S01E02", "...", "S01E07"]
    }
  ],
  "episodes": [
    {
      "episode": "S01E01",
      "theme": "transformation through desperation",
      "events": [
        {
          "event": "During the meth lab raid, Walt spots his former student Jesse escaping through a window",
          "plotline_id": "empire",
          "function": "inciting_incident",
          "characters": ["walt", "jesse"]
        }
      ]
    }
  ]
}
```

The output is structured JSON — plug it into your own tools, scripts, or visualizations.

## Key concepts

- **Plotline** — a narrative thread running through one or more episodes (e.g. "Walt: Empire")
- **Story DNA** — who drives the plotline (*hero*), what they want (*goal*), what's in the way (*obstacle*), and what's at risk (*stakes*)
- **A/B/C ranking** — plotline weight: A (main), B (secondary), C (tertiary), runner (minor recurring thread)
- **Format** — procedural (House), serial (Breaking Bad), hybrid (X-Files), ensemble (Game of Thrones)
- **Story engine** — one sentence capturing the show's core dramatic mechanism

More detail in the [prompts](src/tvplotlines/prompts_en/).

## How it works

Four passes, each a separate LLM call with a specialized prompt:

| Pass       | Role                | Input                       | Output                                   |
| ---------- | ------------------- | --------------------------- | ---------------------------------------- |
| **Pass 0** | Context detection   | Show title + first synopses | Format, story engine                     |
| **Pass 1** | Plotline extraction | All synopses + context      | Cast + plotlines with Story DNA          |
| **Pass 2** | Event assignment    | One synopsis + plotlines    | Events assigned to plotlines             |
| **Pass 3** | Quality review      | Full result                 | Verdicts (merge, reassign, create, drop) |

Pass 1 uses majority voting (3 calls). Pass 2 adds one call per episode. Total: 5 fixed calls + one per episode.

Pass 3 reviews the full picture that no earlier pass could see and corrects the result (merge redundant plotlines, reassign misplaced events, etc.).

Tested with Claude Sonnet (default). OpenAI and Ollama are supported but less tested.

## Quick start

```bash
pip install tvplotlines
export ANTHROPIC_API_KEY=sk-ant-...  # or OPENAI_API_KEY for any OpenAI-compatible provider
```

The repo includes example synopses for Breaking Bad and Game of Thrones:

```bash
git clone https://github.com/BirdInTheTree/tvplotlines.git
cd tvplotlines
tvplotlines run examples/synopses/breaking-bad/
```

Pre-computed results are in [`examples/results/`](examples/results/) if you want to explore the output without spending API credits.

### Python API

Seasons can be chained — pass the previous result as `prior` so the model tracks plotline continuity:

```python
from tvplotlines import get_plotlines

s01 = get_plotlines("Breaking Bad", season=1, episodes=season_1_synopses)
s02 = get_plotlines("Breaking Bad", season=2, episodes=season_2_synopses, prior=s01)
```

## LLM providers

Anthropic (default) and any OpenAI-compatible API:

```bash
tvplotlines run breaking-bad/                            # Anthropic (default)
tvplotlines run breaking-bad/ --provider openai          # OpenAI
tvplotlines run breaking-bad/ --provider ollama          # Ollama (local, free)
```

See [docs/api.md](docs/api.md) for full API reference, provider options, and pass modes.

## Citation

If you use tvplotlines in your research, please cite:

```bibtex
@software{tvplotlines2026,
  author = {Vashko, N.},
  title = {tvplotlines: LLM-Driven Plotline Extraction from Episode Synopses},
  year = {2026},
  url = {https://github.com/BirdInTheTree/tvplotlines}
}
```

## License

MIT
