|
| 1 | +# yt-dlp JSON Reference |
| 2 | + |
| 3 | +Reference for the two JSON files YAMP reads from disk: the per-video `*.info.json` and the |
| 4 | +per-channel `*.channel.json` saved by `_fetch_channel_art`. |
| 5 | + |
| 6 | +--- |
| 7 | + |
| 8 | +## Video — `*.info.json` |
| 9 | + |
| 10 | +Written by yt-dlp alongside each downloaded media file. |
| 11 | +Filename pattern: `Video Title [VIDEO_ID].info.json` |
| 12 | + |
| 13 | +```json |
| 14 | +{ |
| 15 | + "id": "aG_9T2uOLeM", |
| 16 | + "display_id": "aG_9T2uOLeM", |
| 17 | + "title": "Another Sky - Watching Basinski (Live Session)", |
| 18 | + "fulltitle": "Another Sky - Watching Basinski (Live Session)", |
| 19 | + "description": "Another Sky - 'Watching Basinski' out now!\n\nListen: https://...", |
| 20 | + |
| 21 | + "channel": "Another Sky", |
| 22 | + "channel_id": "UCXyNhY9FcGAQOVnNIDimrbw", |
| 23 | + "channel_url": "https://www.youtube.com/channel/UCXyNhY9FcGAQOVnNIDimrbw", |
| 24 | + "channel_follower_count": 5330, |
| 25 | + "channel_is_verified": true, |
| 26 | + |
| 27 | + "uploader": "Another Sky", |
| 28 | + "uploader_id": "@anotherskyvevo2939", |
| 29 | + "uploader_url": "https://www.youtube.com/@anotherskyvevo2939", |
| 30 | + |
| 31 | + "upload_date": "20230623", |
| 32 | + "timestamp": 1687492800, |
| 33 | + |
| 34 | + "duration": 225, |
| 35 | + "duration_string": "3:45", |
| 36 | + |
| 37 | + "thumbnail": "https://i.ytimg.com/vi/aG_9T2uOLeM/maxresdefault.jpg", |
| 38 | + |
| 39 | + "categories": ["Music"], |
| 40 | + "tags": ["Another Sky", "Fiction", "Alternative"], |
| 41 | + |
| 42 | + "view_count": 4537, |
| 43 | + "like_count": 126, |
| 44 | + "comment_count": 11, |
| 45 | + |
| 46 | + "live_status": "not_live", |
| 47 | + "is_live": false, |
| 48 | + "was_live": false, |
| 49 | + "media_type": "video", |
| 50 | + "age_limit": 0, |
| 51 | + "availability": "public", |
| 52 | + |
| 53 | + "extractor": "youtube", |
| 54 | + "extractor_key": "Youtube", |
| 55 | + "webpage_url": "https://www.youtube.com/watch?v=aG_9T2uOLeM", |
| 56 | + |
| 57 | + "width": 3840, |
| 58 | + "height": 2160, |
| 59 | + "resolution": "3840x2160", |
| 60 | + "fps": 25, |
| 61 | + "aspect_ratio": 1.78, |
| 62 | + "vcodec": "vp9", |
| 63 | + "acodec": "opus", |
| 64 | + "ext": "webm", |
| 65 | + "format": "313 - 3840x2160 (2160p)+251 - audio only (medium)", |
| 66 | + "format_id": "313+251", |
| 67 | + |
| 68 | + "epoch": 1774279906, |
| 69 | + |
| 70 | + "formats": [ "... one entry per available quality, large array, skipped ..." ], |
| 71 | + "thumbnails": [ "... multiple resolution variants, skipped ..." ], |
| 72 | + "subtitles": {}, |
| 73 | + "automatic_captions": {}, |
| 74 | + "heatmap": [ "... engagement heatmap, skipped ..." ] |
| 75 | +} |
| 76 | +``` |
| 77 | + |
| 78 | +### Fields YAMP uses |
| 79 | + |
| 80 | +| Field | Where used | Notes | |
| 81 | +|---|---|---| |
| 82 | +| `id` | `extract_video_id()` fallback; index key | YouTube IDs are exactly 11 chars; Bilibili IDs start with `BV` | |
| 83 | +| `title` | Plex `title` | | |
| 84 | +| `description` | Plex `summary` | | |
| 85 | +| `upload_date` | Plex `originallyAvailableAt`, `year` | Format: `YYYYMMDD` string | |
| 86 | +| `duration` | Plex `duration` | Seconds; multiplied ×1000 for Plex (ms) | |
| 87 | +| `thumbnail` | Plex `thumb`; thumbnail proxy | Direct CDN URL; YAMP proxies it so Plex can always reach it | |
| 88 | +| `categories` | Plex `Genre[].tag` | | |
| 89 | +| `channel` | Plex `Director[].tag`; collection matching | | |
| 90 | +| `tags` | Collection rule matching (`MATCH_FIELDS`) | Consumed on match to prevent double-matching | |
| 91 | +| `extractor` | Plex `studio` | e.g. `"youtube"`, `"bilibili"` | |
| 92 | +| `uploader_url` | Channel art prefetch trigger | Used as the key into `_channel_art_cache` | |
| 93 | + |
| 94 | +### Fields used in collection matching (`MATCH_FIELDS` in `collection_map.py`) |
| 95 | + |
| 96 | +`title`, `description`, `channel`, `uploader`, `tags`, `categories` |
| 97 | + |
| 98 | +--- |
| 99 | + |
| 100 | +## Channel — `*.channel.json` |
| 101 | + |
| 102 | +Written by `_fetch_channel_art` when it successfully fetches channel metadata via yt-dlp. |
| 103 | +Saved as `<sanitized_channel_name>.channel.json` in the channel's subdirectory (matched by |
| 104 | +the `[channel_id]` suffix in the dir name — how yt-dlp's default output template names |
| 105 | +directories), or at the data root if no matching dir exists. |
| 106 | + |
| 107 | +The `entries` key (the full video listing) is stripped before saving — it can be thousands |
| 108 | +of items and is not useful for debugging artwork issues. |
| 109 | + |
| 110 | +```json |
| 111 | +{ |
| 112 | + "id": "UC-smeLB9AnOTeypr1YyjJ3A", |
| 113 | + "channel": "ARTE Concert", |
| 114 | + "channel_id": "UC-smeLB9AnOTeypr1YyjJ3A", |
| 115 | + "title": "ARTE Concert", |
| 116 | + "availability": null, |
| 117 | + |
| 118 | + "channel_follower_count": 1920000, |
| 119 | + "description": "", |
| 120 | + "tags": ["ARTE", "concert", "live", "web", "music"], |
| 121 | + "playlist_count": 3, |
| 122 | + |
| 123 | + "uploader": "ARTE Concert", |
| 124 | + "uploader_id": "@arteconcert", |
| 125 | + "uploader_url": "https://www.youtube.com/@arteconcert", |
| 126 | + "channel_url": "https://www.youtube.com/channel/UC-smeLB9AnOTeypr1YyjJ3A", |
| 127 | + |
| 128 | + "modified_date": null, |
| 129 | + "view_count": null, |
| 130 | + "release_year": null, |
| 131 | + |
| 132 | + "extractor": "youtube:tab", |
| 133 | + "extractor_key": "YoutubeTab", |
| 134 | + "_type": "playlist", |
| 135 | + "webpage_url": "https://www.youtube.com/channel/UC-smeLB9AnOTeypr1YyjJ3A", |
| 136 | + "original_url": "https://www.youtube.com/@arteconcert", |
| 137 | + "epoch": 1774297897, |
| 138 | + |
| 139 | + "thumbnails": [ |
| 140 | + { "id": "0", "preference": -10, "width": 1060, "height": 175, "resolution": "1060x175", |
| 141 | + "url": "https://yt3.googleusercontent.com/...=w1060-fcrop64=1,...-no-nd-rj" }, |
| 142 | + { "id": "1", "preference": -10, "width": 1138, "height": 188, "resolution": "1138x188", |
| 143 | + "url": "https://yt3.googleusercontent.com/...=w1138-fcrop64=1,...-no-nd-rj" }, |
| 144 | + { "id": "2", "preference": -10, "width": 1707, "height": 283, "resolution": "1707x283", |
| 145 | + "url": "https://yt3.googleusercontent.com/...=w1707-fcrop64=1,...-no-nd-rj" }, |
| 146 | + { "id": "3", "preference": -10, "width": 2120, "height": 351, "resolution": "2120x351", |
| 147 | + "url": "https://yt3.googleusercontent.com/...=w2120-fcrop64=1,...-no-nd-rj" }, |
| 148 | + { "id": "4", "preference": -10, "width": 2276, "height": 377, "resolution": "2276x377", |
| 149 | + "url": "https://yt3.googleusercontent.com/...=w2276-fcrop64=1,...-no-nd-rj" }, |
| 150 | + { "id": "5", "preference": -10, "width": 2560, "height": 424, "resolution": "2560x424", |
| 151 | + "url": "https://yt3.googleusercontent.com/...=w2560-fcrop64=1,...-no-nd-rj" }, |
| 152 | + { "id": "banner_uncropped", "preference": -5, |
| 153 | + "url": "https://yt3.googleusercontent.com/...=s0" }, |
| 154 | + { "id": "7", "width": 900, "height": 900, "resolution": "900x900", |
| 155 | + "url": "https://yt3.googleusercontent.com/...=s900-c-k-c0x00ffffff-no-rj" }, |
| 156 | + { "id": "avatar_uncropped", "preference": 1, |
| 157 | + "url": "https://yt3.googleusercontent.com/...=s0" } |
| 158 | + ] |
| 159 | +} |
| 160 | +``` |
| 161 | + |
| 162 | +### Thumbnail IDs (consistent across all observed channels) |
| 163 | + |
| 164 | +| ID | Type | Size | Preference | Notes | |
| 165 | +|---|---|---|---|---| |
| 166 | +| `0`–`5` | Banner crop | 1060×175 → 2560×424 | `-10` | Same image, different widths; ~6:1 aspect ratio. On channels with no banner, `0` may be the avatar at 900×900 instead. | |
| 167 | +| `banner_uncropped` | Banner full | native (no metadata) | `-5` | Full-resolution banner; no `width`/`height` fields. Used as `art` (Background) in Plex. | |
| 168 | +| `7` | Avatar crop | 900×900 | none | Cropped/padded square variant. | |
| 169 | +| `avatar_uncropped` | Avatar full | native (no metadata) | `1` | Full-resolution avatar; no `width`/`height` fields. Highest preference value. Used as `image` (Poster) in Plex. | |
| 170 | + |
| 171 | +**Key finding:** match thumbnail IDs by their named `id` string, not by array index — the |
| 172 | +array order is stable in practice but the IDs are explicit and safe to rely on. Both |
| 173 | +`*_uncropped` variants lack `width`/`height` metadata but are the highest-quality options. |
| 174 | + |
| 175 | +### Fields YAMP uses from channel.json |
| 176 | + |
| 177 | +| Field | Where used | |
| 178 | +|---|---| |
| 179 | +| `channel` / `uploader` | `_fetch_channel_art` return value; filename for the `.channel.json` itself | |
| 180 | +| `channel_id` | Used to find the channel's subdirectory (`[channel_id]` suffix match) | |
| 181 | +| `thumbnails[id=avatar_uncropped].url` | Returned as `avatar_url` → suggested as collection `image` (poster) | |
| 182 | +| `thumbnails[id=banner_uncropped].url` | Returned as `banner_url` → suggested as collection `art` (background) | |
| 183 | +| `thumbnail` (top-level) | Fallback `avatar_url` when `avatar_uncropped` is absent | |
0 commit comments