Initial commit

This commit is contained in:
Luis Reich
2026-09-22 23:01:56 +02:00
parent d122f1610e
commit a4bbb390c3
9 changed files with 2296 additions and 1 deletions

View File

@@ -1,2 +1,87 @@
# letterboxd-rating-sorter
# Letterboxd Pairwise Ranker
Turns a Letterboxd `ratings.csv` export into a ranked order by asking you to pick between
two films at a time, then converts that order back into 0.5-5 star ratings.
## Normal use
Double-click **Rank films.cmd**.
The page opens in your browser. Drop your `ratings.csv` on it and start picking. Box art is
fetched for you in the background and cached in `posters.json`, so later runs are instant.
Closing the console window stops the server.
Rankings you have started are listed under the drop zone as **Continue where you left off**,
with their film count, progress and when you last answered something. Continuing needs no
csv: the session stores each film's original row, so it can also export a correct file on its
own. `Delete` forgets one. Note that a saved session holds the film list from the export you
dropped at the time - to include films watched since, drop a fresh csv and start a new one.
Needs `python` on your PATH; the launcher says so if it is missing. Same thing from a
terminal, if you prefer:
```
python serve.py
```
## Without the server
`ranker.html` also opens straight from disk by double-click. It cannot fetch box art on its
own, because a browser blocks a `file://` page from calling letterboxd.com. Resolve the
posters first, then drop both files on the page:
```
python fetch_posters.py path/to/ratings.csv
```
`--retry-failed` retries the URIs that came back empty last time.
## Ranking
Bottom-up merge sort, so the number of questions stays near the minimum: about 25 for 10
films, 90 for 25, 1,310 for 200. No ties, every pair has a winner.
Each round merges pairs of groups, and the groups take turns, so consecutive questions
normally show two films you have not just seen. The exception is the last couple of rounds,
where only one or two groups are left and a film has to be held against several opponents in
a row to find its place.
- Click a poster, or press `1` / `2`, or the arrow keys.
- `U` or Backspace undoes.
- The panel on the right shows everything established so far: each ordered group, best at the
top, with a dashed break wherever the order across it is still unknown. The two films on
screen are highlighted in place. Films you have not been asked about yet are left out, so it
starts at just the current pair and fills in as you go. `Hide list` collapses it, and it
stays collapsed.
- Progress is saved after every answer, keyed to the film set. Close the tab and the page
offers to resume where you left off. A different export starts its own session.
Dark by default. The button in the top right switches to light and remembers the choice. The
system theme is ignored on purpose.
Progress lives in browser storage for `127.0.0.1:8791`, so it is tied to that browser and
that port. Clearing site data for it, or running on a different port with `--port`, means no
resume. If 8791 is ever busy the server moves up and says so.
## Stars and export
When the order is complete, the result screen splits it into the ten half-star bands. Set how
many films land in each band with the `+` / `-` buttons or by typing a count, or use a preset:
- **Match old ratings** keeps the shape of your existing ratings, so only which film sits
where changes. This is the default when the export has a `Rating` column.
- **Even split** and **Top heavy** are starting points to adjust from.
**Download rated CSV** writes your export back out unchanged - same header, same columns,
`Date` included - in ranking order, with `Rating` replaced by the new star value.
## Files
| File | What it is |
| --- | --- |
| `Rank films.cmd` | Double-click launcher. Runs `python serve.py`. |
| `ranker.html` | The whole app. No dependencies, no build step. |
| `serve.py` | Serves the page and resolves posters on its behalf. |
| `fetch_posters.py` | Standalone poster resolver, also the module `serve.py` uses. |
| `posters.json` | Poster URL cache. Safe to delete, it rebuilds. |
| `sample.csv` | Eight rows for a quick smoke test. |