jhin

package module
v0.4.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 6, 2026 License: MIT Imports: 2 Imported by: 0

README

CI Go Reference License

Jhin

All-in-one Go library for torrent release names: parsing, ranking, filtering, and sorting — built for high accuracy and high throughput, with zero runtime dependencies.

Jhin is the Go successor to PTT and rank-torrent-name, unified into one library:

  • jhin/parser extracts 46 metadata fields from a release name in ~55µs. Accuracy is contract-tested: a 1,156-title golden corpus verifies byte-identical output against the Python PTT parser.
  • jhin/rank scores, filters, and sorts releases against a declarative user profile — per-attribute policies, regex gates, resolution and language rules — evaluated in parallel.

Install

go get github.com/dreulavelle/jhin

Quick start: parsing

package main

import (
	"fmt"

	"github.com/dreulavelle/jhin"
)

func main() {
	r := jhin.Parse("The.Witcher.S01-S03.COMPLETE.2160p.NF.WEB-DL.DDP5.1.Atmos.DV.HDR.HEVC.10bit.MULTi-Kitsune[TGx]")

	fmt.Println(r.Title)      // "The Witcher"
	fmt.Println(r.Seasons)    // [1 2 3]
	fmt.Println(r.Resolution) // "2160p"
	fmt.Println(r.Quality)    // "WEB-DL"
	fmt.Println(r.HDR)        // ["DV" "HDR"]
	fmt.Println(r.Audio)      // ["Atmos" "Dolby Digital Plus"]
	fmt.Println(r.Network)    // "Netflix"
	fmt.Println(r.Group)      // "Kitsune"
}

Result has 46 fields (see the reference below) and marshals straight to JSON. Useful extras:

r.Normalize()                 // canonical forms: 2160p→4k, codec avc→AVC, ...
r.LanguageNames()             // ["fr"] → ["French"]

parser.ParseAll(titles)       // parallel batch, index-aligned with input
parser.ExtractSeasons(title)  // just the season numbers
parser.GetPartialParser([]string{"resolution", "year"}) // few-field fast path

Quick start: ranking, filtering & sorting

The rank package answers three questions for every release: how good is it (rank), am I allowed to grab it (fetch + rejection reasons), and in what order to output them (sort) — releases are bucketed by resolution with higher resolutions on top, ranked descending within each bucket.

package main

import (
	"fmt"

	"github.com/dreulavelle/jhin/rank"
)

func main() {
	ranker, err := rank.New(rank.Default())
	if err != nil {
		panic(err)
	}

	titles := []string{
		"Movie.2020.2160p.BluRay.REMUX.DV.TrueHD.7.1-GRP",
		"Movie.2020.1080p.WEB-DL.DDP5.1.H.264-GRP",
		"Movie.2020.CAM.x264-TRASH",
	}

	// Index-aligned with the input — nothing is reordered or dropped.
	// (Use rank.Entry with ranker.RankEntries to carry infohashes along.)
	torrents := ranker.RankAll(titles)
	for _, t := range torrents {
		fmt.Printf("%6d fetch=%-5v %v %s\n", t.Rank, t.Fetch, t.Rejections, t.Raw)
	}

	// Sorting is separate and explicit: resolution bucket, then rank by
	// default — or compose your own chain via SortOptions.Criteria.
	best := ranker.Sort(torrents, rank.SortOptions{
		FetchableOnly: true,
		BucketLimit:   5, // top 5 per resolution bucket
	})
	fmt.Println("best:", best[0].Raw)
}
Profiles

Everything tunable lives in one declarative, JSON-serializable Profile:

p := rank.Default()

// Per-attribute policy: may it be fetched, and what is it worth?
p.Attributes = map[rank.Attr]rank.Policy{
	rank.AttrRemux:       {Fetch: true, Rank: 25000},
	rank.AttrDolbyVision: {Fetch: false},           // veto DV entirely
}

// Regex gates against the raw title ("/pat/" = case-sensitive).
// Require is conjunctive — every pattern must match, so use alternation
// within one pattern for "any of these".
p.Require = []string{`\b(2160p|1080p)\b`}
p.Exclude = []string{`\bHDCAM\b`}   // any match rejects
p.Preferred = []string{`\bIMAX\b`} // matching adds Options.PreferredBonus

// Resolution and language rules. Default enables 4K/1440p/1080p/720p;
// disable what you don't want, or reorder preference without banning:
p.Resolutions[rank.Res2160p] = false
p.ResolutionOrder = []rank.Resolution{rank.Res1080p, rank.Res2160p, rank.Res720p}
p.Languages.Exclude = []string{"ru"}       // codes or groups: anime/common/all
p.Languages.Preferred = []string{"en"}

// Weighted keywords: additive scores without vetoes.
p.PatternRanks = []rank.PatternRank{
	{Pattern: `\bIMAX\b`, Rank: 500},
	{Pattern: `\bHDCAM\b`, Rank: -2000},
}

p.Save("profile.json")                      // and rank.Load("profile.json")

ranker, _ := rank.New(p)
Pinning to a specific movie or show
t := ranker.Rank(raw, rank.RankOptions{
	TargetTitle: "The Matrix",
	Aliases:     []string{"Matrix"},
})
// t.TitleRatio holds the similarity; below Options.TitleThreshold (0.85)
// the release is rejected with "title_mismatch".

Standalone helpers: rank.TitleMatch(a, b, threshold, aliases...), rank.Similarity(a, b), rank.Normalize(title). For debugging a score, ranker.Explain(&torrent) returns the per-clause breakdown — every point traces to an attribute, pattern, or preference in the profile.

CLI

go install github.com/dreulavelle/jhin/cmd/jhin@latest

jhin parse --pretty "The.Matrix.1999.1080p.BluRay.x264"   # parse one or more titles
jhin rank --target "The Matrix" < titles.txt              # rank/filter/sort a list
jhin version                                              # installed version

Performance

Benchmarked on a Ryzen 9 5900HX (see docs/benchmark.md):

Operation Time Notes
Parse (simple title) ~35µs 58 allocs
Parse (corpus mean, 1,156 mixed titles) ~55µs easy and hostile titles alike
Batch parse ~14µs/title ParseAll across 8 threads — 100k titles in ~1.4s

How it stays fast: the parser is an ordered table of ~430 regex handlers, but most never run. At startup, every handler's pattern is analyzed to find substrings it cannot match without — down to two-character sets like s0s9 for S01-style patterns — and all of them are compiled into a single Aho-Corasick automaton. One scan per title then decides which handlers could possibly match, cutting ~430 potential regex executions to about 42. The same reasoning guards title cleanup, where a byte-level check replaces each cleanup regex that provably cannot match. Neither can ever do more than skip work: equivalence is enforced by tests and continuous fuzzing.

Accuracy

parser/testdata/golden.json pins the expected output for 1,156 real-world release names across every field — the corpus was generated by the original Python PTT parser and jhin reproduces it byte-for-byte. Any behavioral regression fails CI.

How it compares

Measured 2026-07-25 on the 1,156-title corpus; full methodology, disclosure, and reproduction steps in docs/benchmark.md. Accuracy is scored per field, only on the 9 fields every library claims, after neutral vocabulary normalization.

Library Accuracy (9 shared fields) Speed (per title, serial) Fields
jhin (Go) 100% ¹ 55µs (14µs batched) 46
ProfChaos/torrent-name-parser (Go) 78.8% 60µs 28
middelink/go-parse-torrent-name (Go, unmaintained) 70.1% 38µs 22
razsteinmetz/go-ptn (Go) 67.4% 43µs 26
parse-torrent-title (JS) not scored ² 17µs ~20
PTT (Python) 100% ¹ 576µs 46
guessit (Python) not scored ² 4,298µs ~30

¹ The gold labels are generated by Python PTT, and jhin is contract-tested byte-identical to it — so both score 100% by construction. The table's real content is the other columns and the other rows.

² Different output schema; there is no honest cross-vocabulary accuracy score without a shared gold standard, so these are compared on speed only.

The lighter Go parsers run ~30 regexes filling ~20 scalar fields; jhin runs 432 handlers behind an Aho-Corasick prefilter to extract 46 fields (multi-season packs, episode ranges, 60+ languages, HDR, editions, trash detection) — and still lands within ~1.4x of the fastest of them, which fills less than half as many fields. The accuracy column is what the remaining microseconds buy.

Result Reference

Field semantics match PTT 1.8.5 (commit 88429bb) exactly, verified by the golden corpus.

  • Adult (bool): adult-content detection (keyword list)
  • Audio ([]string): DTS Lossless, DTS Lossy, Atmos, TrueHD, FLAC, Dolby Digital Plus, Dolby Digital, AAC, PCM, OPUS, MP3, HQ Clean Audio
  • BitDepth (string): 8bit, 10bit, 12bit
  • Bitrate (string): e.g. 448kbps
  • Channels ([]string): 2.0, 5.1, 7.1, stereo, mono
  • Codec (string): avc, hevc, av1, xvid, mpeg (normalized: AVC, HEVC, ...)
  • Commentary / Complete / Convert / Documentary / Dubbed (bool)
  • Container (string): mkv, avi, mp4, ...
  • Country (string): US, UK, AU, NZ, CA
  • Date (string): YYYY-MM-DD
  • Edition (string): Anniversary Edition, Director's Cut, Extended Edition, IMAX, ...
  • EpisodeCode (string): 8-char CRC code
  • Episodes / Seasons / Volumes ([]int)
  • Extension (string): file extension
  • Extras ([]string): Featurette, Sample, Trailer, NCED, NCOP, ...
  • Group (string): release group
  • HDR ([]string): DV, HDR10+, HDR, SDR
  • Hardcoded (bool)
  • Languages ([]string): ISO 639-1 codes (en, ja, zh, ...) plus multi subs, multi audio, dual audio
  • Network (string): Netflix, Amazon, HBO, ...
  • PPV / Proper / Remastered / Repack / Retail (bool)
  • Quality (string): WEB, WEB-DL, WEBRip, BluRay, BluRay REMUX, HDTV, CAM, TeleSync, DVDRip, ...
  • Region (string): R0-R9
  • Resolution (string): 2160p, 1440p, 1080p, 720p, 480p, ... (Normalize() maps to 4k/2k)
  • Scene (bool): scene-release detection
  • Site (string): source website
  • Size (string): e.g. 2.3GB
  • Subbed (bool)
  • ThreeD (bool): 3D release
  • Title (string): cleaned title
  • Torrent / Trash / Uncensored / Unrated / Upscaled (bool)
  • Year (string): YYYY or YYYY-YYYY

Acknowledgements

License

Licensed under the MIT License. Check the LICENSE file for details.

Documentation

Overview

Package jhin is an all-in-one library for working with torrent release names: parsing metadata out of raw titles, then ranking, filtering, and sorting releases against a user profile.

The root package is a thin facade over the subpackages so that the common case stays a one-liner:

result := jhin.Parse("Deadpool 2016 1080p BluRay x264 DTS-JYK")

For ranking, filtering, and sorting, use the rank subpackage:

ranker, _ := rank.New(rank.Default())
torrents := ranker.RankAll(titles)          // index-aligned with input
best := rank.Sort(torrents, rank.SortOptions{FetchableOnly: true})

Subpackages:

  • github.com/dreulavelle/jhin/parser — the title parsing engine
  • github.com/dreulavelle/jhin/rank — ranking, filtering, and sorting
Example (Ranking)
package main

import (
	"fmt"

	"github.com/dreulavelle/jhin/rank"
)

func main() {
	ranker, _ := rank.New(rank.Default())

	torrents := ranker.RankAll([]string{
		"Movie.2020.1080p.BluRay.REMUX.AVC.TrueHD.7.1-GRP",
		"Movie.2020.1080p.WEB-DL.DDP5.1.H.264-GRP",
		"Movie.2020.HDCAM.x264-TRASH",
	})

	best := rank.Sort(torrents, rank.SortOptions{FetchableOnly: true})
	for _, t := range best {
		fmt.Println(t.Raw)
	}
}
Output:
Movie.2020.1080p.BluRay.REMUX.AVC.TrueHD.7.1-GRP
Movie.2020.1080p.WEB-DL.DDP5.1.H.264-GRP

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func GetPartialParser

func GetPartialParser(fieldNames []string) func(title string) *Result

GetPartialParser returns a parse function that only runs the handlers for the given field names. Useful when only a few fields are needed and throughput matters.

Example
package main

import (
	"fmt"

	"github.com/dreulavelle/jhin"
)

func main() {
	parse := jhin.GetPartialParser([]string{"resolution", "year"})
	r := parse("The.Matrix.1999.1080p.BluRay.x264")
	fmt.Println(r.Resolution, r.Year)
}
Output:
1080p 1999

func Version

func Version() ver

Types

type Result

type Result = parser.Result

Result holds all metadata extracted from a torrent title. It is an alias of parser.Result.

func Parse

func Parse(title string) *Result

Parse extracts metadata from a torrent title.

Example
package main

import (
	"fmt"

	"github.com/dreulavelle/jhin"
)

func main() {
	r := jhin.Parse("The.Walking.Dead.S05E03.720p.HDTV.x264-ASAP[ettv]")
	fmt.Println(r.Title, r.Seasons, r.Episodes, r.Resolution, r.Quality, r.Group)
}
Output:
The Walking Dead [5] [3] 720p HDTV ASAP

Directories

Path Synopsis
cmd
jhin command
Package parser extracts release metadata from torrent names using an ordered handler table with a literal prefilter.
Package parser extracts release metadata from torrent names using an ordered handler table with a literal prefilter.
Package rank scores, filters, and sorts torrent releases.
Package rank scores, filters, and sorts torrent releases.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL