diff --git a/README.md b/README.md index 6252448..23aa7f4 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,124 @@ # unified-user-data-format + Specification for the unified user data format across the various "Tube" apps (NewPipe, FreeTube, Invidious, etc..) + +## Intro + +* This specification is a very early draft, and prone to regular changes +* Unless otherwise specified, all fields are assumed to be `Strings` + + +## Main file (`main.json`) + +#### `"version"` + +The [semantic version](https://semver.org/) of the specification. + +Conditions for bumping major/minor/patch version: TBD +Potential suffixes: `-draft`, TBD + +#### `"subscriptions"` + +An array of [subscription objects](spec/subscription_object). + +#### `"playlists"` + +An array of Strings. + +Each string refers to the name of a playlist file (without the `playlist_` prefix) +that was included in the export. + +#### `"saved_playlists_present"` + +A Boolean. + +Indicates whether the `saved_playlists.json` file was included in the export. + +#### `"watch_history_present"` + +A Boolean. + +Indicates whether the special playlist `watch_history` was included in the export. + +### Overview + +```javascript +{ + "version": "X.Y.Z[-draft/-rc]", + "saved_playlists_present": Boolean, + "watch_history_present": Boolean, + "subscriptions": SubscriptionObject[], + "playlists": String[], +} +``` + + +## Playlist file (`playlist_xxx.json`) + +A playlist file MUST contain the data of a single playlist created by the user. + +#### > `"name"` + +The title of the playlist. + +#### > `"visibility"` + +The visibility of the playlist. + +Accepted values: "public", "unlisted", "private" + +#### > `"description"` + +The description of the playlist. +Format: plain text (? TBD) + +CAN be nil; In this case, the parser MUST assume an empty `String`. + +#### > `"videos"` + +An array of [video objects](spec/video_object). + +### Overview + +```javascript +{ + "name": String, + "visibility": String, + "description": String, + "videos": VideoObject[], +} +``` + + +## Saved playlist file (`saved_playlists.json`) + +This file contains the playlists from other services that the user bookmarked. + +#### > `"playlists"` + +An array of [saved playlist objects](spec/saved playlist_object). + +### Overview + +```javascript +{ + "playlists": SavedPlaylist[], +} +``` + + +## Watch history file (`watch_history.json`) + +The watch history is nothing more than a private paylist, without metadata + +#### > `"videos"` + +An array of [video objects](spec/video_object). + +### Overview + +```javascript +{ + "videos": VideoObject[], +} +``` diff --git a/examples/main.json b/examples/main.json new file mode 100644 index 0000000..634c105 --- /dev/null +++ b/examples/main.json @@ -0,0 +1,17 @@ +{ + "version": "0.0.5-draft", + "subscriptions": [ + { + "type": "youtube", + "id": "UCXuqSBlHAE6Xw-yeJA0Tunw", + "name": "Linus Tech Tips", + "url": "https://www.youtube.com/channel/UCXuqSBlHAE6Xw-yeJA0Tunw", + "thumbnail": "https://yt3.ggpht.com/ytc/[snip]" + } + ], + "saved_playlists_present": true, + "watch_history_present": true, + "playlists": [ + "abc123" + ] +} diff --git a/examples/playlist_abc123.json b/examples/playlist_abc123.json new file mode 100644 index 0000000..91d9dad --- /dev/null +++ b/examples/playlist_abc123.json @@ -0,0 +1,49 @@ +{ + "name": "My fav Music!", + "visibility": "public", + "description": "The playlist I listen to every day", + "videos": [ + { + "type": "youtube", + "id": "djV11Xbc914", + "url": "https://www.youtube.com/v/djV11Xbc914", + "thumbnail": "https://i.ytimg.com/vi/djV11Xbc914/default.jpg" + }, + { + "type": "youtube", + "id": "HEXWRTEbj1I", + "url": "https://www.youtube.com/v/HEXWRTEbj1I", + "thumbnail": "https://i.ytimg.com/vi/HEXWRTEbj1I/default.jpg" + }, + { + "type": "youtube", + "id": "sRl02nXVMhw", + "url": "https://www.youtube.com/v/sRl02nXVMhw", + "thumbnail": "https://i.ytimg.com/vi/sRl02nXVMhw/default.jpg" + }, + { + "type": "youtube", + "id": "dQw4w9WgXcQ", + "url": "https://www.youtube.com/v/dQw4w9WgXcQ", + "thumbnail": "https://i.ytimg.com/vi/dQw4w9WgXcQ/default.jpg" + }, + { + "type": "youtube", + "id": "Hy8kmNEo1i8", + "url": "https://www.youtube.com/v/Hy8kmNEo1i8", + "thumbnail": "https://i.ytimg.com/vi/Hy8kmNEo1i8/default.jpg" + }, + { + "type": "youtube", + "id": "zA52uNzx7Y4", + "url": "https://www.youtube.com/v/zA52uNzx7Y4", + "thumbnail": "https://i.ytimg.com/vi/zA52uNzx7Y4/default.jpg" + }, + { + "type": "youtube", + "id": "9bZkp7q19f0", + "url": "https://www.youtube.com/v/9bZkp7q19f0", + "thumbnail": "https://i.ytimg.com/vi/9bZkp7q19f0/default.jpg" + } + ] +} diff --git a/examples/saved_playlists.json b/examples/saved_playlists.json new file mode 100644 index 0000000..20dd5c0 --- /dev/null +++ b/examples/saved_playlists.json @@ -0,0 +1,12 @@ +{ + "playlists": [ + { + "type": "youtube", + "id": "PLYT4vq6pQVSvgqA8Qsm8TXjYJMTT2XyNV", + "title": "World Of Walker (Full Album)", + "author": "Alan Walker", + "url": "https://www.youtube.com/playlist?list=PLYT4vq6pQVSvgqA8Qsm8TXjYJMTT2XyNV", + "thumbnail": "https://yt3.ggpht.com/ytc/[snip]" + } + ] +} diff --git a/examples/watch_history.json b/examples/watch_history.json new file mode 100644 index 0000000..74ae25e --- /dev/null +++ b/examples/watch_history.json @@ -0,0 +1,46 @@ +{ + "videos": [ + { + "type": "youtube", + "id": "djV11Xbc914", + "url": "https://www.youtube.com/v/djV11Xbc914", + "thumbnail": "https://i.ytimg.com/vi/djV11Xbc914/default.jpg" + }, + { + "type": "youtube", + "id": "HEXWRTEbj1I", + "url": "https://www.youtube.com/v/HEXWRTEbj1I", + "thumbnail": "https://i.ytimg.com/vi/HEXWRTEbj1I/default.jpg" + }, + { + "type": "youtube", + "id": "sRl02nXVMhw", + "url": "https://www.youtube.com/v/sRl02nXVMhw", + "thumbnail": "https://i.ytimg.com/vi/sRl02nXVMhw/default.jpg" + }, + { + "type": "youtube", + "id": "dQw4w9WgXcQ", + "url": "https://www.youtube.com/v/dQw4w9WgXcQ", + "thumbnail": "https://i.ytimg.com/vi/dQw4w9WgXcQ/default.jpg" + }, + { + "type": "youtube", + "id": "Hy8kmNEo1i8", + "url": "https://www.youtube.com/v/Hy8kmNEo1i8", + "thumbnail": "https://i.ytimg.com/vi/Hy8kmNEo1i8/default.jpg" + }, + { + "type": "youtube", + "id": "zA52uNzx7Y4", + "url": "https://www.youtube.com/v/zA52uNzx7Y4", + "thumbnail": "https://i.ytimg.com/vi/zA52uNzx7Y4/default.jpg" + }, + { + "type": "youtube", + "id": "9bZkp7q19f0", + "url": "https://www.youtube.com/v/9bZkp7q19f0", + "thumbnail": "https://i.ytimg.com/vi/9bZkp7q19f0/default.jpg" + } + ] +} diff --git a/spec/saved_playlist_object.md b/spec/saved_playlist_object.md new file mode 100644 index 0000000..d38aeb8 --- /dev/null +++ b/spec/saved_playlist_object.md @@ -0,0 +1,55 @@ +# SavedPlaylist object + +#### > `"type"` + +The type of the service this entry refers to. + +This field is a short and meaningful identifier that prevents the parser from +trying to guess the name of the service from the `url` present in this object. + +#### > `"id"` + +The ID of the playlist. + +The format if this ID is service-dependant. + +The only requirement being the uniqueness of the ID in the service. +E.g: All youtube playlist IDs (starting with `PL`) are unique to eachother. + +#### > `"title"` + +The playlist's title. + +#### > `"author"` + +The name of the channel/user who created the playlist. + +#### > `"url"` + +The URL used to access the video. + +This URL SHOULD NOT contain query parameters, unless required by the service +(e.g youtube playlists). + +#### > `"thumbnail"` + +The URL to the playlist's thumbnail. + +This field CAN be omitted, e.g if the playlist doesn't have a thumbnail, or +if the exporter doesn't store this metadata. + +If present, it MUST represent the original URL to the thumbnail as found on +the service mentioned in `type`. + +### Overview + +```javascript +{ + "type": String, + "id": String, + "title": String, + "author": String, + "url": String, + "thumbnail": String | null +} +``` diff --git a/spec/subscription_object.md b/spec/subscription_object.md new file mode 100644 index 0000000..8c4ef4f --- /dev/null +++ b/spec/subscription_object.md @@ -0,0 +1,49 @@ +# Subscription object + +#### > `"type"` + +The type of the service this entry refers to. + +This field is a short and meaningful identifier that prevents the parser from +trying to guess the name of the service from the `url` present in this object. + +#### > `"id"` + +The ID of the subscribed channel. + +The format if this ID is service-dependant. + +The only requirement being the uniqueness of the ID in the service. +E.g: All youtube channel IDs (starting with `UC`) are unique to eachother. + +#### > `"name"` + +The name of the subscribed channel. + +#### > `"url"` + +The URL used to access the user subscribed channel. + +This URL SHOULD NOT contain query parameters. + +#### > `"thumbnail"` + +The URL to the user/channel's avatar. + +This field CAN be omitted, e.g if the channel doesn't have an avatar, or +if the exporter doesn't store this metadata. + +If present, it MUST represent the original URL to the avatar as found on +the service mentioned in `type`. + +### Overview + +```javascript +{ + "type": String, + "id": String, + "name": String, + "url": String, + "thumbnail": String | null +} +``` diff --git a/spec/video_object.md b/spec/video_object.md new file mode 100644 index 0000000..b226d96 --- /dev/null +++ b/spec/video_object.md @@ -0,0 +1,44 @@ +# Video object + +#### > `"type"` + +The type of the service this entry refers to. + +This field is a short and meaningful identifier that prevents the parser from +trying to guess the name of the service from the `url` present in this object. + +#### > `"id"` + +The ID of the video. + +The format if this ID is service-dependant. + +The only requirement being the uniqueness of the ID in the service. +E.g: All youtube video IDs (11 character long) are unique to eachother. + +#### > `"url"` + +The URL used to access the video. + +This URL SHOULD NOT contain query parameters. + +#### > `"thumbnail"` + +The URL to the video's thumbnail. + +This field CAN be omitted, e.g if the video doesn't have a thumbnail, or +if the exporter doesn't store this metadata. + +If present, it MUST represent the original URL to the thumbnail as found on +the service mentioned in `type`. + +### Overview + +```javascript +{ + "type": String, + "id": String, + "url": String, + "thumbnail": String | null +} +```