guessit-js

August 15, 2026 · View on GitHub

Extract metadata (title, year, season, episode, codec, language, etc.) from media filenames — TypeScript port of Python guessit.

npm version Tests License: LGPL-3.0 Python guessit compat Coverage

Live Demo · API Docs · npm

Features

  • 100% compatibility with Python guessit 4.4.0 (1342/1342 fixtures passing, including Python's own grown 4.x corpus) — and more correct: ships fixes for 32 historical upstream bugs, Python's still-open #875 and #877, and 112 of the 276 cross-parser cases Python admits failing (see Differences from Python)
  • 3.5x faster than Python (6.87ms vs 23.86ms per parse)
  • 50 properties detected: title, year, season, episode, resolution, codec, language, and more — with a machine-readable schema
  • Single dependency (rebulk-js)
  • Dual format: ESM and CommonJS
  • TypeScript: full, precise type definitions (typed GuessItResult, enum'd value fields)
  • WASM: runs in any WASI-compatible runtime, bit-identical to the JS build

Install

npm install guessit-js

CLI

Drop-in replacement for the Python guessit command — same flags, same output formats (verified against the real Python CLI in CI). Installs both guessit-js and guessit binaries:

npx guessit-js "The.Dark.Knight.2008.1080p.BluRay.x264-GROUP.mkv"
# title: The Dark Knight
# year: 2008
# ...

npx guessit-js -j "Mob Psycho 100 - 09.mkv"        # JSON (-y YAML, --jsonl streaming)
npx guessit-js -P title "Movie.2020.mkv"           # single property
npx guessit-js -t episode "ambiguous.file.mkv"     # force type
npx guessit-js @list.txt                           # filenames from file (also -f list.txt)
find . -name '*.mkv' | npx guessit-js --jsonl      # from stdin, one JSON per line
npx guessit-js -a "Movie.2020.mkv"                 # advanced: value/raw/start/end
npx guessit-js -p                                  # list detectable properties (-V with values)

Parsing options match the Python CLI: -n name-only, -Y/-D date order, -L/-C allowed languages/countries, -E episode-prefer-number, -T/-G expected title/group, --includes/--excludes, -s single-value, -v verbose. User config is auto-loaded from ~/.guessit/options.json and ~/.config/guessit/options.json (.yaml/.yml too; disable with --no-user-config), plus explicit -c <file>. See npx guessit-js --help.

Extras beyond the Python CLI:

npx guessit-js --serve 3847            # instant REST API (see below)
npx guessit-js --benchmark 1000 "Movie.2020.1080p.mkv"   # throughput report
eval "$(npx guessit-js --completion bash)"               # shell completion (bash/zsh)

Usage

import { guessit } from 'guessit-js';

const result = guessit('The.Dark.Knight.2008.1080p.BluRay.x264-GROUP.mkv');
// {
//   title: 'The Dark Knight',
//   year: 2008,
//   screen_size: '1080p',
//   source: 'Blu-ray',
//   video_codec: 'H.264',
//   release_group: 'GROUP',
//   container: 'mkv',
//   type: 'movie'
// }

// Short titles are handled correctly too:
guessit('X2.2003.720p.DSNP.WEB-DL.DDP5.1.H.264-EVO.mkv');
// { title: 'X2', year: 2003, screen_size: '720p',
//   streaming_service: 'Disney+', source: 'Web', ... }

CommonJS

const { guessit } = require('guessit-js');
const result = guessit('Breaking.Bad.S01E02.720p.BluRay.x264-DEMAND.mkv');
console.log(result.title);   // 'Breaking Bad'
console.log(result.season);  // 1
console.log(result.episode); // 2

Options

guessit('file.mkv', { type: 'episode' });
guessit('my 720p show S01E02', { expected_title: ['my 720p show'] });
guessit('file.mkv', { allowed_languages: ['en', 'fr'] });
guessit('file.mkv', { excludes: ['release_group'] });
import { version } from 'guessit-js';  // package version string

Detected Properties

CategoryProperties
Titletitle, alternative_title, episode_title
Episodeseason, episode, episode_details, episode_count, season_count, absolute_episode, disc, part
Dateyear, date
Videoscreen_size, aspect_ratio, frame_rate, video_codec, video_profile, color_depth
Audioaudio_codec, audio_profile, audio_channels, audio_bit_rate
Sourcesource, streaming_service
Releaserelease_group, edition, other, proper_count
Filecontainer (video / subtitle / archive / image / nfo / torrent / nzb), mimetype, size, crc32, uuid
Metadatalanguage, subtitle_language, country, type

Output schema

The result is fully typed — import GuessItResult for autocomplete and type-checking:

import { guessit, properties, GUESSIT_SCHEMA, type GuessItResult } from 'guessit-js';

const r: GuessItResult = guessit('The.Dark.Knight.2008.1080p.BluRay.x264-GRP.mkv');
r.source;  // typed as the closed enum: "Blu-ray" | "Web" | "HDTV" | …

properties();        // { source: ["Blu-ray", "Web", …], type: ["episode","movie"], … } for all 50 properties
GUESSIT_SCHEMA.source.enum;  // the allowed values, programmatically
  • properties() — returns every emittable property with its possible values (value-constrained props list their full enum; free/computed props list [null]), mirroring Python guessit's properties().
  • GUESSIT_SCHEMA — the machine-readable schema (type, cardinality, enum) for all properties.
  • docs/output-schema.json — a JSON Schema (draft-07) of the output, for validating results or generating clients in other languages.

Regenerate the schema (after parsing changes) with npm run schema. A test (test/schema.test.ts) guarantees it never goes stale — every value emitted across the corpus must be in the schema.

REST API

Hosted demo (testing only):

curl "https://guessit.opensubtitles.com/api/guessit?filename=Movie.2024.1080p.mkv"
curl -X POST https://guessit.opensubtitles.com/api/guessit \
  -H 'Content-Type: application/json' \
  -d '{"filenames": ["A.2020.mkv", "B.S01E02.mkv"]}'

⚠️ For testing and evaluation only — never use in production. It runs on a free Cloudflare Workers tier with a hard daily request cap and no uptime guarantee; it can be throttled, rate-limited, or removed at any time. For production, self-host (npx guessit-js --serve, one command) or better, call the library in-process (~1.5 ms per parse, no HTTP at all).

Runs on Cloudflare Workers at the edge, CORS enabled, batch up to 500 filenames per POST, redeployed automatically with every release.

Self-hosted — no install needed beyond the package itself:

npx guessit-js --serve                 # port 3847 (or --serve 8080 / PORT env)
curl "http://localhost:3847/api/guessit?filename=Movie.2024.1080p.mkv"
curl -X POST http://localhost:3847/api/guessit \
  -H 'Content-Type: application/json' \
  -d '{"filenames": ["A.2020.mkv", "B.S01E02.mkv"], "options": {"type": "episode"}}'

CLI parsing flags become server defaults: guessit-js --serve -t episode -L en. The repo also ships a fuller dev server (npm start) with the demo page, Swagger UI at /docs, and static WASM serving.

WASM

For non-JS environments (Rust, Go, C++, edge compute). Uses Javy (QuickJS → WASM).

npm run wasm
echo '{"filename":"Movie.2024.1080p.mkv"}' | wasmtime wasm/guessit.wasm

The WASM build is bit-identical to the JS build across the entire test corpus (1035/1035, including accented titles) — verified by test/wasm-full.test.ts.

Differences from Python guessit

guessit-js is a faithful port (1342/1342 fixtures, tracking Python 4.4.0), but it is not bug-for-bug identical — where Python has a genuine parsing bug, guessit-js is corrected. Highlights:

  • Cases the released Python 4.4.0 still gets wrong (each verified against the real Python CLI; the demo shows live side-by-sides): X2.2003… (Python: bonus: 2, no title), Python's still-open #875 and #877 (Season 3 - 11, anchored weak episodes, 0x539 ids), Python's dead RemoveLessSpecificSeasonEpisode("episode") pass (#961, our fix submitted as #962), and 112 of the 276 cross-parser cases Python's own test data admits failing — fullwidth CJK bracket groups, UFC event numbers, anime compound dash titles, fully-bracketed titles, and more.
  • 32 historical upstream bugs were fixed here ahead of upstream during the 4.x catch-up (many since adopted by Python 4.4 — e.g. Us.2019, The.Collector, grown-ish, imdb_id/tmdb_id, volume, artwork and month-date detection are now correct in both). Full ledger with dispositions: docs/upstream-issues.md.
  • Typed, schema-described output: a precise GuessItResult interface with enum'd value fields, properties() mirroring Python's API, and a JSON Schema (draft-07) of the output — none of which Python provides as machine-readable artifacts.
  • No Python runtime: zero runtime dependencies, ESM + CJS + WASM, ~3.5x faster.
  • Intentional divergences (cases where guessit-js is more correct than Python) are catalogued per-example in docs/python-parity.md.

Known issues

  • Music files are not supported (#599): Artist - Album/01 Track.flac is parsed with the video vocabulary (title / alternative_title), not artist/album/track. Out of scope for now.
  • No composite quality field (#802): screen_size, source and video_codec are returned separately, not combined into one string (the request is underspecified).
  • A few ambiguous anime conventions remain (#690/#696/#747): e.g. Re ZERO …- Season 2 - 15, romaji + (English title) — no unambiguous correct parse.
  • 12 debatable cases vs Python (neither clearly right) are listed under "② NEUTRAL" in docs/python-parity.md.

Performance

Warm per-parse, measured on one machine (absolute numbers are hardware-dependent — the live demo times it in your browser):

Runtimems/parseNotes
Browser (V8)~2–3 msfastest; JIT-compiled
Node.js 22 (V8)~4.4 ms~3.5× faster than Python (same machine) — recommended for servers/CLI
Python 3.8~15.5 msreference
WASM (QuickJS/Javy)~35 ms warm · ~150 ms coldfor portability, not speed (see below)

About the WASM build. It exists so you can run guessit in environments without a JS engine (Rust, Go, C/C++, edge/WASI runtimes). Javy compiles the bundle to QuickJS, which is an interpreter (no JIT), so per-parse compute (~35 ms) is slower than V8 and even than Python — that's inherent to the engine, not the code. If you want speed, use the Node/browser build (V8). If you need WASM, the levers that actually help:

  • Amortize startup — instantiate the module once and parse many filenames; most of the ~150 ms single-shot cost is wasmtime + module init, not parsing.
  • AOT-compile the module (wasmtime compile guessit.wasm -o guessit.cwasm) to skip per-run JIT of the wasm itself (~30 ms off cold start).
  • The ~35 ms warm floor is QuickJS interpretation; beating it would require a JIT-capable WASI JS engine (none production-ready) or a native port — out of scope. WASM correctness is bit-identical to the JS build.

License

Copyright (C) 2024-2026 OpenSubtitles

Licensed under the GNU Lesser General Public License v3.0 (LGPL-3.0), the same license as the original Python guessit library this project is ported from.

Build-time and embedded third-party components (Javy, QuickJS) are covered in THIRD_PARTY_NOTICES.md.