Scan pipeline

How VinylCat finds a record

You hold a record up to the camera. About two seconds later the app says the artist, title, year and format out loud, and the record is in the library. This page walks through the six layers that make that happen, what each one costs, and the exact words sent to the vision model.

1 · Camera watch steady 1 s → one still 2 · Local read barcode · OCR · zoom 3 · Vision model 13 fields · ~$0.001 clean barcode: skip the model 4 · Discogs up to 6 queries 5 · Rank 0.85 match · 0.60 ask 6 · Outcome save · ask · park free · on device the only paid step the only network step besides the model
Every scan runs left to right. Only one stage costs money and only one talks to Discogs. A clean barcode lets the run skip the paid stage entirely.

Two ideas underpin the whole design. First, the catalogue number does most of the work. A label that says "POSP 113" pins a release far more precisely than the artist and title do, so every layer is tuned to find and preserve that string. Second, do the free thing first. Apple's on-device recognition runs before anything is sent to a paid model, and a good barcode means nothing is sent at all.

1

Camera watch

free · on device · about one second of steady frames

Nothing is searched yet. This layer's only job is to notice that a record is being held still and sharp in front of the camera, and to take exactly one photo of it. It never fires while you are moving the record, and it never fires twice on the same record.

What it measures

Every frame is shrunk to a small greyscale thumbnail of the central region only, so hands at the edges do not count. Four numbers are computed per frame, each smoothed over the last five so a single flicker cannot flip the state.

SignalHowDefault
SharpnessVariance of an edge filter. Blur flattens edges.≥ 60
MotionMean pixel difference against the previous frame.≤ 6
PresenceMean difference against the memorised empty scene.≥ 14
Fingerprint64-bit perceptual hash. "Is this the same item I just shot?"≤ 10 bits

The SHARP, STILL and RECORD bars in the app's Settings are these three thresholds, live, so you can see why a capture isn't firing.

Details that matter

  • Hold credit, not a timer. Good frames add time to a one-second bucket; bad frames drain it at a third of the rate. Once a hold has started the thresholds relax, so the ring doesn't strobe.
  • Drift check. A record sliding past looks "still" frame to frame, so the engine also compares against the frame the hold started from.
  • Two modes. Identifying a new record waits for the record to be removed before re-arming. An extra shot of a matched record demands a burst of motion first, so a flip is required before the next capture.
  • Hand-held iPhone. The motion and drift limits were raised so a Continuity-Camera iPhone in the hand can ever fill the ring.
  • Manual mode. Switch CAPTURE off and only SPACE or "capture" takes a shot.
2

Local read

free · Apple Vision · 0.65 s per photo

The still goes first to Apple's on-device Vision framework. This costs nothing, works offline, and takes well under a second. It produces three things: any barcodes, every line of printed text it can see, and a shortlist of tokens that look like catalogue numbers.

Three orientations, then a zoom pass

Labels are round and held at any angle, so the text pass runs three ways up. Then the smallest print is cropped, magnified two to four times, and read again. That zoom pass took "catalogue number somewhere in the candidates" from 84% to 91% of photos, at the cost of doubling the time.

The catalogue-number sieve

Every OCR line is upper-cased and run through one loose pattern: up to two short letter or digit groups, then a run of digits. That net catches "CHS TT 17", "2059 227" and "SRK 6095", but also years and speeds, so the filters do the real work:

  • Drop bare four-digit numbers, "45" and "33". Years and RPM.
  • Drop "A1", "B2" style side markers.
  • De-duplicate ignoring spaces.
  • Score by shape: letters first, then longer strings. Every rule in that scorer was earned by a photo the eval got wrong.

Details that matter

  • OCR autocorrect is off. Apple's language correction would "fix" catalogue numbers into words. The raw glyphs are what's wanted.
  • Comparison form. Whenever two catalogue numbers are compared, both are reduced to upper-case letters and digits, so "POSP-113", "POSP 113" and "posp113" all agree.
  • Barcode shortcut. A barcode of eight digits or more skips the model entirely. A stray Code 39 fragment does not trigger it.
  • No API key at all? The app still works, badly. It guesses "is this a label?" from words like RPM, SIDE A, MADE IN, picks the first long lines as label, title and artist, and lets the catalogue number carry the search. The HUD nudges you to add a key.
  • The whole shortlist goes on to the Discogs layer, not just the winner. Labels print matrix and side suffixes too, and the first-ranked token is not always the one Discogs uses.
3

Vision model

your key · GPT-5.6 Luna or Gemini 3.8 Flash · about $0.001 and 4 s per photo

If there was no usable barcode, the photo goes to a multimodal model with everything the local pass found attached as hints. The model is not asked to identify the release. It is asked to read the sleeve or label into thirteen fixed fields, and it is forced to answer in exactly that shape.

The exact prompts

Both providers get identical wording. The system prompt sets the job and the house rules. The user prompt is assembled per scan from the hints.

System prompt · sent verbatim
You identify vinyl records from a photo of a sleeve (cover) or the paper label in the centre of the disc. Extract only what is visible or can be inferred with high confidence. Catalogue numbers matter most: copy them exactly as printed (e.g. "POSP 113", "FAC 73", "7N 17241"). Do not guess a year unless printed. Set isLabelShot true when the photo shows the round centre label. formatGuess: "7in" for a small single label or 45 RPM marking, "12in" for a 45/33 RPM maxi single, "LP" for an album, "10in" for a ten-inch. confidence is your certainty that artist and title are right (0–1). Return JSON matching the schema; use null for unknown fields.
User prompt · assembled per scan; the hint lines appear only when that hint exists
Read this record photo. The operator is currently scanning seven inch. This should be the centre label. Barcode detected locally: 5012394112345.
Local OCR lines (may contain errors):
POLYDOR
BLUE MONDAY
POSP 113
45 RPM
SIDE A

"The operator is currently scanning seven inch" comes from the format button or the voice commands. "This should be the centre label" is added after the app has asked you for the label. The OCR block is a deliberate hint: the model can read the photo itself, but a second opinion on hard-to-read catalogue numbers helps, and "may contain errors" stops it copying OCR mistakes blindly.

The answer shape

The model must return every one of these keys, null when unknown. Anything else is rejected before the app sees it.

FieldMeaning
artist, titleAs printed
labelRecord company
catalogNumberExactly as printed. The most important field.
otherNumbersAnything else that looks like one
barcodeDigits if visible
year, countryOnly if printed
formatGuess7in · 10in · 12in · LP
isLabelShotTrue for the round centre label
sideA / B / 1 / 2 if visible
visibleTracksTrack titles it can see
confidence0 to 1 for artist and title only
notesFree text

Details that matter

  • Image size. The still is downscaled to 1024 px on the long side and JPEG-compressed before upload, plus the zoomed close-ups from the local pass. That caps the per-image token cost.
  • Strict output. OpenAI gets a JSON schema in strict mode; Google gets a response schema at low temperature. The reply cap is 2,500 tokens, because reasoning tokens count against it and a smaller cap cut the JSON off mid-field.
  • Cost meter. Every reply's token counts are priced and added to the session total in the HUD's LLM COST cell.
  • A model failure never aborts a scan. Timeout, bad key, malformed reply: the app carries on OCR-only and records the error against the scan.
  • When OCR and the model disagree, the model wins. In the eval it gets the catalogue number right first time on 88% of photos against OCR's 49%.
4

Discogs search

your token · 60 requests a minute · one to six calls per scan

Discogs is the only source of truth for what a release actually is. The app never asks Discogs an open question if it can ask a precise one, so it works down a ladder from the strongest key to the weakest, collecting hits into one pool and stopping the moment a precise key lands.

The ladder

RungQueryFires when
1barcodea barcode was read
2label + catno + formatthe model read a label and a number
3catno alonerung 2 came back empty
4free text = the catalogue tokenno catno hit at all
5artist + release title + formatthe precise keys found nothing
6free text = "artist title"last resort

Every OCR candidate gets its own rung-3 attempt, so a mis-ranked token still gets searched.

A worked example

The model reads a Polydor 7" label as New Order, Blue Monday, Polydor, POSP 113. OCR also produced POSP 113. No barcode. Session format is 7". The first call that returns hits ends the run:

Rung 2 · label + catno + format
/database/search?type=release&per_page=15
  &label=Polydor&catno=POSP%20113&format=7%22
Rung 3 · only if rung 2 was empty
/database/search?type=release&per_page=15&catno=POSP%20113
  • Rate limit is read from Discogs' own headers and kept at 60 a minute with headroom. Valuation after a Collection pull runs one call every 2.2 s so scanning keeps most of the budget.
  • No token means the public limit of 25 a minute and no writes to your Collection.
5

Rank & decide

free · instant · two thresholds and a safety net

Discogs returns hits, not answers. Each one is scored from 0 to 1 against what was read off the record, look-alike pressings are folded together, and then a simple set of gates decides whether to accept the top hit, ask you to choose, or admit defeat.

Scoring weights

Points are added per hit and clamped to 0 to 1. The reasons are kept and shown in the HUD's meta line, for example 0.94 · r2413807 · catno title format.

EvidenceTestPoints
Catalogue number, exactLetters and digits identical after normalising+0.50
Catalogue number, nearOne contains or ends with the other+0.30
BarcodeDigits identical+0.50
LabelName similarity above 0.85+0.15
Artist / titleSimilarity above 0.60, scaledup to +0.25
FormatMatches the session or the model's guess+0.10
FormatContradicts it−0.15
YearWithin a year of a printed year+0.05
Home countryUK for GBP, US for USD, and so on+0.03

An exact catalogue number plus a good title plus the right format lands around 0.85 on its own. A barcode plus title does the same. Artist and title alone can never pass 0.85, which is the point: names alone cannot tell pressings apart.

Details that matter

  • Text similarity is Jaro–Winkler on lower-cased, punctuation-stripped strings with a leading "The" removed. It rewards shared prefixes, which suits titles.
  • Swapped artist and title are tolerated: the ranker tries the whole "Artist – Title" string and each part against each part, and takes the best.
  • "Strong" means an exact catalogue number or barcode and a title match. A strong hit is accepted even below 0.85. This is the safety net for scores dented by a wrong format guess.
  • Look-alikes. Two Discogs entries with the same label, catalogue number, country and year cannot be told apart from a photo, so they are folded into one before deciding. Without this, every reissue would trigger a "which one?" question.
  • Ambiguous lists every distinct pressing within 0.25 of the top score, three to nine of them, each as headline plus label, catno, country, year and format. Keys 1 to 9 or the spoken number pick one.
6

What happens next

spoken · chimed · saved with its evidence

The verdict drives the HUD, the voice and the camera mode. There are three lanes, and two of them loop back to the camera for another photo of the same record before anything is given up on.

Match lane

  • Duplicate check is by Discogs release id. A second copy is allowed, but only if you say "add anyway".
  • What gets saved comes from the full Discogs release, not the search hit: artist line, title, label, catalogue number, format, year, country, genres, styles, tracklist, cover image. Alongside it, the photo, the OCR lines, the model's reading, every ranked candidate and the score. That is the evidence Review and the thumbs-down verdict work from.
  • One photo per record, the identifying one. Extra shots are only ever asked for by hand: "flip", "back cover", "gatefold", "disc two", and the record closes again after each one.
  • Spoken confirmation: "The Jam, Going Underground, 1980, seven inch." Then the next record.
  • Discogs sync, if you switched it on, adds the release to your Collection in the background and fetches a value in your currency.

Ambiguous and none lanes

  • The camera is frozen while a question is on screen, so the next steady frame cannot wipe the list. SPACE and "capture" still work.
  • Second look. Flip the record while the list is up and the new read runs the whole pipeline again; if exactly one candidate survives both reads, it is accepted and both photos are kept.
  • Label first. When a cover shot fails to match, the app assumes the label will do better, switches the camera to shot mode and tells the model to expect a label. Only if that also fails does it offer one retry, then park the record.
  • Parked means the Review tab, with everything read so far. Type a few extra words: a catalogue number in them is pulled out and searched exactly, the rest goes in as free text, and the same ranker orders the results.
  • Errors park too. Discogs down, network gone: the photo goes to Review with a plain-words note, and nothing is lost.
Every knob in one place

What you can turn

LayerSettingDefaultWhere
1Sharpness needed60Settings → Capture
1Steady for1.0 sSettings → Capture
1Scene change needed14 of 255Settings → Capture
1Auto captureonCAPTURE switch in the top bar · "pause" / "resume"
2Skip the model when a barcode of 8+ digits readsonSettings → Identification
3ProviderGPT-5.6 LunaSettings → Identification
4Session format7" singlesFORMAT in the top bar · "forty-fives" / "twelve inch" / "albums"
5Accept / ask thresholds0.85 / 0.60fixed
5Preferred countryfrom your Discogs currencySettings → Discogs
6Add matches to my CollectionoffWizard's Discogs step · Settings → Discogs
6Read out results · Listen from launchon · offSettings → Voice
VinylCat app icon

VinylCat 0.1.0

macOS 15 or later · Apple silicon · DMG · free · read the install steps first
Download the DMG