{"openapi":"3.1.0","info":{"title":"视频筛查","version":"1","description":"判断 YouTube 视频适不适合 2–5 岁的孩子看。不下载视频：画面用 YouTube 进度条缩略图测饱和度和色彩丰富度，字幕过脏话词表和模型价值观检查单。\n\n闪烁、红色闪烁和剪辑节奏用缩略图测不了，固定为 unknown（未筛查），不计入整体结论。\n\n不需要 Key。结果缓存 7 天；没缓存的视频每个 IP 每分钟最多 20 个（超了回 429 rate_limited）。"},"servers":[{"url":"https://screening.jiajun.fyi"}],"paths":{"/videos/{id}":{"get":{"summary":"筛查一个视频","description":"有缓存直接返回；没有就现测（画面约 1 秒，字幕的模型检查可能要几十秒）。","parameters":[{"name":"id","in":"path","required":true,"description":"YouTube 视频 id（11 位）。","schema":{"type":"string","pattern":"^[\\w-]{11}$"},"example":"n-GlQ9MOLd8"}],"responses":{"200":{"description":"筛查结果","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Screening"}}}},"400":{"description":"错误","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"错误","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"错误","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"错误","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"错误","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/playlists/{id}":{"get":{"summary":"播放列表里的视频和已有的结果","description":"最多 200 个视频。没筛查的在 pending 里：逐个调 /videos/{id}（建议同时不超过 3 个），再取一次列表。","parameters":[{"name":"id","in":"path","required":true,"description":"播放列表 id（链接里 list= 后面的部分）。","schema":{"type":"string"},"example":"PLONHGBSOepTm3dfh_asBIh2G_jlRAeEoU"}],"responses":{"200":{"description":"列表","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Playlist"}}}},"400":{"description":"错误","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"错误","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"错误","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/videos/{id}/sheets/{n}":{"get":{"summary":"进度条缩略图大图（同源代理）","parameters":[{"name":"id","in":"path","required":true,"description":"YouTube 视频 id（11 位）。","schema":{"type":"string","pattern":"^[\\w-]{11}$"},"example":"n-GlQ9MOLd8"},{"name":"n","in":"path","required":true,"schema":{"type":"integer","minimum":0}}],"responses":{"200":{"description":"JPEG 或 WebP","content":{"image/jpeg":{},"image/webp":{}}},"404":{"description":"错误","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"schemas":{"Verdict":{"type":"string","enum":["pass","warn","fail","unknown"],"description":"pass 通过 / warn 留意 / fail 不合适 / unknown 未筛查。"},"Check":{"type":"object","properties":{"id":{"type":"string","description":"检查项 id（packages/content-intake/src/screening.ts SCREENING_CHECKS）。","examples":["saturation"]},"group":{"type":"string","enum":["visual","language","values"],"description":"画面 / 语言 / 价值观。"},"name":{"type":"string","description":"显示名（glossary.toml [labels.screening_check]）。","examples":["画面过饱和"]},"groupName":{"type":"string"},"verdict":{"$ref":"#/components/schemas/Verdict"},"value":{"type":"string","description":"测到的值，给人看。","examples":["21% 的帧过饱和，平均饱和度 39%，平均亮度 52%"]},"rule":{"type":"string"},"sources":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"url":{"type":"string"}},"required":["label","url"]}},"measured":{"type":"boolean"}},"required":["id","group","name","groupName","verdict","value","rule","sources","measured"],"description":"检查单的一项：结论、测量值、规则和出处。measured=false 的项用进度条缩略图测不了，固定为 unknown，不计入整体结论。"},"Flag":{"type":"object","properties":{"check":{"type":"string"},"severity":{"type":"string","enum":["warn","fail"]},"start":{"type":"number"},"quote":{"type":"string"},"reason":{"type":"string"},"by":{"type":"string","enum":["word_list","model"]}},"required":["check","severity","start","quote","reason","by"],"description":"字幕里标出的一句：词表或模型标的。start 是秒，-1 表示标题或简介。"},"Visual":{"type":"object","properties":{"method":{"type":"string","const":"storyboard"},"frameIntervalS":{"type":"number"},"saturatedShare":{"type":"number","description":"过饱和的帧占比（0–1）。","examples":[0.217]},"meanSaturation":{"type":"number","description":"平均 HSV 饱和度（不算亮度 < 20% 的像素）。","examples":[0.39]},"vividShare":{"type":"number","description":"鲜艳像素（饱和度 ≥ 80% 且亮度 ≥ 35%）平均占比。","examples":[0.049]},"meanColourfulness":{"type":"number","description":"Hasler & Süsstrunk 色彩丰富度 M3 的平均值，只作参考。","examples":[41.2]},"colourfulnessBand":{"type":"string","description":"M3 对应的人工评分档。","examples":["中等"]},"saturatedAt":{"type":"array","items":{"type":"number"},"description":"过饱和帧的秒数。"},"frames":{"type":"array","items":{"type":"object","properties":{"t":{"type":"number"},"sat":{"type":"number"},"vivid":{"type":"number"},"colourfulness":{"type":"number"},"saturated":{"type":"boolean"}},"required":["t","sat","vivid","colourfulness","saturated"]}},"storyboard":{"type":"object","properties":{"columns":{"type":"integer"},"tileWidth":{"type":"integer"},"tileHeight":{"type":"integer"},"sheets":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"rows":{"type":"integer"}},"required":["url","rows"]},"description":"缩略图大图（同源代理），按行列拼着按时间排好的帧；最后一张行数可能少。"}},"required":["columns","tileWidth","tileHeight","sheets"]}},"required":["method","frameIntervalS","saturatedShare","meanSaturation","vividShare","meanColourfulness","colourfulnessBand","saturatedAt","frames","storyboard"],"description":"画面测量：来自 YouTube 进度条缩略图，每 frameIntervalS 秒一帧。"},"Screening":{"type":"object","properties":{"id":{"type":"string","description":"YouTube 视频 id。","examples":["n-GlQ9MOLd8"]},"url":{"type":"string"},"title":{"type":"string"},"channel":{"type":"string"},"durationS":{"type":"number"},"verdict":{"$ref":"#/components/schemas/Verdict"},"screenedAt":{"type":"string","description":"测量时间（UTC ISO）；7 天内再请求直接读缓存。"},"checks":{"type":"array","items":{"$ref":"#/components/schemas/Check"}},"visual":{"anyOf":[{"$ref":"#/components/schemas/Visual"},{"type":"null"}]},"language":{"type":"object","properties":{"transcript":{"type":"string","enum":["captions","none"],"description":"none：没有字幕，语言和价值观各项未筛查。"},"captionLanguage":{"type":["string","null"]},"captionKind":{"type":["string","null"],"enum":["asr","manual",null]},"lines":{"type":"integer"},"flags":{"type":"array","items":{"$ref":"#/components/schemas/Flag"}}},"required":["transcript","captionLanguage","captionKind","lines","flags"]}},"required":["id","url","title","channel","durationS","verdict","screenedAt","checks","visual","language"],"description":"一个视频的筛查结果。"},"PlaylistVideo":{"type":"object","properties":{"id":{"type":"string"},"position":{"type":"integer"},"title":{"type":"string"},"channel":{"type":"string"},"durationS":{"type":["number","null"]},"screening":{"anyOf":[{"$ref":"#/components/schemas/Screening"},{"type":"null"}]}},"required":["id","position","title","channel","durationS","screening"],"description":"列表里的一个视频；screening 为 null 表示还没筛查（在 pending 里）。"},"Playlist":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":["string","null"]},"url":{"type":"string"},"truncated":{"type":"boolean","description":"列表超过 200 个，只取了前 200 个。"},"videos":{"type":"array","items":{"$ref":"#/components/schemas/PlaylistVideo"}},"pending":{"type":"array","items":{"type":"string"}}},"required":["id","title","url","truncated","videos","pending"],"description":"播放列表：已筛查的带结果，没筛查的列在 pending，逐个 GET /videos/{id} 后再取一次。"},"Error":{"type":"object","properties":{"error":{"type":"string","pattern":"^[a-z][a-z0-9_]*$","description":"Stable machine code (snake_case): branch on this.","examples":["not_found"]},"message":{"description":"What went wrong, for people. Wording may change; do not match on it.","type":"string"},"details":{"description":"Every validation issue, when the request body or query failed its schema.","type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"]}}},"required":["error"],"description":"`error` 是固定代码：invalid_video_id、invalid_playlist_id、video_unavailable、playlist_unavailable、youtube_blocked、youtube_error、rate_limited、screening_failed、not_found。"}}}}