From 8c22e6a640d458c5447fd8a841a6694f077864e4 Mon Sep 17 00:00:00 2001 From: ChunkyProgrammer <78101139+ChunkyProgrammer@users.noreply.github.com> Date: Tue, 14 Nov 2023 22:39:33 -0500 Subject: [PATCH 1/3] use start time and endtime for clips --- src/invidious/routes/watch.cr | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/invidious/routes/watch.cr b/src/invidious/routes/watch.cr index 3d935f0a..1b1169d8 100644 --- a/src/invidious/routes/watch.cr +++ b/src/invidious/routes/watch.cr @@ -275,6 +275,22 @@ module Invidious::Routes::Watch return error_template(400, "Invalid clip ID") if response["error"]? if video_id = response.dig?("endpoint", "watchEndpoint", "videoId") + if params = response.dig?("endpoint", "watchEndpoint", "params").try &.as_s + decoded_protobuf = params.try { |i| URI.decode_www_form(i) } + .try { |i| Base64.decode(i) } + .try { |i| IO::Memory.new(i) } + .try { |i| Protodec::Any.parse(i) } + + start_time = decoded_protobuf + .try(&.["50:0:embedded"]["2:1:varint"].as_i64) + + end_time = decoded_protobuf + .try(&.["50:0:embedded"]["3:2:varint"].as_i64) + + env.params.query["start"] = (start_time / 1000).to_s + env.params.query["end"] = (end_time / 1000).to_s + end + return env.redirect "/watch?v=#{video_id}&#{env.params.query}" else return error_template(404, "The requested clip doesn't exist") From b344d98c25185ca4e163eeda128b9fc68ff865b6 Mon Sep 17 00:00:00 2001 From: ChunkyProgrammer <78101139+ChunkyProgrammer@users.noreply.github.com> Date: Tue, 14 Nov 2023 23:35:11 -0500 Subject: [PATCH 2/3] Add API endpoint for Clips --- src/invidious/routes/api/v1/videos.cr | 43 +++++++++++++++++++++++++++ src/invidious/routes/watch.cr | 16 ++-------- src/invidious/routing.cr | 1 + src/invidious/videos/clip.cr | 20 +++++++++++++ 4 files changed, 67 insertions(+), 13 deletions(-) create mode 100644 src/invidious/videos/clip.cr diff --git a/src/invidious/routes/api/v1/videos.cr b/src/invidious/routes/api/v1/videos.cr index 1017ac9d..9281f4dd 100644 --- a/src/invidious/routes/api/v1/videos.cr +++ b/src/invidious/routes/api/v1/videos.cr @@ -363,4 +363,47 @@ module Invidious::Routes::API::V1::Videos end end end + + def self.clips(env) + locale = env.get("preferences").as(Preferences).locale + + env.response.content_type = "application/json" + + clip_id = env.params.url["id"] + region = env.params.query["region"]? + proxy = {"1", "true"}.any? &.== env.params.query["local"]? + + response = YoutubeAPI.resolve_url("https://www.youtube.com/clip/#{clip_id}") + return error_json(400, "Invalid clip ID") if response["error"]? + + video_id = response.dig?("endpoint", "watchEndpoint", "videoId").try &.as_s + return error_json(400, "Invalid clip ID") if video_id.nil? + + start_time = nil + end_time = nil + clip_title = nil + + if params = response.dig?("endpoint", "watchEndpoint", "params").try &.as_s + start_time, end_time, clip_title = parse_clip_parameters(params) + end + + begin + video = get_video(video_id, region: region) + rescue ex : NotFoundException + return error_json(404, ex) + rescue ex + return error_json(500, ex) + end + + return JSON.build do |json| + json.object do + json.field "startTime", start_time + json.field "endTime", end_time + json.field "clipTitle", clip_title + json.field "video" do + Invidious::JSONify::APIv1.video(video, json, locale: locale, proxy: proxy) + end + end + end + end end diff --git a/src/invidious/routes/watch.cr b/src/invidious/routes/watch.cr index 1b1169d8..1cba86f6 100644 --- a/src/invidious/routes/watch.cr +++ b/src/invidious/routes/watch.cr @@ -276,19 +276,9 @@ module Invidious::Routes::Watch if video_id = response.dig?("endpoint", "watchEndpoint", "videoId") if params = response.dig?("endpoint", "watchEndpoint", "params").try &.as_s - decoded_protobuf = params.try { |i| URI.decode_www_form(i) } - .try { |i| Base64.decode(i) } - .try { |i| IO::Memory.new(i) } - .try { |i| Protodec::Any.parse(i) } - - start_time = decoded_protobuf - .try(&.["50:0:embedded"]["2:1:varint"].as_i64) - - end_time = decoded_protobuf - .try(&.["50:0:embedded"]["3:2:varint"].as_i64) - - env.params.query["start"] = (start_time / 1000).to_s - env.params.query["end"] = (end_time / 1000).to_s + start_time, end_time, _ = parse_clip_parameters(params) + env.params.query["start"] = start_time.to_s + env.params.query["end"] = end_time.to_s end return env.redirect "/watch?v=#{video_id}&#{env.params.query}" diff --git a/src/invidious/routing.cr b/src/invidious/routing.cr index d6bd991c..ba05da19 100644 --- a/src/invidious/routing.cr +++ b/src/invidious/routing.cr @@ -235,6 +235,7 @@ module Invidious::Routing get "/api/v1/captions/:id", {{namespace}}::Videos, :captions get "/api/v1/annotations/:id", {{namespace}}::Videos, :annotations get "/api/v1/comments/:id", {{namespace}}::Videos, :comments + get "/api/v1/clips/:id", {{namespace}}::Videos, :clips # Feeds get "/api/v1/trending", {{namespace}}::Feeds, :trending diff --git a/src/invidious/videos/clip.cr b/src/invidious/videos/clip.cr new file mode 100644 index 00000000..47f108a3 --- /dev/null +++ b/src/invidious/videos/clip.cr @@ -0,0 +1,20 @@ +require "json" + +# returns start_time, end_time and clip_title +def parse_clip_parameters(params) : {Float64, Float64, String} + decoded_protobuf = params.try { |i| URI.decode_www_form(i) } + .try { |i| Base64.decode(i) } + .try { |i| IO::Memory.new(i) } + .try { |i| Protodec::Any.parse(i) } + + start_time = decoded_protobuf + .try(&.["50:0:embedded"]["2:1:varint"].as_i64) + + end_time = decoded_protobuf + .try(&.["50:0:embedded"]["3:2:varint"].as_i64) + + clip_title = decoded_protobuf + .try(&.["50:0:embedded"]["4:3:string"].as_s) + + return (start_time / 1000), (end_time / 1000), clip_title +end From 7da4a7f72b0e328f72aff884605a21c4ffe7cb04 Mon Sep 17 00:00:00 2001 From: ChunkyProgrammer <78101139+ChunkyProgrammer@users.noreply.github.com> Date: Tue, 19 Dec 2023 22:37:48 -0500 Subject: [PATCH 3/3] add null safety to clip parsing --- src/invidious/routes/watch.cr | 4 ++-- src/invidious/videos/clip.cr | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/invidious/routes/watch.cr b/src/invidious/routes/watch.cr index 1cba86f6..aabe8dfc 100644 --- a/src/invidious/routes/watch.cr +++ b/src/invidious/routes/watch.cr @@ -277,8 +277,8 @@ module Invidious::Routes::Watch if video_id = response.dig?("endpoint", "watchEndpoint", "videoId") if params = response.dig?("endpoint", "watchEndpoint", "params").try &.as_s start_time, end_time, _ = parse_clip_parameters(params) - env.params.query["start"] = start_time.to_s - env.params.query["end"] = end_time.to_s + env.params.query["start"] = start_time.to_s if start_time != nil + env.params.query["end"] = end_time.to_s if end_time != nil end return env.redirect "/watch?v=#{video_id}&#{env.params.query}" diff --git a/src/invidious/videos/clip.cr b/src/invidious/videos/clip.cr index 47f108a3..29c57182 100644 --- a/src/invidious/videos/clip.cr +++ b/src/invidious/videos/clip.cr @@ -1,7 +1,7 @@ require "json" # returns start_time, end_time and clip_title -def parse_clip_parameters(params) : {Float64, Float64, String} +def parse_clip_parameters(params) : {Float64?, Float64?, String?} decoded_protobuf = params.try { |i| URI.decode_www_form(i) } .try { |i| Base64.decode(i) } .try { |i| IO::Memory.new(i) } @@ -9,12 +9,14 @@ def parse_clip_parameters(params) : {Float64, Float64, String} start_time = decoded_protobuf .try(&.["50:0:embedded"]["2:1:varint"].as_i64) + .try { |i| i/1000 } end_time = decoded_protobuf .try(&.["50:0:embedded"]["3:2:varint"].as_i64) + .try { |i| i/1000 } clip_title = decoded_protobuf .try(&.["50:0:embedded"]["4:3:string"].as_s) - return (start_time / 1000), (end_time / 1000), clip_title + return start_time, end_time, clip_title end