summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSamantaz Fox <coding@samantaz.fr>2022-08-23 19:03:09 +0200
committerSamantaz Fox <coding@samantaz.fr>2022-10-31 20:09:04 +0100
commit87a5d70062b8f4b2b942d027f8c4cf0bb30907eb (patch)
tree81d8613d57f3154849b0593857d0365f37fcebe2 /src
parentae03ed7bf7faeefa3d8e8bf5b6382f56ef154fe8 (diff)
downloadinvidious-87a5d70062b8f4b2b942d027f8c4cf0bb30907eb.tar.gz
invidious-87a5d70062b8f4b2b942d027f8c4cf0bb30907eb.tar.bz2
invidious-87a5d70062b8f4b2b942d027f8c4cf0bb30907eb.zip
videos: move API's JSON structure to a dedicated module
Diffstat (limited to 'src')
-rw-r--r--src/invidious.cr2
-rw-r--r--src/invidious/channels/channels.cr2
-rw-r--r--src/invidious/channels/community.cr2
-rw-r--r--src/invidious/helpers/serialized_yt_data.cr4
-rw-r--r--src/invidious/jsonify/api_v1/video_json.cr255
-rw-r--r--src/invidious/playlists.cr2
-rw-r--r--src/invidious/routes/api/v1/misc.cr2
-rw-r--r--src/invidious/routes/api/v1/videos.cr2
-rw-r--r--src/invidious/videos.cr256
9 files changed, 272 insertions, 255 deletions
diff --git a/src/invidious.cr b/src/invidious.cr
index 8df0c0cd..2874cc71 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -39,6 +39,8 @@ require "./invidious/yt_backend/*"
require "./invidious/frontend/*"
require "./invidious/videos/*"
+require "./invidious/jsonify/**"
+
require "./invidious/*"
require "./invidious/channels/*"
require "./invidious/user/*"
diff --git a/src/invidious/channels/channels.cr b/src/invidious/channels/channels.cr
index e0459cc3..e3d3d9ee 100644
--- a/src/invidious/channels/channels.cr
+++ b/src/invidious/channels/channels.cr
@@ -29,7 +29,7 @@ struct ChannelVideo
json.field "title", self.title
json.field "videoId", self.id
json.field "videoThumbnails" do
- generate_thumbnails(json, self.id)
+ Invidious::JSONify::APIv1.thumbnails(json, self.id)
end
json.field "lengthSeconds", self.length_seconds
diff --git a/src/invidious/channels/community.cr b/src/invidious/channels/community.cr
index 2a2c74aa..8e300288 100644
--- a/src/invidious/channels/community.cr
+++ b/src/invidious/channels/community.cr
@@ -138,7 +138,7 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode)
json.field "title", video_title
json.field "videoId", video_id
json.field "videoThumbnails" do
- generate_thumbnails(json, video_id)
+ Invidious::JSONify::APIv1.thumbnails(json, video_id)
end
json.field "lengthSeconds", decode_length_seconds(attachment["lengthText"]["simpleText"].as_s)
diff --git a/src/invidious/helpers/serialized_yt_data.cr b/src/invidious/helpers/serialized_yt_data.cr
index 3918bd13..c52e2a0d 100644
--- a/src/invidious/helpers/serialized_yt_data.cr
+++ b/src/invidious/helpers/serialized_yt_data.cr
@@ -76,7 +76,7 @@ struct SearchVideo
json.field "authorUrl", "/channel/#{self.ucid}"
json.field "videoThumbnails" do
- generate_thumbnails(json, self.id)
+ Invidious::JSONify::APIv1.thumbnails(json, self.id)
end
json.field "description", html_to_content(self.description_html)
@@ -155,7 +155,7 @@ struct SearchPlaylist
json.field "lengthSeconds", video.length_seconds
json.field "videoThumbnails" do
- generate_thumbnails(json, video.id)
+ Invidious::JSONify::APIv1.thumbnails(json, video.id)
end
end
end
diff --git a/src/invidious/jsonify/api_v1/video_json.cr b/src/invidious/jsonify/api_v1/video_json.cr
new file mode 100644
index 00000000..1082f6d3
--- /dev/null
+++ b/src/invidious/jsonify/api_v1/video_json.cr
@@ -0,0 +1,255 @@
+require "json"
+
+module Invidious::JSONify::APIv1
+ extend self
+
+ def video(video : Video, json : JSON::Builder, *, locale : String?)
+ json.object do
+ json.field "type", video.video_type
+
+ json.field "title", video.title
+ json.field "videoId", video.id
+
+ json.field "error", video.info["reason"] if video.info["reason"]?
+
+ json.field "videoThumbnails" do
+ self.thumbnails(json, video.id)
+ end
+ json.field "storyboards" do
+ self.storyboards(json, video.id, video.storyboards)
+ end
+
+ json.field "description", video.description
+ json.field "descriptionHtml", video.description_html
+ json.field "published", video.published.to_unix
+ json.field "publishedText", translate(locale, "`x` ago", recode_date(video.published, locale))
+ json.field "keywords", video.keywords
+
+ json.field "viewCount", video.views
+ json.field "likeCount", video.likes
+ json.field "dislikeCount", 0_i64
+
+ json.field "paid", video.paid
+ json.field "premium", video.premium
+ json.field "isFamilyFriendly", video.is_family_friendly
+ json.field "allowedRegions", video.allowed_regions
+ json.field "genre", video.genre
+ json.field "genreUrl", video.genre_url
+
+ json.field "author", video.author
+ json.field "authorId", video.ucid
+ json.field "authorUrl", "/channel/#{video.ucid}"
+
+ json.field "authorThumbnails" do
+ json.array do
+ qualities = {32, 48, 76, 100, 176, 512}
+
+ qualities.each do |quality|
+ json.object do
+ json.field "url", video.author_thumbnail.gsub(/=s\d+/, "=s#{quality}")
+ json.field "width", quality
+ json.field "height", quality
+ end
+ end
+ end
+ end
+
+ json.field "subCountText", video.sub_count_text
+
+ json.field "lengthSeconds", video.length_seconds
+ json.field "allowRatings", video.allow_ratings
+ json.field "rating", 0_i64
+ json.field "isListed", video.is_listed
+ json.field "liveNow", video.live_now
+ json.field "isUpcoming", video.is_upcoming
+
+ if video.premiere_timestamp
+ json.field "premiereTimestamp", video.premiere_timestamp.try &.to_unix
+ end
+
+ if hlsvp = video.hls_manifest_url
+ hlsvp = hlsvp.gsub("https://manifest.googlevideo.com", HOST_URL)
+ json.field "hlsUrl", hlsvp
+ end
+
+ json.field "dashUrl", "#{HOST_URL}/api/manifest/dash/id/#{video.id}"
+
+ json.field "adaptiveFormats" do
+ json.array do
+ video.adaptive_fmts.each do |fmt|
+ json.object do
+ # Only available on regular videos, not livestreams/OTF streams
+ if init_range = fmt["initRange"]?
+ json.field "init", "#{init_range["start"]}-#{init_range["end"]}"
+ end
+ if index_range = fmt["indexRange"]?
+ json.field "index", "#{index_range["start"]}-#{index_range["end"]}"
+ end
+
+ # Not available on MPEG-4 Timed Text (`text/mp4`) streams (livestreams only)
+ json.field "bitrate", fmt["bitrate"].as_i.to_s if fmt["bitrate"]?
+
+ json.field "url", fmt["url"]
+ json.field "itag", fmt["itag"].as_i.to_s
+ json.field "type", fmt["mimeType"]
+ json.field "clen", fmt["contentLength"]? || "-1"
+ json.field "lmt", fmt["lastModified"]
+ json.field "projectionType", fmt["projectionType"]
+
+ if fmt_info = Invidious::Videos::Formats.itag_to_metadata?(fmt["itag"])
+ fps = fmt_info["fps"]?.try &.to_i || fmt["fps"]?.try &.as_i || 30
+ json.field "fps", fps
+ json.field "container", fmt_info["ext"]
+ json.field "encoding", fmt_info["vcodec"]? || fmt_info["acodec"]
+
+ if fmt_info["height"]?
+ json.field "resolution", "#{fmt_info["height"]}p"
+
+ quality_label = "#{fmt_info["height"]}p"
+ if fps > 30
+ quality_label += "60"
+ end
+ json.field "qualityLabel", quality_label
+
+ if fmt_info["width"]?
+ json.field "size", "#{fmt_info["width"]}x#{fmt_info["height"]}"
+ end
+ end
+ end
+
+ # Livestream chunk infos
+ json.field "targetDurationSec", fmt["targetDurationSec"].as_i if fmt.has_key?("targetDurationSec")
+ json.field "maxDvrDurationSec", fmt["maxDvrDurationSec"].as_i if fmt.has_key?("maxDvrDurationSec")
+
+ # Audio-related data
+ json.field "audioQuality", fmt["audioQuality"] if fmt.has_key?("audioQuality")
+ json.field "audioSampleRate", fmt["audioSampleRate"].as_s.to_i if fmt.has_key?("audioSampleRate")
+ json.field "audioChannels", fmt["audioChannels"] if fmt.has_key?("audioChannels")
+
+ # Extra misc stuff
+ json.field "colorInfo", fmt["colorInfo"] if fmt.has_key?("colorInfo")
+ json.field "captionTrack", fmt["captionTrack"] if fmt.has_key?("captionTrack")
+ end
+ end
+ end
+ end
+
+ json.field "formatStreams" do
+ json.array do
+ video.fmt_stream.each do |fmt|
+ json.object do
+ json.field "url", fmt["url"]
+ json.field "itag", fmt["itag"].as_i.to_s
+ json.field "type", fmt["mimeType"]
+ json.field "quality", fmt["quality"]
+
+ fmt_info = Invidious::Videos::Formats.itag_to_metadata?(fmt["itag"])
+ if fmt_info
+ fps = fmt_info["fps"]?.try &.to_i || fmt["fps"]?.try &.as_i || 30
+ json.field "fps", fps
+ json.field "container", fmt_info["ext"]
+ json.field "encoding", fmt_info["vcodec"]? || fmt_info["acodec"]
+
+ if fmt_info["height"]?
+ json.field "resolution", "#{fmt_info["height"]}p"
+
+ quality_label = "#{fmt_info["height"]}p"
+ if fps > 30
+ quality_label += "60"
+ end
+ json.field "qualityLabel", quality_label
+
+ if fmt_info["width"]?
+ json.field "size", "#{fmt_info["width"]}x#{fmt_info["height"]}"
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+
+ json.field "captions" do
+ json.array do
+ video.captions.each do |caption|
+ json.object do
+ json.field "label", caption.name
+ json.field "language_code", caption.language_code
+ json.field "url", "/api/v1/captions/#{video.id}?label=#{URI.encode_www_form(caption.name)}"
+ end
+ end
+ end
+ end
+
+ json.field "recommendedVideos" do
+ json.array do
+ video.related_videos.each do |rv|
+ if rv["id"]?
+ json.object do
+ json.field "videoId", rv["id"]
+ json.field "title", rv["title"]
+ json.field "videoThumbnails" do
+ self.thumbnails(json, rv["id"])
+ end
+
+ json.field "author", rv["author"]
+ json.field "authorUrl", "/channel/#{rv["ucid"]?}"
+ json.field "authorId", rv["ucid"]?
+ if rv["author_thumbnail"]?
+ json.field "authorThumbnails" do
+ json.array do
+ qualities = {32, 48, 76, 100, 176, 512}
+
+ qualities.each do |quality|
+ json.object do
+ json.field "url", rv["author_thumbnail"].gsub(/s\d+-/, "s#{quality}-")
+ json.field "width", quality
+ json.field "height", quality
+ end
+ end
+ end
+ end
+ end
+
+ json.field "lengthSeconds", rv["length_seconds"]?.try &.to_i
+ json.field "viewCountText", rv["short_view_count"]?
+ json.field "viewCount", rv["view_count"]?.try &.empty? ? nil : rv["view_count"].to_i64
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+
+ def thumbnails(json, id)
+ json.array do
+ build_thumbnails(id).each do |thumbnail|
+ json.object do
+ json.field "quality", thumbnail[:name]
+ json.field "url", "#{thumbnail[:host]}/vi/#{id}/#{thumbnail["url"]}.jpg"
+ json.field "width", thumbnail[:width]
+ json.field "height", thumbnail[:height]
+ end
+ end
+ end
+ end
+
+ def storyboards(json, id, storyboards)
+ json.array do
+ storyboards.each do |storyboard|
+ json.object do
+ json.field "url", "/api/v1/storyboards/#{id}?width=#{storyboard[:width]}&height=#{storyboard[:height]}"
+ json.field "templateUrl", storyboard[:url]
+ json.field "width", storyboard[:width]
+ json.field "height", storyboard[:height]
+ json.field "count", storyboard[:count]
+ json.field "interval", storyboard[:interval]
+ json.field "storyboardWidth", storyboard[:storyboard_width]
+ json.field "storyboardHeight", storyboard[:storyboard_height]
+ json.field "storyboardCount", storyboard[:storyboard_count]
+ end
+ end
+ end
+ end
+end
diff --git a/src/invidious/playlists.cr b/src/invidious/playlists.cr
index c4eb7507..57f1f53e 100644
--- a/src/invidious/playlists.cr
+++ b/src/invidious/playlists.cr
@@ -56,7 +56,7 @@ struct PlaylistVideo
json.field "authorUrl", "/channel/#{self.ucid}"
json.field "videoThumbnails" do
- generate_thumbnails(json, self.id)
+ Invidious::JSONify::APIv1.thumbnails(json, self.id)
end
if index
diff --git a/src/invidious/routes/api/v1/misc.cr b/src/invidious/routes/api/v1/misc.cr
index 844fedb8..43d360e6 100644
--- a/src/invidious/routes/api/v1/misc.cr
+++ b/src/invidious/routes/api/v1/misc.cr
@@ -124,7 +124,7 @@ module Invidious::Routes::API::V1::Misc
json.field "videoThumbnails" do
json.array do
- generate_thumbnails(json, video.id)
+ Invidious::JSONify::APIv1.thumbnails(json, video.id)
end
end
diff --git a/src/invidious/routes/api/v1/videos.cr b/src/invidious/routes/api/v1/videos.cr
index 1b7b4fa7..6f1f5916 100644
--- a/src/invidious/routes/api/v1/videos.cr
+++ b/src/invidious/routes/api/v1/videos.cr
@@ -185,7 +185,7 @@ module Invidious::Routes::API::V1::Videos
response = JSON.build do |json|
json.object do
json.field "storyboards" do
- generate_storyboards(json, id, storyboards)
+ Invidious::JSONify::APIv1.storyboards(json, id, storyboards)
end
end
end
diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr
index 9b19bc2a..fcc9a8a4 100644
--- a/src/invidious/videos.cr
+++ b/src/invidious/videos.cr
@@ -31,234 +31,25 @@ struct Video
end
end
- def to_json(locale : String?, json : JSON::Builder)
- json.object do
- json.field "type", self.video_type
-
- json.field "title", self.title
- json.field "videoId", self.id
-
- json.field "error", info["reason"] if info["reason"]?
-
- json.field "videoThumbnails" do
- generate_thumbnails(json, self.id)
- end
- json.field "storyboards" do
- generate_storyboards(json, self.id, self.storyboards)
- end
-
- json.field "description", self.description
- json.field "descriptionHtml", self.description_html
- json.field "published", self.published.to_unix
- json.field "publishedText", translate(locale, "`x` ago", recode_date(self.published, locale))
- json.field "keywords", self.keywords
-
- json.field "viewCount", self.views
- json.field "likeCount", self.likes
- json.field "dislikeCount", 0_i64
-
- json.field "paid", self.paid
- json.field "premium", self.premium
- json.field "isFamilyFriendly", self.is_family_friendly
- json.field "allowedRegions", self.allowed_regions
- json.field "genre", self.genre
- json.field "genreUrl", self.genre_url
-
- json.field "author", self.author
- json.field "authorId", self.ucid
- json.field "authorUrl", "/channel/#{self.ucid}"
-
- json.field "authorThumbnails" do
- json.array do
- qualities = {32, 48, 76, 100, 176, 512}
-
- qualities.each do |quality|
- json.object do
- json.field "url", self.author_thumbnail.gsub(/=s\d+/, "=s#{quality}")
- json.field "width", quality
- json.field "height", quality
- end
- end
- end
- end
-
- json.field "subCountText", self.sub_count_text
-
- json.field "lengthSeconds", self.length_seconds
- json.field "allowRatings", self.allow_ratings
- json.field "rating", 0_i64
- json.field "isListed", self.is_listed
- json.field "liveNow", self.live_now
- json.field "isUpcoming", self.is_upcoming
-
- if self.premiere_timestamp
- json.field "premiereTimestamp", self.premiere_timestamp.try &.to_unix
- end
-
- if hlsvp = self.hls_manifest_url
- hlsvp = hlsvp.gsub("https://manifest.googlevideo.com", HOST_URL)
- json.field "hlsUrl", hlsvp
- end
-
- json.field "dashUrl", "#{HOST_URL}/api/manifest/dash/id/#{id}"
-
- json.field "adaptiveFormats" do
- json.array do
- self.adaptive_fmts.each do |fmt|
- json.object do
- # Only available on regular videos, not livestreams/OTF streams
- if init_range = fmt["initRange"]?
- json.field "init", "#{init_range["start"]}-#{init_range["end"]}"
- end
- if index_range = fmt["indexRange"]?
- json.field "index", "#{index_range["start"]}-#{index_range["end"]}"
- end
-
- # Not available on MPEG-4 Timed Text (`text/mp4`) streams (livestreams only)
- json.field "bitrate", fmt["bitrate"].as_i.to_s if fmt["bitrate"]?
-
- json.field "url", fmt["url"]
- json.field "itag", fmt["itag"].as_i.to_s
- json.field "type", fmt["mimeType"]
- json.field "clen", fmt["contentLength"]? || "-1"
- json.field "lmt", fmt["lastModified"]
- json.field "projectionType", fmt["projectionType"]
-
- if fmt_info = Invidious::Videos::Formats.itag_to_metadata?(fmt["itag"])
- fps = fmt_info["fps"]?.try &.to_i || fmt["fps"]?.try &.as_i || 30
- json.field "fps", fps
- json.field "container", fmt_info["ext"]
- json.field "encoding", fmt_info["vcodec"]? || fmt_info["acodec"]
-
- if fmt_info["height"]?
- json.field "resolution", "#{fmt_info["height"]}p"
-
- quality_label = "#{fmt_info["height"]}p"
- if fps > 30
- quality_label += "60"
- end
- json.field "qualityLabel", quality_label
-
- if fmt_info["width"]?
- json.field "size", "#{fmt_info["width"]}x#{fmt_info["height"]}"
- end
- end
- end
-
- # Livestream chunk infos
- json.field "targetDurationSec", fmt["targetDurationSec"].as_i if fmt.has_key?("targetDurationSec")
- json.field "maxDvrDurationSec", fmt["maxDvrDurationSec"].as_i if fmt.has_key?("maxDvrDurationSec")
-
- # Audio-related data
- json.field "audioQuality", fmt["audioQuality"] if fmt.has_key?("audioQuality")
- json.field "audioSampleRate", fmt["audioSampleRate"].as_s.to_i if fmt.has_key?("audioSampleRate")
- json.field "audioChannels", fmt["audioChannels"] if fmt.has_key?("audioChannels")
-
- # Extra misc stuff
- json.field "colorInfo", fmt["colorInfo"] if fmt.has_key?("colorInfo")
- json.field "captionTrack", fmt["captionTrack"] if fmt.has_key?("captionTrack")
- end
- end
- end
- end
-
- json.field "formatStreams" do
- json.array do
- self.fmt_stream.each do |fmt|
- json.object do
- json.field "url", fmt["url"]
- json.field "itag", fmt["itag"].as_i.to_s
- json.field "type", fmt["mimeType"]
- json.field "quality", fmt["quality"]
-
- fmt_info = Invidious::Videos::Formats.itag_to_metadata?(fmt["itag"])
- if fmt_info
- fps = fmt_info["fps"]?.try &.to_i || fmt["fps"]?.try &.as_i || 30
- json.field "fps", fps
- json.field "container", fmt_info["ext"]
- json.field "encoding", fmt_info["vcodec"]? || fmt_info["acodec"]
-
- if fmt_info["height"]?
- json.field "resolution", "#{fmt_info["height"]}p"
-
- quality_label = "#{fmt_info["height"]}p"
- if fps > 30
- quality_label += "60"
- end
- json.field "qualityLabel", quality_label
-
- if fmt_info["width"]?
- json.field "size", "#{fmt_info["width"]}x#{fmt_info["height"]}"
- end
- end
- end
- end
- end
- end
- end
+ # Methods for API v1 JSON
- json.field "captions" do
- json.array do
- self.captions.each do |caption|
- json.object do
- json.field "label", caption.name
- json.field "language_code", caption.language_code
- json.field "url", "/api/v1/captions/#{id}?label=#{URI.encode_www_form(caption.name)}"
- end
- end
- end
- end
-
- json.field "recommendedVideos" do
- json.array do
- self.related_videos.each do |rv|
- if rv["id"]?
- json.object do
- json.field "videoId", rv["id"]
- json.field "title", rv["title"]
- json.field "videoThumbnails" do
- generate_thumbnails(json, rv["id"])
- end
-
- json.field "author", rv["author"]
- json.field "authorUrl", "/channel/#{rv["ucid"]?}"
- json.field "authorId", rv["ucid"]?
- if rv["author_thumbnail"]?
- json.field "authorThumbnails" do
- json.array do
- qualities = {32, 48, 76, 100, 176, 512}
-
- qualities.each do |quality|
- json.object do
- json.field "url", rv["author_thumbnail"].gsub(/s\d+-/, "s#{quality}-")
- json.field "width", quality
- json.field "height", quality
- end
- end
- end
- end
- end
-
- json.field "lengthSeconds", rv["length_seconds"]?.try &.to_i
- json.field "viewCountText", rv["short_view_count"]?
- json.field "viewCount", rv["view_count"]?.try &.empty? ? nil : rv["view_count"].to_i64
- end
- end
- end
- end
- end
- end
+ def to_json(locale : String?, json : JSON::Builder)
+ Invidious::JSONify::APIv1.video(self, json, locale: locale)
end
# TODO: remove the locale and follow the crystal convention
def to_json(locale : String?, _json : Nil)
- JSON.build { |json| to_json(locale, json) }
+ JSON.build do |json|
+ Invidious::JSONify::APIv1.video(self, json, locale: locale)
+ end
end
def to_json(json : JSON::Builder | Nil = nil)
to_json(nil, json)
end
+ # Misc methods
+
def video_type : VideoType
video_type = info["videoType"]?.try &.as_s || "video"
return VideoType.parse?(video_type) || VideoType::Video
@@ -631,34 +422,3 @@ def build_thumbnails(id)
{host: HOST_URL, height: 90, width: 120, name: "end", url: "3"},
}
end
-
-def generate_thumbnails(json, id)
- json.array do
- build_thumbnails(id).each do |thumbnail|
- json.object do
- json.field "quality", thumbnail[:name]
- json.field "url", "#{thumbnail[:host]}/vi/#{id}/#{thumbnail["url"]}.jpg"
- json.field "width", thumbnail[:width]
- json.field "height", thumbnail[:height]
- end
- end
- end
-end
-
-def generate_storyboards(json, id, storyboards)
- json.array do
- storyboards.each do |storyboard|
- json.object do
- json.field "url", "/api/v1/storyboards/#{id}?width=#{storyboard[:width]}&height=#{storyboard[:height]}"
- json.field "templateUrl", storyboard[:url]
- json.field "width", storyboard[:width]
- json.field "height", storyboard[:height]
- json.field "count", storyboard[:count]
- json.field "interval", storyboard[:interval]
- json.field "storyboardWidth", storyboard[:storyboard_width]
- json.field "storyboardHeight", storyboard[:storyboard_height]
- json.field "storyboardCount", storyboard[:storyboard_count]
- end
- end
- end
-end