video seo
All checks were successful
Deploy / build-deploy (push) Successful in 2m33s

This commit is contained in:
Jorijn van der Graaf 2026-08-19 00:02:32 +02:00
commit a8bc400371
3 changed files with 128 additions and 7 deletions

View file

@ -181,5 +181,63 @@ int main(int argc, char** argv) {
Check(personOk, "about Person schema parses and names the founder");
}
// ── the post's video ──────────────────────────────────────────────
// Google carouselled these recordings off the bare <video> element; the
// VideoObject is what makes that deliberate rather than lucky.
//
// PostVideoLd only marks up our OWN mirrored copies, so this can only be
// asserted when the fixture was actually mirrored — an unmirrored checkout
// still carries the instance's absolute URLs, and skipping is the correct
// behaviour there, not a failure. Same reasoning (and same note) as the
// format-ladder checks in ShouldServePostPages.
{
constexpr std::string_view kVideoPost =
"/posts/fairphone-6-postmarketos-working-main-camera";
const std::string postBody = srv.Body(std::string(kVideoPost));
if (postBody.find("poster=\"/media/") == std::string::npos) {
std::println("note: no locally-mirrored poster on {} — the "
"VideoObject checks did not run", kVideoPost);
} else {
bool found = false;
bool complete = false;
bool ownMirror = false;
for (const Json::Value& doc : ExtractLd(srv, std::string(kVideoPost))) {
if (doc.Str("@type") != "BlogPosting") continue;
const Json::Value* v = doc.Find("video");
if (!v) continue;
if (v->IsArray() && v->array.empty()) continue;
// One video: the object itself, not a single-element array.
const Json::Value& vid = v->IsArray() ? v->array.front() : *v;
found = true;
const std::string_view thumb = vid.Str("thumbnailUrl");
const std::string_view content = vid.Str("contentUrl");
complete = vid.Str("@type") == "VideoObject"
&& !vid.Str("name").empty()
&& !vid.Str("uploadDate").empty()
&& !thumb.empty() && !content.empty();
ownMirror = thumb.starts_with("https://catcrafts.net/media/")
&& content.starts_with("https://catcrafts.net/media/");
}
Check(found, "a video post's BlogPosting carries a video node");
Check(complete, "VideoObject has the fields the rich result requires");
Check(ownMirror, "video thumbnail and content URLs point at our own mirror");
}
}
// A post that leads with no video must not grow an empty video property:
// an Article claiming a video it does not have is a structured-data error,
// not a harmless extra key.
{
bool sawPosting = false;
bool bare = true;
for (const Json::Value& doc :
ExtractLd(srv, "/posts/the-linux-phone-travel-experience")) {
if (doc.Str("@type") != "BlogPosting") continue;
sawPosting = true;
if (doc.Find("video")) bare = false;
}
Check(sawPosting && bare, "a post with no video carries no video node");
}
return Finish();
}