Movie browser and player for archive.org
See the codeForgotten 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.
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.
src/programme/featured.json), with a line on why. Rotates in order, never runs dry.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)./?genre= and /#film links still work./api/mcp and runnable locally from mcp/.The television schedule is public, in three shapes, all computed from the same clock:
| URL | What it is |
|---|---|
/api/tv | JSON: every channel, its lineup, what is on now (with the offset in seconds), and the next six hours |
/api/tv/playlist.m3u | Extended 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.xml | XMLTV 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.
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.
| Variable | For | Required |
|---|---|---|
VITE_TMDB_API_KEY | Live matching and film details in the browser | No |
TMDB_API_KEY, OPEN_ROUTER_API_KEY | Building the poster index (npm run index) | Only to build the index |
OMDB_API_KEY | A second candidate source when the index gives up (--retry-none) | No |
KV_REST_API_*, STATS_TOKEN | Own usage counts and the private /stats page | No |
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.
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.
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.
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.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/ 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.
In the order we mean to do them. Open an issue if you want one.
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.
This product uses the TMDB API but is not endorsed or certified by TMDB.
MIT - feel free to use this project for personal or commercial purposes.
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.
git checkout -b feature/AmazingFeature)git commit -m 'Add some AmazingFeature')git push origin feature/AmazingFeature)The people who have had a pull request merged, most merged first. Generated by npm run contributors.
fatihcvs | nightcityblade | dyk1454683243-sudo | mehul-vi | MehulNegi | Ayesha-zaheer-123 | dw-dash-codes | kante-Ramanaidu |
karthikyannabthina | Rokesh2008 | ruthikx |
Want to be here? Issues labelled good first issue are scoped, with file and line references. A list or a channel counts too.
JavaScript
53.0%
HTML
46.6%
Movie browser and player for archive.org
See the codeForgotten 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.
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.
src/programme/featured.json), with a line on why. Rotates in order, never runs dry.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)./?genre= and /#film links still work./api/mcp and runnable locally from mcp/.The television schedule is public, in three shapes, all computed from the same clock:
| URL | What it is |
|---|---|
/api/tv | JSON: every channel, its lineup, what is on now (with the offset in seconds), and the next six hours |
/api/tv/playlist.m3u | Extended 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.xml | XMLTV 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.
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.
| Variable | For | Required |
|---|---|---|
VITE_TMDB_API_KEY | Live matching and film details in the browser | No |
TMDB_API_KEY, OPEN_ROUTER_API_KEY | Building the poster index (npm run index) | Only to build the index |
OMDB_API_KEY | A second candidate source when the index gives up (--retry-none) | No |
KV_REST_API_*, STATS_TOKEN | Own usage counts and the private /stats page | No |
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.
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.
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.
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.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/ 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.
In the order we mean to do them. Open an issue if you want one.
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.
This product uses the TMDB API but is not endorsed or certified by TMDB.
MIT - feel free to use this project for personal or commercial purposes.
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.
git checkout -b feature/AmazingFeature)git commit -m 'Add some AmazingFeature')git push origin feature/AmazingFeature)The people who have had a pull request merged, most merged first. Generated by npm run contributors.
fatihcvs | nightcityblade | dyk1454683243-sudo | mehul-vi | MehulNegi | Ayesha-zaheer-123 | dw-dash-codes | kante-Ramanaidu |
karthikyannabthina | Rokesh2008 | ruthikx |
Want to be here? Issues labelled good first issue are scoped, with file and line references. A list or a channel counts too.
JavaScript
53.0%
HTML
46.6%