npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@nepiraw/parsium

v1.1.0

Published

The most accurate torrent/video filename parser

Readme

Parsium

A highly accurate torrent and video filename parser.

Built and measured against real-world torrents. Handles movies, TV series, anime, multi-language releases and messy international filenames, with a very high title accuracy.

Zero dependencies. Around 0.15ms per parse. Works everywhere JavaScript runs.

Try it live in your browser with our interactive demo and playground.


Highlights

  • Very high title accuracy: validated against 10K real production filenames
  • Auto-detects content type: movies, series, anime, no hints needed
  • 80+ languages: VFF, VOSTFR, MULTi, Latino, DL, DualAudio, and more
  • Anime-first-class: fansub conventions, absolute episodes, batch ranges
  • Position-independent: handles tracker prefixes, noise, mixed separators
  • Explainable: every field reports the evidence it was decided on, and what lost
  • Plugin system: add custom proposers without modifying core
  • Zero dependencies: pure TypeScript, ESM + CJS, about 57KB gzipped

Install

npm install parsium-media
# or
pnpm add parsium-media
# or
yarn add parsium-media

The package is published as parsium-media. A scoped alias @nepiraw/parsium points at the identical build if you prefer it: npm install @nepiraw/parsium.


Quick Start

import { parse } from 'parsium-media';

const result = parse("The.Boys.S05E02.REPACK.1080p.WEB.H264-TyHD.mkv");
// {
//   title: "The Boys",
//   contentType: "series",
//   seasons: [5],
//   episodes: [2],
//   resolution: "1080p",
//   source: "WEB-DL",
//   codec: "H.264",
//   releaseGroup: "TyHD",
//   isRepack: true,
//   ...
// }

CLI

# Parse a single filename
npx parsium-media "Movie.2024.2160p.UHD.BluRay.x265-GROUP"

# JSON output (only present fields)
npx parsium-media "filename.mkv" --json

# Full JSON (all fields including false/empty)
npx parsium-media "filename.mkv" --json-full

# Show the step-by-step trace
npx parsium-media "filename.mkv" --explain

# Show what each field was decided on, and what lost
npx parsium-media "filename.mkv" --why

# Add every candidate considered, including the rejected ones
npx parsium-media "filename.mkv" --why full

# Stack the extraction trace onto the same section
npx parsium-media "filename.mkv" --why --explain

# Batch mode from file (one filename per line)
npx parsium-media --file torrents.txt --json --progress

API

parse(filename, options?)

Parse a single filename into structured metadata.

import { parse } from 'parsium-media';

const result = parse(
  "Superman.2025.MULTi.2160p.UHD.BluRay.REMUX.DV.HDR10.HEVC.TrueHD.Atmos.7.1-FRATERNiTY.mkv"
);

result.title           // "Superman"
result.year            // 2025
result.contentType     // "movie"
result.resolution      // "2160p"
result.source          // "UHD BluRay"
result.codec           // "HEVC"
result.hdr             // ["DV", "HDR10"]
result.audio           // ["TrueHD", "Atmos"]
result.channels        // ["7.1"]
result.isMultiLanguage // true
result.isRemux         // true
result.releaseGroup    // "FRATERNiTY"

Options:

| Option | Type | Default | Description | |--------|------|---------|-------------| | explain | boolean \| 'summary' \| 'full' | false | Explain the decision. true means 'summary'; 'full' adds every candidate considered. Also includes the step-by-step explanation trace |

// Why did it decide that?
const result = parse("The.Sopranos.S01E02-46.Long.BDRemux.mkv", { explain: 'summary' });
// `explain` is only present when you asked for it, so narrow before reading it.
result.explain?.fields.episodes    // { value: [2], ranges: [[13, 19]], why: [{ signal: "vocab-exact", weight: 10 }], rejected: [] }
result.explain?.runnerUp           // { seasons: [1], episodeRange: { from: 2, to: 46 } }

// 'full' adds every claim and every rejection the parse ever considered
parse("The.Sopranos.S01E02-46.Long.BDRemux.mkv", { explain: 'full' }).explain;

// The step-by-step trace is still there
parse("Movie.2024.1080p.mkv", { explain: true }).explanation;
// [{ proposer: "resolution", value: "1080p", range: [11, 16], reason: "..." }, ...]

parseBatch(filenames, options?)

Parse multiple filenames with optional progress reporting.

import { parseBatch } from 'parsium-media';

const filenames = ['Movie.2024.2160p.BluRay.x265-GROUP.mkv', '[SubsPlease] Frieren - 28 [1080p].mkv'];

const results = parseBatch(filenames, {
  onProgress: (done, total) => console.log(`${done}/${total}`),
  explain: true,
});

createCachedParser(maxSize?)

Create a parser with LRU cache for repeated parsing of the same filenames.

import { createCachedParser } from 'parsium-media';

const parser = createCachedParser(10000); // max 10K entries
parser.parse("filename.mkv"); // parsed
parser.parse("filename.mkv"); // instant cache hit
parser.size;   // current cache size
parser.clear(); // clear cache

createParser({ plugins })

Create a custom parser whose plugins add proposers, arbitrated against the built-ins by the same evidence rules.

import { createParser } from 'parsium-media';
import type { ParsiumPlugin } from 'parsium-media';

const myPlugin: ParsiumPlugin = {
  name: 'my-plugin',
  // A proposer emits CLAIMS. `priority` is optional: omit it to run after every built-in.
  proposers: [{
    name: 'my-proposer',
    propose(ctx) {
      void ctx;
      return { claims: [], rejections: [] };
    },
  }],
};

const parser = createParser({ plugins: [myPlugin] });
const result = parser.parse("filename.mkv");

See PLUGINS.md for the full plugin API.


Extracted Fields

Identity

| Field | Type | Description | |-------|------|-------------| | title | string | Extracted title (always present) | | altTitle | string | Alternative title (e.g., original language title) | | year | number | Release year | | contentType | 'movie' \| 'series' \| 'unknown' | Auto-detected content type | | contentSubtype | 'anime' | Subtype when anime signals are detected |

Episodes

| Field | Type | Description | |-------|------|-------------| | seasons | number[] | Season numbers (e.g., [5] or [1,2,3] for packs) | | episodes | number[] | Episode numbers (e.g., [2] or [1,2] for multi-ep) | | episodeRange | { from, to } | Episode range (e.g., E01-E12) | | absoluteEpisode | number | Anime absolute episode number | | absoluteEpisodeRange | { from, to } | Anime batch range (e.g., 01~25) | | episodeTitle | string | Episode title if present | | date | string | Date-based episode (YYYY-MM-DD format) | | isSeasonPack | boolean | Full season pack | | isCompleteSeries | boolean | Complete series release | | isBatchRelease | boolean | Batch release (anime) |

Video Quality

| Field | Type | Description | |-------|------|-------------| | resolution | string | 2160p, 1080p, 720p, 480p, etc. | | source | string | BluRay, UHD BluRay, WEB-DL, WEBRip, HDTV, etc. | | codec | string | x264, x265, H.264, H.265, HEVC, AV1, etc. | | bitDepth | string | 10-bit, 8-bit, 12-bit | | frameRate | string | 24fps, 60fps, etc. | | hdr | string[] | Atomic, ordered: HDR10, DV, HDR10+, HLG, SDR (e.g. ["DV","HDR10"]) |

Audio

| Field | Type | Description | |-------|------|-------------| | audio | string[] | Atomic codecs/layers, e.g. ["DD+","Atmos"], ["TrueHD","Atmos"], ["DTS-HD MA"], ["AAC"] | | channels | string[] | All channel layouts found, e.g. ["7.1"], ["7.1","5.1"], ["2.0"] |

Language

| Field | Type | Description | |-------|------|-------------| | languages | Language[] | Detected languages with codes and labels | | isMultiLanguage | boolean | MULTi / multi-language release | | isDualAudio | boolean | Dual audio (DualAudio, DUAL, DL/ML markers) | | subtitleLanguages | Language[] | Detected subtitle languages | | isMultiSubtitle | boolean | Multiple subtitle tracks (Multi Subs, MSub, M-SUB) |

Release Info

| Field | Type | Description | |-------|------|-------------| | releaseGroup | string | Release group name | | editions | string[] | Edition labels (Director's Cut, Extended, Remastered...) | | streamingService | string | Service (Netflix, Amazon, Disney+, etc.) | | isRemux | boolean | REMUX release | | isRepack | boolean | REPACK release | | isProper | boolean | PROPER release | | is3D | boolean | 3D release | | isHybrid | boolean | Hybrid release | | isUpscaled | boolean | AI upscale detected |

File Info

| Field | Type | Description | |-------|------|-------------| | container | string | File container (mkv, mp4, avi) | | raw | string | Original input string | | warnings | string[] | Conditions worth knowing about, as code:detail. See docs/FIELDS.md |


Examples

Movies

parse("Inception.2010.2160p.UHD.BluRay.REMUX.HDR.HEVC.Atmos-GRAIL.mkv");
// title: "Inception", year: 2010, resolution: "2160p", source: "UHD BluRay",
// isRemux: true, hdr: ["HDR"], codec: "HEVC", audio: ["Atmos"]

parse("Le.Comte.de.Monte-Cristo.2024.MULTi.1080p.BluRay.x264-FRATERNiTY");
// title: "Le Comte de Monte-Cristo", year: 2024, isMultiLanguage: true

TV Series

parse("The.Boys.S05E02.REPACK.1080p.WEB.H264-TyHD.mkv");
// title: "The Boys", seasons: [5], episodes: [2], isRepack: true

parse("Malcolm.In.The.Middle.S02E19.1080p.AMZN.WEB-DL.DD+5.1.H.264-ViSiON");
// title: "Malcolm In The Middle", streamingService: "Amazon Prime Video",
// audio: ["DD+"], channels: ["5.1"]

Anime

parse("[SubsPlease] Jujutsu Kaisen 2nd Season - 08 (1080p) [ABC12345].mkv");
// title: "Jujutsu Kaisen", contentType: "series", contentSubtype: "anime",
// seasons: [2], episodes: [8], releaseGroup: "SubsPlease"

parse("[Erai-raws] Oshi no Ko - 01~11 [1080p][Multiple Subtitle]");
// title: "Oshi no Ko", contentType: "series", contentSubtype: "anime",
// absoluteEpisodeRange: { from: 1, to: 11 }, isBatchRelease: true

Multi-Language

parse("Film.2024.MULTi.VFF.1080p.WEB-DL.H265-GroupName");
// title: "Film", isMultiLanguage: true, languages: [{ label: "French" }]

parse("Movie.2024.DUAL.1080p.BluRay.x264-GROUP");
// title: "Movie", isDualAudio: true

Date-Based Episodes

parse("Last.Week.Tonight.2025.01.15.1080p.WEB.H264-GROUPNAME");
// title: "Last Week Tonight", date: "2025-01-15", contentType: "series"

Architecture

Parsium uses a 5-stage pipeline:

Input → Normalize → Structure → Profile → Propose → Select → Identity → Classify → Repair → Result
  1. Normalize: strips noise (tracker prefixes, URLs), normalizes separators, decodes entities
  2. Structure and profile: bracket groups, dash positions and one shared structural picture, computed once
  3. Propose: 14 proposers emit claims, each carrying the evidence for its reading
  4. Select: overlapping claims are solved together. The better-evidenced reading wins, and the loser is recorded rather than thrown away
  5. Identity: title, altTitle and episodeTitle, by elimination
  6. Classify the content type, then apply conservative repairs

A proposer does not consume text, it proposes. A claim commits when its support outweighs the evidence that its span is ordinary text, so a losing reading leaves its text available instead of eating it. The title is what no other field claimed, which is what lets Parsium handle a filename that does not start with its title.

A central field registry (src/field-registry.ts) defines all output fields in one place: their types, categories, display behavior, JSON serialization mode, and default values. The CLI, pipeline assembly, and comparison tools are all driven from this registry, so adding a new field requires only a single entry.

Proposer priority order

Priority is a prior, not ownership: resolution claiming 1080p before season-episode runs is what stops s01e02-1080p being read as a 1080-episode range.

| Priority | Proposer | What it detects | |----------|----------|-----------------| | 1 | Resolution | 2160p, 1080p, 720p, 4K | | 2 | Source | BluRay, WEB-DL, HDTV | | 3 | Codec / Date / FPS | x265, H.264 / 2025.01.15 / 24fps | | 4 | Audio | TrueHD, DTS-HD MA, DD+ | | 5 | HDR | DV, HDR10, HDR10+ | | 6 | Language | 80+ langs, scene markers | | 7 | Year | Position-aware scoring | | 8 | Edition | Director's Cut, Extended | | 9 | Misc | REPACK, PROPER, streaming services | | 10 | Season/Episode | S01E02, packs, ranges | | 11 | Anime Episode | Absolute eps, batches | | 12 | Release Group | Trailing dash, fansub brackets |

There is no title proposer. Identity is a pipeline stage that runs after selection, over whatever text no claim owns.


Requirements

  • Node.js 22+ (or Bun/Deno)
  • No native dependencies

HTTP API

Parsium also provides an HTTP API for remote parsing. See the API README for endpoints, deployment, and configuration.

Live API demo: https://api.parsium.nepiraw.com

cd api && pnpm install && pnpm dev
# → Parsium API listening on http://localhost:7002
curl -X POST http://localhost:7002/v1/parse \
  -H "Content-Type: application/json" \
  -d '{"filename": "Movie.2024.1080p.BluRay.x264-GROUP.mkv"}'

Endpoints: POST /v1/parse, POST /v1/parse/batch, GET /v1/health, GET /v1/version, GET /v1/openapi.json

Options: full, explain (passed via options object in request body)

Deployment: Docker, Vercel, or any Node.js host.