mirror of
https://github.com/iv-org/invidious.git
synced 2026-06-02 12:54:25 +00:00
Encapsulate videos parser and clip functions inside it's own Invidious::Videos::Parser and Invidious::Videos::Clip module (#5745)
Part of https://github.com/iv-org/invidious/issues/5744
This commit is contained in:
@@ -7,7 +7,7 @@ Spectator.describe "parse_video_info" do
|
|||||||
_next = load_mock("video/regular_mrbeast.next")
|
_next = load_mock("video/regular_mrbeast.next")
|
||||||
|
|
||||||
raw_data = _player.merge!(_next)
|
raw_data = _player.merge!(_next)
|
||||||
info = parse_video_info("2isYuQZMbdU", raw_data)
|
info = Invidious::Videos::Parser.parse_video_info("2isYuQZMbdU", raw_data)
|
||||||
|
|
||||||
# Some basic verifications
|
# Some basic verifications
|
||||||
expect(typeof(info)).to eq(Hash(String, JSON::Any))
|
expect(typeof(info)).to eq(Hash(String, JSON::Any))
|
||||||
@@ -88,7 +88,7 @@ Spectator.describe "parse_video_info" do
|
|||||||
_next = load_mock("video/regular_no-description.next")
|
_next = load_mock("video/regular_no-description.next")
|
||||||
|
|
||||||
raw_data = _player.merge!(_next)
|
raw_data = _player.merge!(_next)
|
||||||
info = parse_video_info("iuevw6218F0", raw_data)
|
info = Invidious::Videos::Parser.parse_video_info("iuevw6218F0", raw_data)
|
||||||
|
|
||||||
# Some basic verifications
|
# Some basic verifications
|
||||||
expect(typeof(info)).to eq(Hash(String, JSON::Any))
|
expect(typeof(info)).to eq(Hash(String, JSON::Any))
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Spectator.describe "parse_video_info" do
|
|||||||
_next = load_mock("video/scheduled_live_PBD-Podcast.next")
|
_next = load_mock("video/scheduled_live_PBD-Podcast.next")
|
||||||
|
|
||||||
raw_data = _player.merge!(_next)
|
raw_data = _player.merge!(_next)
|
||||||
info = parse_video_info("N-yVic7BbY0", raw_data)
|
info = Invidious::Videos::Parser.parse_video_info("N-yVic7BbY0", raw_data)
|
||||||
|
|
||||||
# Some basic verifications
|
# Some basic verifications
|
||||||
expect(typeof(info)).to eq(Hash(String, JSON::Any))
|
expect(typeof(info)).to eq(Hash(String, JSON::Any))
|
||||||
|
|||||||
@@ -410,7 +410,7 @@ module Invidious::Routes::API::V1::Videos
|
|||||||
clip_title = nil
|
clip_title = nil
|
||||||
|
|
||||||
if params = response.dig?("endpoint", "watchEndpoint", "params").try &.as_s
|
if params = response.dig?("endpoint", "watchEndpoint", "params").try &.as_s
|
||||||
start_time, end_time, clip_title = parse_clip_parameters(params)
|
start_time, end_time, clip_title = Invidious::Videos::Clip.parse_clip_parameters(params)
|
||||||
end
|
end
|
||||||
|
|
||||||
begin
|
begin
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ module Invidious::Routes::Embed
|
|||||||
else nil # Continue
|
else nil # Continue
|
||||||
end
|
end
|
||||||
|
|
||||||
params = process_video_params(env.params.query, preferences)
|
params = Invidious::Videos.process_video_params(env.params.query, preferences)
|
||||||
|
|
||||||
user = env.get?("user").try &.as(User)
|
user = env.get?("user").try &.as(User)
|
||||||
if user
|
if user
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ module Invidious::Routes::Watch
|
|||||||
end
|
end
|
||||||
subscriptions ||= [] of String
|
subscriptions ||= [] of String
|
||||||
|
|
||||||
params = process_video_params(env.params.query, preferences)
|
params = Invidious::Videos.process_video_params(env.params.query, preferences)
|
||||||
env.params.query.delete_all("listen")
|
env.params.query.delete_all("listen")
|
||||||
|
|
||||||
begin
|
begin
|
||||||
@@ -273,7 +273,7 @@ module Invidious::Routes::Watch
|
|||||||
|
|
||||||
if video_id = response.dig?("endpoint", "watchEndpoint", "videoId")
|
if video_id = response.dig?("endpoint", "watchEndpoint", "videoId")
|
||||||
if params = response.dig?("endpoint", "watchEndpoint", "params").try &.as_s
|
if params = response.dig?("endpoint", "watchEndpoint", "params").try &.as_s
|
||||||
start_time, end_time, _ = parse_clip_parameters(params)
|
start_time, end_time, _ = Invidious::Videos::Clip.parse_clip_parameters(params)
|
||||||
env.params.query["start"] = start_time.to_s if start_time != nil
|
env.params.query["start"] = start_time.to_s if start_time != nil
|
||||||
env.params.query["end"] = end_time.to_s if end_time != nil
|
env.params.query["end"] = end_time.to_s if end_time != nil
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -324,7 +324,7 @@ rescue DB::Error
|
|||||||
end
|
end
|
||||||
|
|
||||||
def fetch_video(id, region)
|
def fetch_video(id, region)
|
||||||
info = extract_video_info(video_id: id)
|
info = Invidious::Videos::Parser.extract_video_info(video_id: id)
|
||||||
|
|
||||||
if info.nil?
|
if info.nil?
|
||||||
raise InfoException.new("Invidious companion is not available. \
|
raise InfoException.new("Invidious companion is not available. \
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
require "json"
|
require "json"
|
||||||
|
|
||||||
# returns start_time, end_time and clip_title
|
module Invidious::Videos::Clip
|
||||||
def parse_clip_parameters(params) : {Float64?, Float64?, String?}
|
extend self
|
||||||
|
|
||||||
|
# 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) }
|
decoded_protobuf = params.try { |i| URI.decode_www_form(i) }
|
||||||
.try { |i| Base64.decode(i) }
|
.try { |i| Base64.decode(i) }
|
||||||
.try { |i| IO::Memory.new(i) }
|
.try { |i| IO::Memory.new(i) }
|
||||||
@@ -19,4 +22,5 @@ def parse_clip_parameters(params) : {Float64?, Float64?, String?}
|
|||||||
.try(&.["50:0:embedded"]["4:3:string"].as_s)
|
.try(&.["50:0:embedded"]["4:3:string"].as_s)
|
||||||
|
|
||||||
return start_time, end_time, clip_title
|
return start_time, end_time, clip_title
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
require "json"
|
require "json"
|
||||||
|
|
||||||
# Use to parse both "compactVideoRenderer" and "endScreenVideoRenderer".
|
module Invidious::Videos::Parser
|
||||||
# The former is preferred as it has more videos in it. The second has
|
extend self
|
||||||
# the same 11 first entries as the compact rendered.
|
|
||||||
#
|
# Use to parse both "compactVideoRenderer" and "endScreenVideoRenderer".
|
||||||
# TODO: "compactRadioRenderer" (Mix) and
|
# The former is preferred as it has more videos in it. The second has
|
||||||
# TODO: Use a proper struct/class instead of a hacky JSON object
|
# the same 11 first entries as the compact rendered.
|
||||||
def parse_related_video(related : JSON::Any) : Hash(String, JSON::Any)?
|
#
|
||||||
|
# TODO: "compactRadioRenderer" (Mix) and
|
||||||
|
# TODO: Use a proper struct/class instead of a hacky JSON object
|
||||||
|
def parse_related_video(related : JSON::Any) : Hash(String, JSON::Any)?
|
||||||
return nil if !related["videoId"]?
|
return nil if !related["videoId"]?
|
||||||
|
|
||||||
# The compact renderer has video length in seconds, where the end
|
# The compact renderer has video length in seconds, where the end
|
||||||
@@ -50,9 +53,9 @@ def parse_related_video(related : JSON::Any) : Hash(String, JSON::Any)?
|
|||||||
"author_verified" => JSON::Any.new(author_verified),
|
"author_verified" => JSON::Any.new(author_verified),
|
||||||
"published" => JSON::Any.new(published || ""),
|
"published" => JSON::Any.new(published || ""),
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def extract_video_info(video_id : String)
|
def extract_video_info(video_id : String)
|
||||||
# Fetch data from the player endpoint
|
# Fetch data from the player endpoint
|
||||||
player_response = YoutubeAPI.player(video_id: video_id)
|
player_response = YoutubeAPI.player(video_id: video_id)
|
||||||
|
|
||||||
@@ -103,7 +106,7 @@ def extract_video_info(video_id : String)
|
|||||||
player_response = player_response.merge(next_response)
|
player_response = player_response.merge(next_response)
|
||||||
end
|
end
|
||||||
|
|
||||||
params = parse_video_info(video_id, player_response)
|
params = self.parse_video_info(video_id, player_response)
|
||||||
params["reason"] = JSON::Any.new(reason) if reason
|
params["reason"] = JSON::Any.new(reason) if reason
|
||||||
|
|
||||||
{"captions", "playabilityStatus", "playerConfig", "storyboards"}.each do |f|
|
{"captions", "playabilityStatus", "playerConfig", "storyboards"}.each do |f|
|
||||||
@@ -129,9 +132,9 @@ def extract_video_info(video_id : String)
|
|||||||
params["version"] = JSON::Any.new(Video::SCHEMA_VERSION.to_i64)
|
params["version"] = JSON::Any.new(Video::SCHEMA_VERSION.to_i64)
|
||||||
|
|
||||||
return params
|
return params
|
||||||
end
|
end
|
||||||
|
|
||||||
def try_fetch_streaming_data(id : String, client_config : YoutubeAPI::ClientConfig) : Hash(String, JSON::Any)?
|
def try_fetch_streaming_data(id : String, client_config : YoutubeAPI::ClientConfig) : Hash(String, JSON::Any)?
|
||||||
LOGGER.debug("try_fetch_streaming_data: [#{id}] Using #{client_config.client_type} client.")
|
LOGGER.debug("try_fetch_streaming_data: [#{id}] Using #{client_config.client_type} client.")
|
||||||
response = YoutubeAPI.player(video_id: id)
|
response = YoutubeAPI.player(video_id: id)
|
||||||
|
|
||||||
@@ -149,9 +152,9 @@ def try_fetch_streaming_data(id : String, client_config : YoutubeAPI::ClientConf
|
|||||||
else
|
else
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse_video_info(video_id : String, player_response : Hash(String, JSON::Any)) : Hash(String, JSON::Any)
|
def parse_video_info(video_id : String, player_response : Hash(String, JSON::Any)) : Hash(String, JSON::Any)
|
||||||
# Top level elements
|
# Top level elements
|
||||||
|
|
||||||
main_results = player_response.dig?("contents", "twoColumnWatchNextResults")
|
main_results = player_response.dig?("contents", "twoColumnWatchNextResults")
|
||||||
@@ -242,7 +245,7 @@ def parse_video_info(video_id : String, player_response : Hash(String, JSON::Any
|
|||||||
.dig?("secondaryResults", "secondaryResults", "results")
|
.dig?("secondaryResults", "secondaryResults", "results")
|
||||||
secondary_results.try &.as_a.each do |element|
|
secondary_results.try &.as_a.each do |element|
|
||||||
if item = element["compactVideoRenderer"]?
|
if item = element["compactVideoRenderer"]?
|
||||||
related_video = parse_related_video(item)
|
related_video = self.parse_related_video(item)
|
||||||
related << JSON::Any.new(related_video) if related_video
|
related << JSON::Any.new(related_video) if related_video
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -257,7 +260,7 @@ def parse_video_info(video_id : String, player_response : Hash(String, JSON::Any
|
|||||||
|
|
||||||
player_overlays.try &.as_a.each do |element|
|
player_overlays.try &.as_a.each do |element|
|
||||||
if item = element["endScreenVideoRenderer"]?
|
if item = element["endScreenVideoRenderer"]?
|
||||||
related_video = parse_related_video(item)
|
related_video = self.parse_related_video(item)
|
||||||
related << JSON::Any.new(related_video) if related_video
|
related << JSON::Any.new(related_video) if related_video
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -441,9 +444,9 @@ def parse_video_info(video_id : String, player_response : Hash(String, JSON::Any
|
|||||||
}
|
}
|
||||||
|
|
||||||
return params
|
return params
|
||||||
end
|
end
|
||||||
|
|
||||||
private def convert_url(fmt)
|
private def convert_url(fmt)
|
||||||
if cfr = fmt["signatureCipher"]?.try { |json| HTTP::Params.parse(json.as_s) }
|
if cfr = fmt["signatureCipher"]?.try { |json| HTTP::Params.parse(json.as_s) }
|
||||||
url = URI.parse(cfr["url"])
|
url = URI.parse(cfr["url"])
|
||||||
params = url.query_params
|
params = url.query_params
|
||||||
@@ -458,8 +461,9 @@ private def convert_url(fmt)
|
|||||||
LOGGER.trace("convert_url: new url is '#{url}'")
|
LOGGER.trace("convert_url: new url is '#{url}'")
|
||||||
|
|
||||||
return url.to_s
|
return url.to_s
|
||||||
rescue ex
|
rescue ex
|
||||||
LOGGER.debug("convert_url: Error when parsing video URL")
|
LOGGER.debug("convert_url: Error when parsing video URL")
|
||||||
LOGGER.trace(ex.inspect_with_backtrace)
|
LOGGER.trace(ex.inspect_with_backtrace)
|
||||||
return ""
|
return ""
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
struct VideoPreferences
|
module Invidious::Videos
|
||||||
|
extend self
|
||||||
|
|
||||||
|
struct VideoPreferences
|
||||||
include JSON::Serializable
|
include JSON::Serializable
|
||||||
|
|
||||||
property annotations : Bool
|
property annotations : Bool
|
||||||
@@ -25,9 +28,9 @@ struct VideoPreferences
|
|||||||
property volume : Int32
|
property volume : Int32
|
||||||
property vr_mode : Bool
|
property vr_mode : Bool
|
||||||
property save_player_pos : Bool
|
property save_player_pos : Bool
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_video_params(query, preferences)
|
def process_video_params(query, preferences)
|
||||||
annotations = query["iv_load_policy"]?.try &.to_i?
|
annotations = query["iv_load_policy"]?.try &.to_i?
|
||||||
preload = query["preload"]?.try { |q| (q == "true" || q == "1").to_unsafe }
|
preload = query["preload"]?.try { |q| (q == "true" || q == "1").to_unsafe }
|
||||||
autoplay = query["autoplay"]?.try { |q| (q == "true" || q == "1").to_unsafe }
|
autoplay = query["autoplay"]?.try { |q| (q == "true" || q == "1").to_unsafe }
|
||||||
@@ -159,4 +162,5 @@ def process_video_params(query, preferences)
|
|||||||
})
|
})
|
||||||
|
|
||||||
return params
|
return params
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user