Methodology & Accuracy

Every tool on ThumbnailPro is powered by small, tested functions, and every claim in the site's copy should be checkable. This page documents how the tools actually work — including the parts other thumbnail sites gloss over: what happens for sizes YouTube never generated, why the downloads work at all, and where official rules end and conventions begin.

This page is the reference; the guides take the same material one question at a time. YouTube thumbnail sizes explained covers the 5 stored sizes, Why maxresdefault is missing the placeholder check, How the one-click download works the CORS mechanism, Safe areas explained and Aspect-ratio math the size guide and the calculator, On-device background removal the local models, and How to read the thumbnail tester the difference between a typical and a spec. Where a guide and this page overlap, this page is the canonical statement.

URL parsing

Every YouTube video is identified by an 11-character ID drawn from letters, digits, hyphen, and underscore. The parser extracts that ID from the link forms people actually paste — standard watch?v= URLs, youtu.be share links (with their tracking query strings), Shorts, embed, /live/, and legacy /v/ paths, or a bare ID — and validates it against the exact character set and length. It rejects, with specific explanations rather than a generic error: playlist links (no single video), channel links (/channel/, /@handle, /c/, /user/), non-YouTube domains including lookalikes such as youtube.com.evil.example, and non-http schemes like javascript: that can smuggle a fake hostname past naive checks. All of this is covered by an automated test matrix.

Thumbnail URLs and the placeholder-detection check

From a video ID, the 5 stored sizes live at predictable addresses under https://img.youtube.com/vi/{id}/: default (120×90), mqdefault (320×180), hqdefault (480×360), sddefault (640×480), and maxresdefault (1280×720). The two largest exist only for some uploads. When a size is missing, YouTube answers with HTTP 404 — but the response body is a gray 120×90 placeholder image, which a plain <img> tag happily renders, and which careless tools happily serve as a "download". ThumbnailPro checks both signals: the HTTP status of the fetch, and the decoded pixel width of the returned image (asking for a 1280px maxresdefault and decoding 120px means placeholder). Only sizes that pass both checks get a download button; a missing size is labeled "not available for this video", and a size whose bytes arrived with a success status but would not decode is labeled "This size could not be decoded — try the direct link." and gets no button either. The classification logic is a pure function with its own tests.

Why one-click downloads work (the CORS reality)

A web page can only read image bytes from another domain if that domain's server explicitly allows it (CORS). We verified empirically that YouTube's image server sends Access-Control-Allow-Origin: * on thumbnail responses, which is why this site can fetch the image in your browser, verify it is not a placeholder, and hand you a real one-click download. Your browser talks directly to YouTube's servers — the same requests it makes when you browse YouTube — and this site's servers never see your link. If something in your environment blocks those fetches (an aggressive extension, a corporate proxy that strips CORS headers), the tool degrades honestly: it shows "Open image" links to the original YouTube URLs with a save instruction, rather than pretending a download happened.

Preview sizes, safe areas: typicals, not specs

The tester renders thumbnails at true display sizes — 320×180 for a desktop home-grid card, 168×94 for a suggested-sidebar row, 246×138 for a search row, 160×90 for a mobile list row. These are honest current typicals of how video platforms size those surfaces; platforms shift them a few pixels with every redesign, so they are labeled as representative, never exact. The same goes for the safe-area checker: YouTube publishes no overlay specification, so its zones (duration badge bottom-right, hover controls top-right, progress bar along the bottom) model the convention at typical proportions and say so on their labels. Official figures — 16:9, 640px minimum width, the upload size limits — are a different category: those are cited on the size guide to Google's help documentation, verified against it on July 25, 2026.

The maker: a pure engine driving a canvas

The thumbnail maker is split in two. A pure, heavily tested engine owns every decision: the versioned document model (layers, z-order, immutable updates, JSON serialization with corrupt-data rejection), the transform geometry (rotated-rect hit tests, scale-from-handle math, snap guides), undo/redo history semantics, the deterministic layout templates, and the export planner. The browser layer only draws that document onto a canvas and forwards pointer and keyboard events. Serialization is round-trip tested, and a corrupted autosave is rejected with a fresh start — never rendered.

Background removal: a local model, verifiably local

The maker's subject cutout runs Google's open-source MediaPipe selfie-segmentation model (@mediapipe/tasks-vision 0.10.35, Apache-2.0) entirely in your browser, and the standalone background remover's "Click the subject" mode adds Google's MediaPipe MagicTouch interactive-segmentation model (also Apache-2.0), executed by the same local runtime. This site hosts the WASM runtime and both .tflite models itself (249 KB and 6.2 MB) under /vendor/mediapipe/ — no CDN, no third-party request, and a provenance note with the source URLs, versions, checksums, and full license text ships alongside the files. To verify the privacy claim yourself: open your browser's DevTools network panel, import a photo, toggle the cutout, and observe that the only requests are your own browser fetching the model files from this site — no image bytes leave the page. Running WebAssembly required exactly one addition to this site's Content-Security-Policy: 'wasm-unsafe-eval' in script-src (verified empirically against a server sending the production headers). If the model fails to load, the toggle disables with a message and the rest of the editor keeps working.

The background remover's "Best quality" tier runs a third model the same local-first way: BiRefNet_lite (Bilateral Reference for High-Resolution Dichotomous Image Segmentation, tiny variant — upstream ZhengPeng7/BiRefNet_lite, MIT), as a 512×512 ONNX export with 4-bit-quantized weights, executed by onnxruntime-web 1.29.0 (MIT). The 512×512 input is not a stylistic choice but a measured constraint: the full 1024×1024 export needs roughly 6 GB of working memory at inference, which exceeds the ~4 GB WebAssembly heap every browser gives a page — so the previous 1024 artifact failed for everyone, and the shipped one was verified to actually run and to reproduce the reference model's masks. It executes on onnxruntime-web's WASM CPU provider inside a worker thread (the WebGPU provider was tested and produced silently wrong output for this model on the pinned runtime version, so it is deliberately not used — a quality tier that quietly returns an empty mask would be worse than a slightly slower one that is right). Both the model and the runtime are self-hosted under /vendor/birefnet/ and /vendor/onnxruntime/, each with a provenance note (source URLs, repository revisions, checksums, the full derivation and verification chain — including mask-parity measurements against the official full-resolution export — and license texts) shipped alongside the files, and a test suite pins every vendored byte size and the runtime version so the notes cannot silently drift from what is served. The same DevTools check applies: the only network requests are model and runtime files from this site, and no image bytes ever leave the page.

Maker templates, fonts, and export honesty

The style templates are deterministic data — generated gradients, outlined text, arrows, badges, and labeled photo slots — pinned by tests that assert each is structurally valid and distinct. They are not AI, and their descriptions state what each layout does rather than promising click-through. The three fonts (Archivo Black, Inter, Baloo 2) are SIL Open Font License releases, self-hosted as pinned-version subsets with license texts committed in the repository. Exports respect a user-chosen size cap (2 MB by default — YouTube's mobile limit, per the size guide's citation of Google's help documentation, verified July 25, 2026): JPG encoding binary-searches quality until the file fits (the same tested planner approach used across the Sumvia network), an unreachable cap is reported instead of ignored, and every export is re-decoded and dimension-checked before the download is offered, with the real byte size displayed.

Trademark and copyright stance

The preview mockups are deliberately generic — neutral grays, no YouTube logo, no cloned iconography — because YouTube's interface and branding are YouTube's property, and imitating them is neither necessary nor honest. Thumbnails themselves are the creators' artwork: the downloader exists for recovering your own work, backups, and research, and the site says so wherever downloads happen. ThumbnailPro is not affiliated with or endorsed by YouTube or Google.

Privacy: uploads never leave the browser

Images you load into the tester or the safe-area checker are read with local JavaScript, displayed from in-memory object URLs, and discarded when you close the page. There is no upload endpoint on this site. Links you paste into the downloader are parsed locally; the only network requests are your own browser fetching images from YouTube. You can watch all of this in your browser's network inspector — that is the level of verifiability we aim for.

Worked examples can't drift

The figures quoted in page copy — the derived thumbnail URL on the downloader page, the 1280→720 and 1000→562.5 examples on the aspect calculator — are computed at build time by the same engines that power the interactive tools. If the logic changed, the copy would change with it or the build would fail, so the documentation cannot silently disagree with the tools.

Worked figures

The illustrations below each make that claim concrete: every value in them was produced on this page at build time by the engine named in the heading, and none was typed in. The tool pages print the engines' answers to the questions a visitor brings; the tables here are the ones a reference needs — the complete placeholder decision, every rejection the parser can produce, the rules it applies before deciding, the inverse of the rounding example — rather than the typical case, and where a sentence names a count, a code, or a verdict, the build asserts it. The video ID jNQXAC9IVRw is the illustrative one the URL engine's own documentation uses; the derivation is identical for any valid ID.

thumbnailSet('jNQXAC9IVRw')

KeyPixelsGenerated forURL
default 120×90 effectively every public video https://img.youtube.com/vi/jNQXAC9IVRw/default.jpg
mqdefault 320×180 effectively every public video https://img.youtube.com/vi/jNQXAC9IVRw/mqdefault.jpg
hqdefault 480×360 effectively every public video https://img.youtube.com/vi/jNQXAC9IVRw/hqdefault.jpg
sddefault 640×480 some uploads only https://img.youtube.com/vi/jNQXAC9IVRw/sddefault.jpg
maxresdefault 1280×720 some uploads only https://img.youtube.com/vi/jNQXAC9IVRw/maxresdefault.jpg

A download of the largest size would be saved as youtube-jNQXAC9IVRw-maxresdefault-1280x720.jpg — the ID, the size key, and the pixel dimensions are all in the name, so files from different videos and sizes cannot collide. Note that the smallest size, default, is 120×90 — the same dimensions as the gray placeholder, an equality the build asserts rather than assumes — which is why the classification treats a small decode at default as legitimate and only flags it as a placeholder for the larger sizes.

classifyThumbnail(size, probe) — the complete decision grid

The probe is what the browser learned about one thumbnail URL: whether the fetch's status was a success, and the pixel width the bytes decoded to — or nothing, if they would not decode. The size guide and the downloader page each show the verdict for the situations a visitor meets. This grid is exhaustive instead: every stored size against a failed status, a body that would not decode, and a successful decode at every width the engine knows — the placeholder's and each stored size's own. Each cell is the function's return value.

Requested size Status failed Status ok, no decode Status ok, 120 px decodedStatus ok, 320 px decodedStatus ok, 480 px decodedStatus ok, 640 px decodedStatus ok, 1280 px decoded
default (120×90) missing undecodable available available available available available
mqdefault (320×180) missing undecodable missing available available available available
hqdefault (480×360) missing undecodable missing available available available available
sddefault (640×480) missing undecodable missing available available available available
maxresdefault (1280×720) missing undecodable missing available available available available

The grid reads as three rules applied in order. The status check runs first and alone: a failed status is missing for every size, including default, and the decoded width is never consulted. Second, with a success status but no decodable image, every size is undecodable — a separate word from missing, kept so that a broken file is never reported as a size YouTube did not generate; the downloader draws no preview or button for such a card. Third, with a success status and a decoded width, the rule is "narrower than requested and no wider than the placeholder". Of the 25 decode cells, exactly 4 read missing, all in the 120-px column, one for each of the 4 sizes wider than the placeholder; the same column reads available for default, because 120 is that size's real width, which is why the rule compares against the requested size rather than a fixed number. Every other decode cell is available — including decodes narrower than requested but wider than the placeholder, and decodes wider than requested. The function detects the placeholder; it does not audit dimensions, and the grid shows the whole of that boundary. Only available earns a download button; every other verdict produces a labeled card and no file.

parseYouTubeInput(text) — every rejection the engine can produce

A rejected paste comes back as a Result carrying two things: a code drawn from a closed list, so the interface can tell the rejection classes apart, and a plain-text message it can print exactly as received. The list of codes is fixed in the type system, which means a new class of rejection cannot appear without a new code, and every message is written once, in the engine — the field under the downloader shows it word for word. The downloader page prints a sampler of the links people paste; the table here holds one row for each message the parser can return: 18 inputs producing 17 distinct messages under 6 codes, and the code in every row was asserted when this page was built. 4 of those rows — an empty /shorts/, /embed/, /live/, or /v/ path — come from one template in the engine with the path name filled in; each is listed on its own because each is a distinct string the field under the downloader can show.

CodeInputWhat was pastedMessage, word for word
empty-input Nothing, or spaces only Paste a YouTube video link or an 11-character video ID.
not-a-youtube-url the video at youtu.be/jNQXAC9IVRw A sentence with a link inside it That does not look like a YouTube link or video ID — check for stray spaces.
not-a-youtube-url https://youtube.com:video/watch?v=jNQXAC9IVRw A colon where a slash belongs That could not be read as a web address or a video ID.
not-a-youtube-url data://www.youtube.com/watch?v=jNQXAC9IVRw A data: scheme carrying a YouTube hostname Only http(s) YouTube links are supported.
not-a-youtube-url https://www.youtube.com@evil.example/watch?v=jNQXAC9IVRw youtube.com before an @ — the real host is after it That is not a YouTube link. This tool reads youtube.com and youtu.be URLs (or a bare video ID).
not-a-youtube-url jNQXAC9IVR A bare ID one character short That is not a YouTube link. This tool reads youtube.com and youtu.be URLs (or a bare video ID).
channel-url https://www.youtube.com/user/examplechannel/videos Legacy /user/ channel path That is a channel link, not a video. Open one of the channel’s videos and paste that URL.
playlist-url https://www.youtube.com/playlist?list=PLexampleplaylist Playlist page That is a playlist link, not a single video. Open a video in the playlist and paste its URL.
playlist-url https://www.youtube.com/watch?list=PLexampleplaylist Watch page with list= but no v= That watch link points at a playlist without a specific video (no v= parameter). Open the video itself and paste its URL.
playlist-url https://www.youtube.com/embed/videoseries?list=PLexampleplaylist Embed of a playlist (videoseries) That embed link plays a playlist (videoseries), not a single video.
unsupported-url youtu.be youtu.be with nothing after the host That youtu.be link has no video ID after the slash — copy the full share link.
unsupported-url https://www.youtube.com/watch?V=jNQXAC9IVRw Watch page with the parameter in capitals (V=) That watch link is missing its v= video parameter — copy the URL from the video page.
unsupported-url https://www.youtube.com/shorts/ A /shorts/ path with nothing after it That /shorts/ link has no video ID after it.
unsupported-url https://www.youtube.com/embed/ An /embed/ path with nothing after it That /embed/ link has no video ID after it.
unsupported-url https://www.youtube.com/live/ A /live/ path with nothing after it That /live/ link has no video ID after it.
unsupported-url https://www.youtube.com/v/ A legacy /v/ path with nothing after it That /v/ link has no video ID after it.
unsupported-url https://www.youtube.com/feed/subscriptions A YouTube page that is not a video That YouTube link is not a video page this tool recognizes — paste a watch, youtu.be, Shorts, embed, or live URL.
invalid-video-id https://www.youtube.com/watch?v=jNQXAC9IV.w An ID with a character outside the set That link does not contain a valid video ID — YouTube IDs are exactly 11 letters, digits, hyphens, or underscores.

Three rows show where the order of the checks decides the answer. The parser tries the text as a bare ID first and as an address second; a bare ID that is one character short fails the pattern and is therefore read as an address, so it comes back as not-a-youtube-url — its "hostname" is not on the list — rather than as a malformed ID. The message still points at the fix. An address with youtube.com before an @ is handled by the browser's own URL parser before the hostname check runs, and that parser treats everything before the @ as a username: the host it reports is the part after it, which is why the row reads not-a-youtube-url and the lookalike never reaches the path checks. The capital V= row is one of the places the parser is case-sensitive: the first path segment is lowercased before it is matched and the hostname is lowercased by the URL parser, but query-string keys are compared exactly — as is the embed playlist marker videoseries — so V= is not v= and the link comes back as unsupported-url with the message that names the missing parameter. The rejection classes themselves are pinned by the automated suite described at the end of this page.

parseYouTubeInput(text) — what is normalized before the checks

The accepted-forms table on the downloader page covers the links people paste. The rows here are the awkward variants of those links, and each resolves to the same ID — the build checks that it does:

VariantInputID extracted
Spaces before and after the link https://www.youtube.com/watch?v=jNQXAC9IVRw jNQXAC9IVRw
Host in capitals https://WWW.YOUTUBE.COM/watch?v=jNQXAC9IVRw jNQXAC9IVRw
Path in capitals https://www.youtube.com/WATCH?v=jNQXAC9IVRw jNQXAC9IVRw
Plain http scheme http://youtu.be/jNQXAC9IVRw jNQXAC9IVRw
Protocol-relative share link //youtu.be/jNQXAC9IVRw jNQXAC9IVRw
A fragment after the ID https://www.youtube.com/watch?v=jNQXAC9IVRw#t=30 jNQXAC9IVRw
Playlist context and a video https://www.youtube.com/watch?list=PLexampleplaylist&v=jNQXAC9IVRw jNQXAC9IVRw
A segment after the ID https://www.youtube.com/live/jNQXAC9IVRw/extra jNQXAC9IVRw
The music subdomain https://music.youtube.com/watch?v=jNQXAC9IVRw jNQXAC9IVRw

Read together with the rejection table, these rows fix the parser's rules in one place. Whitespace at the ends is trimmed; whitespace inside is fatal. The hostname and the first path segment are matched without regard to case; a query key is not. A missing scheme is taken as https, a protocol-relative // likewise, and plain http is accepted — the scheme check exists to exclude javascript: and data:, not to insist on a secure link. A fragment, extra parameters, and extra path segments after the ID are ignored, because the ID is all that is needed; and list= alongside v= is a video that happens to sit in a playlist, whereas list= alone is a playlist, which is the whole difference between this table's row and the rejection table's. None of this consults the network: the parser says whether the text names a video ID, not whether that video exists, and it finishes before any image request is made.

heightForWidth(width, 16:9) and its inverse, with the rounding flag

WidthExactRounded pixelisExact
1280 720 720 true
1000 562.5 563 false

The flag is what the calculator reads to decide whether to say "rounded" next to a result: the first row is whole, so nothing is added; the second is not, so both the exact value and the nearest whole pixel are shown. A width divisible by 16 always lands on a whole pixel at 16:9.

The flag also records something the rounded number cannot: rounding is one-way. Feed the whole result back into widthForHeight and the round trip closes — 720 tall at 16:9 gives 1280, isExact true. Feed the rounded result back and it does not: 563 tall gives 1000.89, reported as 1001 with isExact false, which is not the 1000 that produced it. describeRatio says the same thing from the other side: 1000×563 simplifies to 1000:563, and is16x9 is false, because the test is integer arithmetic — width × 9 must equal height × 16 — and a rounded pixel has already broken the equality. That is why no tool on this site stores a rounded pair as if it were the ratio: the calculator shows the exact value beside the pixel, and the tester and the size checker report such a pair as not 16:9, with the crop it implies, rather than calling it close enough. The crop table at the end of this section puts a figure on that pair.

overlayZones(1280, 720) — typical zones, not a spec

ZonexyWidthHeight
Duration badge (typical) 998 619 282 101
Hover controls (typical) 1075 0 205 216
Watched-progress bar (typical) 0 684 1280 36

Coordinates are pixels from the image's top-left corner, so a zone whose x plus width equals 1280 hugs the right edge, and one whose y plus height equals 720 hugs the bottom. The rectangles are fixed fractions of the frame, rounded to whole pixels, so they scale with the image. They model the convention at typical proportions — YouTube publishes no overlay specification — which is why every label carries the word "typical".

coverCropReport(width, height) for common export sizes

A 16:9 slot in the tester is a cover crop: the image is scaled until it fills the slot and whatever overflows is trimmed. The report says which axis overflows and by how much.

ImageSimplified ratioFits 16:9Cropped axisCropped
1280×720 (maker preset) 16:9 yes — nothing
1080×1920 (maker preset) 9:16 no height 68.4%
1080×1080 (maker preset) 1:1 no height 43.8%
640×480 (stored sddefault) 4:3 no height 25%
1680×720 (21:9 banner at the maxresdefault height) 7:3 no width 23.8%
1000×563 (rounded 16:9 result from the table above) 1000:563 no height 0.1%

An image that is exactly 16:9 fits with nothing lost — the 1280×720 row. An image taller than 16:9 is scaled to the slot's width, so its extra height is cut from the top and bottom: the vertical preset and the square both lose height, by very different amounts. An image wider than 16:9 is scaled to the slot's height and loses width instead — the 21:9 banner gives up 23.8% of its width from the sides. The sddefault row is the stored 4:3 frame: it is a size YouTube serves back, and it still loses 25% of its height in a 16:9 slot, which is why the sizes guide keeps "served back" apart from whether a size fits the player. The last row is the rounded calculator result from the aspect table: 1000×563 is not exactly 16:9, so the report flags a crop, but the amount is 0.1% — which is how a rounding flag and a crop report can both be right about the same image. The report is geometry only. It states which pixels a slot would trim; it says nothing about how anyone would respond to the result, and the tester's page says so.

Tested against real behavior

The automated suite pins the URL-parser matrix (every accepted form, every rejection class, hostile inputs), the thumbnail-set derivation, the placeholder classification, the aspect arithmetic against hand-computed values, and the safe-area geometry. If you believe a result is wrong — especially a size labeled available that was not, or the reverse — that is exactly the report we want: see the contact page. Confirmed issues are fixed in the engine and locked in with a new test.

Last reviewed: .