amponce/archive-movie-browser

Movie browser and player for archive.org

JavaScript

86

240 commits

updated Sep 24, 2026

See the code

See what people are saying

SourceMessageScoreDate

Orphanedfilms: Open-Source Movie Browser for the Internet Archive

https://www.orphanedfilms.com/ https://github.com/amponce/archive-movie-browser (not mine, found via https://old.reddit.com/r/opendirectories/comments/1wp281r/i_...)

0

Sep 24, 2026

README

Orphaned Films: an Archive movie browser

Forgotten films, found. A browser and player for the films on the Internet Archive that nobody claimed: public-domain features, orphan works, films whose rights lapsed or were never renewed. We work out which film each upload really is, arrange the good ones into a front page, lists and round-the-clock television channels, and play them in a player that remembers where you were. An MCP server lets an assistant do the finding.

Live at orphanedfilms.com. films identified by Jev

Where the data comes from. Every film is hosted and streamed by the Internet Archive, as its uploader published it there; we host no video. Film identities, artwork, cast and ratings come from TMDB, decided offline by Jev into our index and checked against OMDb when TMDB comes up empty. Film lengths for the channels come from each upload's own Archive.org record. Nothing here is ours, and if a film leaves the Archive it leaves here.

The front page: a list of westerns face out, then television playing over the guide
Tonight
A list to start with, a different category each week, then what is on television right now.
Television: the set playing, the channels beside it, the guide below
Television
Twenty-eight channels and a guide, playing from a fixed moment, so the same film is on for everyone.

What it does

  • Tonight's film. One cult film a day from a hand-picked list (src/programme/featured.json), with a line on why. Rotates in order, never runs dry.
  • Television. Twenty-seven channels. Every curated list is one; sixteen more are stations generated by rule from the index (npm run stations: a genre, a span of decades, posters only, English only) and re-picked every Monday. Each plays its films in order from a fixed moment, so what is on is the same for everyone, and you join mid-film like real TV. The set moves to the next film on its own. ↑ ↓ change channel. "From the start" restarts the film. The same schedule is published as an M3U playlist and an XMLTV guide for other players (see Feeds).
  • Films that are what they say. An index, decided by Jev, says which TMDB film each Archive.org upload really is, so the site can show the film's own title, art and details instead of the uploader's.
  • Browse with every filter: collection, genre pills, decade, runtime, sort, grid or list. Two uploads of the same film collapse to the better copy. Old /?genre= and /#film links still work.
  • A film page that says what the upload really is ("Uploaded as Zombi Holocaust 1980. Identified by the poster index"), with tagline, cast you can click to search, director, and "More like this".
  • Our own player. Arrow keys skip, Space pauses, F is full screen, Esc closes, hover the scrub strip for a frame preview, and it resumes where you left off. "Continue watching" on the front page comes from that, kept in your browser, sent nowhere.
  • Lists. Hand-picked films with a note on each, as pages. One JSON file each, anyone can add one, and every list is also a channel.
  • A channel of your own. Add films from their pages; they play as channel 0 on the same clock, and a link to it shows the same minute to anyone. Kept in your browser.
  • Search with type-ahead over the films' real titles (from the index), genres, collections and the tags uploaders use, plus paste-an-Archive-link.
  • MCP server. Search and browse the films, and ask what is on television, from Claude, Cursor or any MCP client. Hosted at /api/mcp and runnable locally from mcp/.

Feeds

The television schedule is public, in three shapes, all computed from the same clock:

URLWhat it is
/api/tvJSON: every channel, its lineup, what is on now (with the offset in seconds), and the next six hours
/api/tv/playlist.m3uExtended M3U: each channel's lineup in order, with lengths, tvg-id, tvg-chno, group-title, posters as logos, and direct Archive.org streams
/api/tv/guide.xmlXMLTV for the next 24 hours

Point VLC, Kodi, TiviMate or StreamVault at the M3U and the guide. A player that reads now.offset from the JSON and seeks to it is tuning into a live channel that exists nowhere else.

The channels are not live streams. They are files on the Internet Archive played in an order from a fixed moment; the arithmetic is in src/services/schedule.js, and it is the same on the site and in the feeds.

Getting started

git clone https://github.com/amponce/archive-movie-browser.git
cd archive-movie-browser
npm install
npm run dev        # http://localhost:3000, with the api/ functions served locally
npm test
npm run build

Optional keys go in .env.local (see .env.example). Without any key the site works: identified films show their details, television runs, search and browse work. A TMDB key adds live matching for films outside the index and cast, director and ratings on the film page.

VariableForRequired
VITE_TMDB_API_KEYLive matching and film details in the browserNo
TMDB_API_KEY, OPEN_ROUTER_API_KEYBuilding the poster index (npm run index)Only to build the index
OMDB_API_KEYA second candidate source when the index gives up (--retry-none)No
KV_REST_API_*, STATS_TOKENOwn usage counts and the private /stats pageNo

Add a list, get a channel

A list is one JSON file in src/lists/: a slug, a title, a blurb, your handle, and Archive.org identifiers in the order you want them shown, each with an optional one-line note. src/lists/README.md has the format.

Every list is also a channel. The generated stations ("curator": "the station") are rebuilt by npm run stations each week; edit scripts/build-stations.mjs to add a rule. After adding or editing any list, run npm run tv to fetch each film's playable stream and length into public/tv-lineups.json, and commit both. Pick uploads the poster index knows (search the site and look at the film's link), so the list and the channel have covers. Ten to fifteen films is a good channel. Repeats are how television works.

How it is built

React 19, Vite, Tailwind. No router, no state library, no UI kit. Vercel serves the site and the functions in api/.

tailwind.config.js        tokens: ink, bone, signal, line; Big Shoulders Display, IBM Plex
src/index.css             shapes: .btn-*, .pill, .control, .field, .film-frame, .display, .eyebrow, .label
src/ui/                   primitives: Button, Section, FilmCard, SearchField
src/layout/               SiteHeader, SiteFooter, on every page
src/pages/                Home, Tv, Lists, Mcp, Stats
src/components/home/      one file per front-page section
src/components/browse/    FilterBar, GenrePills, FilmGrid; ArchiveMovieBrowser composes them
src/components/film/      FilmTopBar, FilmPoster, FilmDetails, NowPlaying, RelatedShelf; MovieDetailPage composes them
src/components/           FilmPlayer, SearchBox, MovieCard, TitleCover, SettingsModal, McpBanner
src/hooks/                useBrowseFilters (the filters, in the URL), useFilms, useFilmDialog, useFilmDetails,
                          useRelated, useMyChannel, usePosterIndex, useRow, useViewMode
src/services/             pure logic, tested: archive (queries), posterIndex, programme, rows, schedule,
                          playback, suggest, urlFilters, lists, myChannel, coverDesign
src/programme/            featured.json, the hand-picked films of the day
src/lists/                the lists, one JSON each; generated stations say so in the file
public/poster-index.json  the index, decided by Jev: upload -> film, year, rating, confidence, genres, length
public/tv-lineups.json    upload -> playable stream and length, for the channels
api/                      event (usage counts), stats, mcp, tv
mcp/                      the MCP server, stdio and hosted
scripts/                  build-poster-index, build-stations, build-tv-lineups, contributors

Three rules keep it that way. Tokens define, shapes and primitives style, pages compose: a visual change is a one-file change, and a long className in a page belongs in a primitive. Logic lives in src/services with no React and no DOM, so it is tested with node --test and reused by the MCP server and the functions. A deliberate shortcut carries a ponytail: comment naming its ceiling and what replaces it.

How films get identified

Every upload on the Archive is judged once, offline, by Jev (typesafe/jev-1.13, a small decision model): given the upload's title and description and a handful of TMDB candidates, which film is this, or is it none, and how sure. The answer goes in public/poster-index.json and the site reads it as a file, so identifying a film costs nothing at request time. 16,387 uploads decided so far, for under a dollar in total.

Archive.org titles are messy (H 2 House On Haunted Hill ( 1959) Classic Vincent Price Horror Full Movie), so matching them to TMDB in the browser misses a lot. public/poster-index.json holds decisions made offline instead: for the most-downloaded uploads in each film collection, which TMDB film it is, or that it is none. The app checks the index first, so indexed films get real posters with no TMDB key and no TMDB requests, and anything not indexed falls back to live matching.

  • Build or extend it with npm run index (options are in the header of scripts/build-poster-index.mjs; --views follows the genre pills and decades, --cross Horror walks a genre in every decade, --retry-none gives the films it gave up on a second look with OMDb candidates). It needs a TMDB key and an OpenRouter key in .env.local; see .env.example. A decision is permanent per Archive.org identifier, so reruns only pay for new uploads. 750 uploads cost about 3 cents.
  • Jev picks among the TMDB candidates we fetch and reports a confidence. Below 0.7 the app shows the generated cover instead: a wrong poster is worse than none. On a hand-labelled set of 80 hard search results this got 67 right with 0 wrong posters, against 43 right and 5 wrong for the in-browser heuristics.
  • A scheduled workflow refreshes it weekly and pushes the result to a branch for review.
  • Found a wrong poster? Edit that identifier's entry in public/poster-index.json and open a PR. Setting it to { "n": 1, "c": 1, "m": 1 } means "show the generated cover"; "m": 1 marks an entry as corrected by hand, and the build script never overwrites those.

MCP server

mcp/ is a Model Context Protocol server built on the same Archive.org code as the site, so an AI assistant can search the films, browse collections and hand back links that play. Four tools, no API keys. Hosted at https://www.orphanedfilms.com/api/mcp; setup for Claude Code, Claude Desktop and Cursor is in mcp/README.md.

What is next

In the order we mean to do them. Open an issue if you want one.

  1. Index the whole catalogue, about 39,000 real films, so no view ever depends on a live match. The client keeps a small subset and asks the server for the rest.
  2. Twenty-five channels that change every week are in; next is channels people can propose with a list file and a sentence.

Privacy

The live site counts usage with its own small endpoint (api/event.js): no cookies, no third party, no visitor identifiers, nothing sold or shared. It stores counts only in a Redis database: events per day, and monthly leaderboards of films opened and played, searches, filters and referring sites. IP addresses are never stored; distinct visitors are estimated with a HyperLogLog fed by a hash that changes every day, so days cannot be linked. Search text is lowercased, cut to 60 characters, and anything shaped like an email address is removed before it leaves the browser. Bots are not counted, and everything expires after 400 days. The rulebook is api/_stats.js and it is tested.

A fork collects nothing unless its owner connects an Upstash Redis database (vercel integration add upstash/upstash-kv) and sets a STATS_TOKEN for the private /stats page.

API Credits

  • Internet Archive - archive.org - Public domain movie collection and streaming
  • TMDB - themoviedb.org - Movie database API for posters and metadata

This product uses the TMDB API but is not endorsed or certified by TMDB.

License

MIT - feel free to use this project for personal or commercial purposes.

Contributing

Contributions are welcome, from first-time contributors and from people who just love old films. Read CONTRIBUTING.md for setup, what to work on, and what review looks like. See the CHANGELOG for release history. Run npm test and npm run build before opening a pull request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/AmazingFeature)
  3. Commit your changes (git commit -m 'Add some AmazingFeature')
  4. Push to the branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

Contributors

The people who have had a pull request merged, most merged first. Generated by npm run contributors.

fatihcvs
fatihcvs
nightcityblade
nightcityblade
dyk1454683243-sudo
dyk1454683243-sudo
mehul-vi
mehul-vi
MehulNegi
MehulNegi
Ayesha-zaheer-123
Ayesha-zaheer-123
dw-dash-codes
dw-dash-codes
kante-Ramanaidu
kante-Ramanaidu
karthikyannabthina
karthikyannabthina
Rokesh2008
Rokesh2008
ruthikx
ruthikx

Want to be here? Issues labelled good first issue are scoped, with file and line references. A list or a channel counts too.

Acknowledgments

  • Internet Archive for making public domain films accessible
  • TMDB for their comprehensive movie database API
  • The React and Vite communities
archive-org
classic-films
internet-archive
movies
public-domain
react
streaming
tailwindcss
tmdb
vite

Contributors

amponce

184 commits

fatihcvs

19 commits

claude

12 commits

amponce/archive-movie-browser

Movie browser and player for archive.org

JavaScript

86

240 commits

updated Sep 24, 2026

See the code

See what people are saying

SourceMessageScoreDate

Orphanedfilms: Open-Source Movie Browser for the Internet Archive

https://www.orphanedfilms.com/ https://github.com/amponce/archive-movie-browser (not mine, found via https://old.reddit.com/r/opendirectories/comments/1wp281r/i_...)

0

Sep 24, 2026

README

Orphaned Films: an Archive movie browser

Forgotten films, found. A browser and player for the films on the Internet Archive that nobody claimed: public-domain features, orphan works, films whose rights lapsed or were never renewed. We work out which film each upload really is, arrange the good ones into a front page, lists and round-the-clock television channels, and play them in a player that remembers where you were. An MCP server lets an assistant do the finding.

Live at orphanedfilms.com. films identified by Jev

Where the data comes from. Every film is hosted and streamed by the Internet Archive, as its uploader published it there; we host no video. Film identities, artwork, cast and ratings come from TMDB, decided offline by Jev into our index and checked against OMDb when TMDB comes up empty. Film lengths for the channels come from each upload's own Archive.org record. Nothing here is ours, and if a film leaves the Archive it leaves here.

The front page: a list of westerns face out, then television playing over the guide
Tonight
A list to start with, a different category each week, then what is on television right now.
Television: the set playing, the channels beside it, the guide below
Television
Twenty-eight channels and a guide, playing from a fixed moment, so the same film is on for everyone.

What it does

  • Tonight's film. One cult film a day from a hand-picked list (src/programme/featured.json), with a line on why. Rotates in order, never runs dry.
  • Television. Twenty-seven channels. Every curated list is one; sixteen more are stations generated by rule from the index (npm run stations: a genre, a span of decades, posters only, English only) and re-picked every Monday. Each plays its films in order from a fixed moment, so what is on is the same for everyone, and you join mid-film like real TV. The set moves to the next film on its own. ↑ ↓ change channel. "From the start" restarts the film. The same schedule is published as an M3U playlist and an XMLTV guide for other players (see Feeds).
  • Films that are what they say. An index, decided by Jev, says which TMDB film each Archive.org upload really is, so the site can show the film's own title, art and details instead of the uploader's.
  • Browse with every filter: collection, genre pills, decade, runtime, sort, grid or list. Two uploads of the same film collapse to the better copy. Old /?genre= and /#film links still work.
  • A film page that says what the upload really is ("Uploaded as Zombi Holocaust 1980. Identified by the poster index"), with tagline, cast you can click to search, director, and "More like this".
  • Our own player. Arrow keys skip, Space pauses, F is full screen, Esc closes, hover the scrub strip for a frame preview, and it resumes where you left off. "Continue watching" on the front page comes from that, kept in your browser, sent nowhere.
  • Lists. Hand-picked films with a note on each, as pages. One JSON file each, anyone can add one, and every list is also a channel.
  • A channel of your own. Add films from their pages; they play as channel 0 on the same clock, and a link to it shows the same minute to anyone. Kept in your browser.
  • Search with type-ahead over the films' real titles (from the index), genres, collections and the tags uploaders use, plus paste-an-Archive-link.
  • MCP server. Search and browse the films, and ask what is on television, from Claude, Cursor or any MCP client. Hosted at /api/mcp and runnable locally from mcp/.

Feeds

The television schedule is public, in three shapes, all computed from the same clock:

URLWhat it is
/api/tvJSON: every channel, its lineup, what is on now (with the offset in seconds), and the next six hours
/api/tv/playlist.m3uExtended M3U: each channel's lineup in order, with lengths, tvg-id, tvg-chno, group-title, posters as logos, and direct Archive.org streams
/api/tv/guide.xmlXMLTV for the next 24 hours

Point VLC, Kodi, TiviMate or StreamVault at the M3U and the guide. A player that reads now.offset from the JSON and seeks to it is tuning into a live channel that exists nowhere else.

The channels are not live streams. They are files on the Internet Archive played in an order from a fixed moment; the arithmetic is in src/services/schedule.js, and it is the same on the site and in the feeds.

Getting started

git clone https://github.com/amponce/archive-movie-browser.git
cd archive-movie-browser
npm install
npm run dev        # http://localhost:3000, with the api/ functions served locally
npm test
npm run build

Optional keys go in .env.local (see .env.example). Without any key the site works: identified films show their details, television runs, search and browse work. A TMDB key adds live matching for films outside the index and cast, director and ratings on the film page.

VariableForRequired
VITE_TMDB_API_KEYLive matching and film details in the browserNo
TMDB_API_KEY, OPEN_ROUTER_API_KEYBuilding the poster index (npm run index)Only to build the index
OMDB_API_KEYA second candidate source when the index gives up (--retry-none)No
KV_REST_API_*, STATS_TOKENOwn usage counts and the private /stats pageNo

Add a list, get a channel

A list is one JSON file in src/lists/: a slug, a title, a blurb, your handle, and Archive.org identifiers in the order you want them shown, each with an optional one-line note. src/lists/README.md has the format.

Every list is also a channel. The generated stations ("curator": "the station") are rebuilt by npm run stations each week; edit scripts/build-stations.mjs to add a rule. After adding or editing any list, run npm run tv to fetch each film's playable stream and length into public/tv-lineups.json, and commit both. Pick uploads the poster index knows (search the site and look at the film's link), so the list and the channel have covers. Ten to fifteen films is a good channel. Repeats are how television works.

How it is built

React 19, Vite, Tailwind. No router, no state library, no UI kit. Vercel serves the site and the functions in api/.

tailwind.config.js        tokens: ink, bone, signal, line; Big Shoulders Display, IBM Plex
src/index.css             shapes: .btn-*, .pill, .control, .field, .film-frame, .display, .eyebrow, .label
src/ui/                   primitives: Button, Section, FilmCard, SearchField
src/layout/               SiteHeader, SiteFooter, on every page
src/pages/                Home, Tv, Lists, Mcp, Stats
src/components/home/      one file per front-page section
src/components/browse/    FilterBar, GenrePills, FilmGrid; ArchiveMovieBrowser composes them
src/components/film/      FilmTopBar, FilmPoster, FilmDetails, NowPlaying, RelatedShelf; MovieDetailPage composes them
src/components/           FilmPlayer, SearchBox, MovieCard, TitleCover, SettingsModal, McpBanner
src/hooks/                useBrowseFilters (the filters, in the URL), useFilms, useFilmDialog, useFilmDetails,
                          useRelated, useMyChannel, usePosterIndex, useRow, useViewMode
src/services/             pure logic, tested: archive (queries), posterIndex, programme, rows, schedule,
                          playback, suggest, urlFilters, lists, myChannel, coverDesign
src/programme/            featured.json, the hand-picked films of the day
src/lists/                the lists, one JSON each; generated stations say so in the file
public/poster-index.json  the index, decided by Jev: upload -> film, year, rating, confidence, genres, length
public/tv-lineups.json    upload -> playable stream and length, for the channels
api/                      event (usage counts), stats, mcp, tv
mcp/                      the MCP server, stdio and hosted
scripts/                  build-poster-index, build-stations, build-tv-lineups, contributors

Three rules keep it that way. Tokens define, shapes and primitives style, pages compose: a visual change is a one-file change, and a long className in a page belongs in a primitive. Logic lives in src/services with no React and no DOM, so it is tested with node --test and reused by the MCP server and the functions. A deliberate shortcut carries a ponytail: comment naming its ceiling and what replaces it.

How films get identified

Every upload on the Archive is judged once, offline, by Jev (typesafe/jev-1.13, a small decision model): given the upload's title and description and a handful of TMDB candidates, which film is this, or is it none, and how sure. The answer goes in public/poster-index.json and the site reads it as a file, so identifying a film costs nothing at request time. 16,387 uploads decided so far, for under a dollar in total.

Archive.org titles are messy (H 2 House On Haunted Hill ( 1959) Classic Vincent Price Horror Full Movie), so matching them to TMDB in the browser misses a lot. public/poster-index.json holds decisions made offline instead: for the most-downloaded uploads in each film collection, which TMDB film it is, or that it is none. The app checks the index first, so indexed films get real posters with no TMDB key and no TMDB requests, and anything not indexed falls back to live matching.

  • Build or extend it with npm run index (options are in the header of scripts/build-poster-index.mjs; --views follows the genre pills and decades, --cross Horror walks a genre in every decade, --retry-none gives the films it gave up on a second look with OMDb candidates). It needs a TMDB key and an OpenRouter key in .env.local; see .env.example. A decision is permanent per Archive.org identifier, so reruns only pay for new uploads. 750 uploads cost about 3 cents.
  • Jev picks among the TMDB candidates we fetch and reports a confidence. Below 0.7 the app shows the generated cover instead: a wrong poster is worse than none. On a hand-labelled set of 80 hard search results this got 67 right with 0 wrong posters, against 43 right and 5 wrong for the in-browser heuristics.
  • A scheduled workflow refreshes it weekly and pushes the result to a branch for review.
  • Found a wrong poster? Edit that identifier's entry in public/poster-index.json and open a PR. Setting it to { "n": 1, "c": 1, "m": 1 } means "show the generated cover"; "m": 1 marks an entry as corrected by hand, and the build script never overwrites those.

MCP server

mcp/ is a Model Context Protocol server built on the same Archive.org code as the site, so an AI assistant can search the films, browse collections and hand back links that play. Four tools, no API keys. Hosted at https://www.orphanedfilms.com/api/mcp; setup for Claude Code, Claude Desktop and Cursor is in mcp/README.md.

What is next

In the order we mean to do them. Open an issue if you want one.

  1. Index the whole catalogue, about 39,000 real films, so no view ever depends on a live match. The client keeps a small subset and asks the server for the rest.
  2. Twenty-five channels that change every week are in; next is channels people can propose with a list file and a sentence.

Privacy

The live site counts usage with its own small endpoint (api/event.js): no cookies, no third party, no visitor identifiers, nothing sold or shared. It stores counts only in a Redis database: events per day, and monthly leaderboards of films opened and played, searches, filters and referring sites. IP addresses are never stored; distinct visitors are estimated with a HyperLogLog fed by a hash that changes every day, so days cannot be linked. Search text is lowercased, cut to 60 characters, and anything shaped like an email address is removed before it leaves the browser. Bots are not counted, and everything expires after 400 days. The rulebook is api/_stats.js and it is tested.

A fork collects nothing unless its owner connects an Upstash Redis database (vercel integration add upstash/upstash-kv) and sets a STATS_TOKEN for the private /stats page.

API Credits

  • Internet Archive - archive.org - Public domain movie collection and streaming
  • TMDB - themoviedb.org - Movie database API for posters and metadata

This product uses the TMDB API but is not endorsed or certified by TMDB.

License

MIT - feel free to use this project for personal or commercial purposes.

Contributing

Contributions are welcome, from first-time contributors and from people who just love old films. Read CONTRIBUTING.md for setup, what to work on, and what review looks like. See the CHANGELOG for release history. Run npm test and npm run build before opening a pull request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/AmazingFeature)
  3. Commit your changes (git commit -m 'Add some AmazingFeature')
  4. Push to the branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

Contributors

The people who have had a pull request merged, most merged first. Generated by npm run contributors.

fatihcvs
fatihcvs
nightcityblade
nightcityblade
dyk1454683243-sudo
dyk1454683243-sudo
mehul-vi
mehul-vi
MehulNegi
MehulNegi
Ayesha-zaheer-123
Ayesha-zaheer-123
dw-dash-codes
dw-dash-codes
kante-Ramanaidu
kante-Ramanaidu
karthikyannabthina
karthikyannabthina
Rokesh2008
Rokesh2008
ruthikx
ruthikx

Want to be here? Issues labelled good first issue are scoped, with file and line references. A list or a channel counts too.

Acknowledgments

  • Internet Archive for making public domain films accessible
  • TMDB for their comprehensive movie database API
  • The React and Vite communities
archive-org
classic-films
internet-archive
movies
public-domain
react
streaming
tailwindcss
tmdb
vite

Contributors

amponce

184 commits

fatihcvs

19 commits

claude

12 commits

Languages

JavaScript

53.0%

HTML

46.6%