comicbox.formats.base.online.cover_hash

source module comicbox.formats.base.online.cover_hash

Cover-hash primitives and the matcher’s hashing-invocation policy.

pHash (8x8 = 64 bits), computed in pure Python bit for bit the way imagehash.phash computes it, because Metron’s precomputed hashes (Issue.cover_hash, returned by Mokkari) come from imagehash and are string-compared against ours. ComicVine and GCD candidates require downloading the cover image — that’s M6’s concern.

The matcher invocation policy decides when hashing runs:

  • Skip when the top metadata score is unambiguous (clears confidence_threshold AND well-separated from runner-up).
  • Hash top K candidates when uncertain or close-call.
  • Skip when nothing clears min_confidence (hashing won’t save it).

Classes

  • CoverHashUrlCache — Tiny SQLite cache mapping cover URLs to their pHash strings.

  • CoverFetchPool — Downloads cover images concurrently and returns their pHashes.

Functions

  • phash_int — Return the 64-bit pHash of an image, as imagehash.phash computes it.

  • compute_phash — Return the pHash of an image as a 16-digit lowercase hex string.

  • parse_hash — Parse a hex-encoded pHash string into its 64-bit integer.

  • hamming_distance — Hamming distance between two hex-encoded pHash strings.

  • cover_score — Convert a Hamming distance into a [0, 1] similarity score.

source phash_int(image: PILImage) → int

Return the 64-bit pHash of an image, as imagehash.phash computes it.

source compute_phash(image_bytes: bytes) → str

Return the pHash of an image as a 16-digit lowercase hex string.

source parse_hash(hex_str: str) → int

Parse a hex-encoded pHash string into its 64-bit integer.

Raises

  • ValueError

source hamming_distance(a: str, b: str) → int

Hamming distance between two hex-encoded pHash strings.

source cover_score(local_hash: str, candidate_hash: str) → float

Convert a Hamming distance into a [0, 1] similarity score.

s_cover = 1 - (hamming / 64). Clamped to [0, 1] for safety.

source class CoverHashUrlCache(db_path: Any)

Tiny SQLite cache mapping cover URLs to their pHash strings.

Holds ONE connection for its lifetime rather than reconnecting per call. The matcher hashes up to 15 candidates per ambiguous comic, so the old reconnect-per-get/set cost two fresh connections per candidate — pure overhead on the hot path. check_same_thread=False plus _lock keeps that single connection safe for the cover-fetch pool’s workers and for -j N boxes sharing one cache object.

Open / create the sqlite cache file at db_path.

Methods

  • get — Return the cached pHash for a cover URL, or None if absent.

  • get_many — Return the cached pHash for every URL that has one.

  • set — Store a pHash for a cover URL, overwriting any previous value.

  • set_many — Store many URL → pHash mappings in one transaction.

  • close — Close the connection. Safe to call more than once.

source method CoverHashUrlCache.get(url: str) → str | None

Return the cached pHash for a cover URL, or None if absent.

source method CoverHashUrlCache.get_many(urls: Sequence[str]) → dict[str, str]

Return the cached pHash for every URL that has one.

One query per chunk instead of one connection per URL — the batch cover-fetch path resolves a whole top-K set in a single round-trip against the cache before any download starts.

source method CoverHashUrlCache.set(url: str, phash: str) → None

Store a pHash for a cover URL, overwriting any previous value.

source method CoverHashUrlCache.set_many(pairs: Iterable[tuple[str, str]]) → None

Store many URL → pHash mappings in one transaction.

source method CoverHashUrlCache.close() → None

Close the connection. Safe to call more than once.

source class CoverFetchPool(max_workers: int = MAX_COVER_FETCH_WORKERS)

Downloads cover images concurrently and returns their pHashes.

Owns ONE httpx.Client for its lifetime, so a top-K batch reuses pooled connections instead of paying a TCP+TLS handshake per candidate. Failures are logged and dropped exactly as the serial path did — a cover that won’t download is a missing signal, never a failed lookup.

Record the worker ceiling; the HTTP client is built on first use.

Methods

  • fetch_hash — Download one cover and return its pHash, or None on any failure.

  • fetch_hashes — Download many covers concurrently; return {url: phash} for the wins.

  • close — Close the HTTP client. Safe to call more than once.

source method CoverFetchPool.fetch_hash(url: str) → str | None

Download one cover and return its pHash, or None on any failure.

source method CoverFetchPool.fetch_hashes(urls: Sequence[str]) → dict[str, str]

Download many covers concurrently; return {url: phash} for the wins.

URLs that fail to download or hash are simply absent from the result. Order is irrelevant — the caller maps back by URL.

source method CoverFetchPool.close() → None

Close the HTTP client. Safe to call more than once.