/* catcrafts.net Copyright (C) 2026 Catcrafts The source code of this website is made available for viewing purposes only. No permission is granted to copy, modify, distribute, or create derivative works. */ // The markup for one mirrored file — image or video. // // This exists as its own partition because two callers need identical output: // :Views renders a post's headline media on the card and at the top of the post // page, and :Markdown renders whatever the body embeds. They were separate // copies of the same element-building code, which is how the AV1 fallback logic // came to be written twice; a format tier added to one and forgotten in the // other shows up as the same picture served two different ways on two pages. // // FORMAT LADDER. Every file the mirror stores is served in the best form the // browser will take, with a form every browser will take underneath it: // // video AV1 in MP4, falling back to H.264 in MP4. Two elements; // the codecs parameter on the first is what lets a browser without // AV1 skip it rather than fail on a file it cannot decode. // image AVIF, falling back to the mirrored original (usually WebP), falling // back to PNG. A , so the browser fetches exactly one of // them — the tiers cost nothing to the visitors who do not need them. // // The middle image tier is free: it is the file the mirror already downloaded, // so it adds no encode and no disk. It matters because it is what stands // between "no AVIF" and the PNG, and PNG is lossless — for a photograph that is // an order of magnitude larger than the WebP beside it. With the middle tier // the PNG is reached only by a browser that supports neither AVIF nor WebP. // // Any tier can be absent (the transcode was unavailable, or the file was never // mirrored at all) and the markup degrades a step at a time, down to a bare // — which is what the site emitted before any of this existed. export module Catcrafts.Shared:Media; import std; import :Html; import :Model; namespace Catcrafts::Media { using Html::SafeHtml; using Html::Escape; using Html::Attr; using Html::Format; using Html::Join; using Html::Url; bool EndsWithNoCase(std::string_view s, std::string_view suffix) { if (s.size() < suffix.size()) return false; const std::size_t off = s.size() - suffix.size(); for (std::size_t i = 0; i < suffix.size(); ++i) { char a = s[off + i]; if (a >= 'A' && a <= 'Z') a = static_cast(a - 'A' + 'a'); if (a != suffix[i]) return false; } return true; } // True when the path names something the