Documentation
¶
Overview ¶
Package hash parses and normalizes the two Stash fingerprints moansubs keys releases on — oshash and phash — and implements the multi-index hashing (MIH) block extraction the bucketed lookup API is built on. See PLAN.md's "Data model" and "Lookup: bucketed by default" sections; the bit ranges and hash-handling rules here are a fixed API contract, not an implementation detail.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func EndpointHash ¶
EndpointHash returns the first 12 hex characters of sha256(normalized) — the lookup key GET /api/v1/lookup/stash/{ehash}/{stash_id} and the batch endpoint's stash_ids entries use in place of the endpoint URL itself (WP-C9a spec: "keeps URLs out of paths and the wire shape stable"). normalized must already be NormalizeStashEndpoint's output — this function does not normalize on its own, so two spellings of the same endpoint must be normalized identically before they reach here.
func Hamming ¶
Hamming returns the Hamming distance between two phashes: the number of differing bits, via popcount(a^b).
func NormalizeStashEndpoint ¶
NormalizeStashEndpoint normalizes a stash-box GraphQL endpoint URL (e.g. "https://stashdb.org/graphql") to a canonical form: trimmed, scheme and host lowercased, path kept as-is (WP-C9a spec: "trim spaces, lowercase host, keep path"). The scheme is lowercased too — not called out explicitly by the spec, but required for "HTTPS://StashDB.org/graphql" and "https://stashdb.org/graphql" to normalize identically, which the spec's own worked example demands.
Client and server both call this before EndpointHash, so the two ends always agree on which ehash a given endpoint hashes to — this is the single source of truth for that normalization, same role internal/hash plays for oshash/phash.
func ParseStashID ¶
ParseStashID lowercases s and validates it is a 36-character UUID shape (WP-C9a spec), rejecting anything else. Shared by the server (upload validation, batch lookup) and the plugin (building a lookup/upload request), so both ends reject the same malformed ids the same way.
Types ¶
type Blocks ¶
type Blocks [5]uint16
Blocks holds the 5 multi-index-hashing (MIH) block values used for bucketed phash lookup.
type OSHash ¶
type OSHash string
OSHash is Stash's oshash: the OpenSubtitles moviehash algorithm's output, always a 16-character zero-padded lowercase hex string. Unlike phash, oshash's %016x formatting is already zero-padded at the source (Stash), so ParseOSHash only needs to validate, not pad.
func ComputeOSHash ¶
ComputeOSHash implements the OpenSubtitles moviehash algorithm exactly as Stash's pkg/hash/oshash computes it (ported from that package's FromReader): file size plus the little-endian uint64 sum of 8-byte words in the first and last chunk of the file, formatted as %016x. Ported to an io.ReaderAt+size signature rather than io.ReadSeeker so callers (e.g. an os.File or an in-memory fixture in tests) don't need seek semantics.
For files smaller than 2*chunkSize, Stash does NOT error like the original OpenSubtitles reference implementation — chunkSize shrinks to the largest multiple of 8 not exceeding the file size, so the head and tail reads overlap (files under 64KiB may double-count almost every byte). Files of 8 bytes or fewer are rejected: there is no room for even one 8-byte word.
func ParseOSHash ¶
ParseOSHash normalizes s to lowercase and validates it is exactly 16 hex characters, rejecting anything else (short/long strings, non-hex characters). Stash always emits this format, but inputs may arrive from elsewhere (e.g. a client's lookup request), so case is normalized before the strict length/charset check.
func (OSHash) BucketPrefix ¶
BucketPrefix returns the first 5 hex characters — the oshash lookup bucket key fixed by PLAN.md's "Lookup: bucketed by default" as an API contract between client and server.
type PHash ¶
type PHash uint64
PHash is Stash's 64-bit perceptual hash (goimagehash PerceptionHash over a 5x5 sprite, see PLAN.md Reference), held as the bit pattern of an unsigned 64-bit integer. Never compare two PHash values as strings — Stash's own string form is unpadded (see ParsePHash) — always compare via Hamming or Blocks.
func PHashFromBigint ¶
PHashFromBigint reverses ToBigint: reinterprets a signed bigint's bit pattern back to the unsigned uint64 phash.
func ParsePHash ¶
ParsePHash parses phash the way Stash's GraphQL API actually emits it: strconv.FormatUint(v, 16) with NO left-padding, so a hash with leading zero bits arrives as fewer than 16 hex characters (PLAN.md hash rule 1). Accepts 1-16 hex characters; callers get the zero-padded canonical form back from String.
func (PHash) Blocks ¶
Blocks extracts the 5 MIH blocks by shift-and-mask. By pigeonhole, any two phashes within Hamming distance 4 must match exactly in at least one of these 5 blocks — that property is what makes bucketed lookup exact rather than approximate (see the MIH property test in phash_test.go).