Skip to content

Commit

Permalink
trying to only use next endpoint for the major data
Browse files Browse the repository at this point in the history
  • Loading branch information
unixfox committed Oct 13, 2024
1 parent 53e8a5d commit da00958
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 100 deletions.
5 changes: 4 additions & 1 deletion locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -496,5 +496,8 @@
"toggle_theme": "Toggle Theme",
"carousel_slide": "Slide {{current}} of {{total}}",
"carousel_skip": "Skip the Carousel",
"carousel_go_to": "Go to slide `x`"
"carousel_go_to": "Go to slide `x`",
"error_from_youtube_unplayable": "Video unplayable due to an error from YouTube:",
"error_processing_data_youtube": "Error while processing the data sent by YouTube",
"refresh_page": "Refresh the page"
}
13 changes: 7 additions & 6 deletions src/invidious/helpers/errors.cr
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,6 @@ def error_template_helper(env : HTTP::Server::Context, status_code : Int32, exce
</div>
END_HTML

# Don't show the usual "next steps" widget. The same options are
# proposed above the error message, just worded differently.
next_steps = ""

return templated "error"
end

Expand All @@ -84,8 +80,13 @@ def error_template_helper(env : HTTP::Server::Context, status_code : Int32, mess

locale = env.get("preferences").as(Preferences).locale

error_message = translate(locale, message)
next_steps = error_redirect_helper(env)
error_message = <<-END_HTML
<div class="error_message">
<h2>#{translate(locale, "error_processing_data_youtube")}</h2>
<p>#{translate(locale, message)}</p>
#{error_redirect_helper(env)}
</div>
END_HTML

return templated "error"
end
Expand Down
114 changes: 59 additions & 55 deletions src/invidious/routes/watch.cr
Original file line number Diff line number Diff line change
Expand Up @@ -117,79 +117,83 @@ module Invidious::Routes::Watch
comment_html ||= ""
end

fmt_stream = video.fmt_stream
adaptive_fmts = video.adaptive_fmts
if video.reason.nil?
fmt_stream = video.fmt_stream
adaptive_fmts = video.adaptive_fmts

if params.local
fmt_stream.each { |fmt| fmt["url"] = JSON::Any.new(URI.parse(fmt["url"].as_s).request_target) }
adaptive_fmts.each { |fmt| fmt["url"] = JSON::Any.new(URI.parse(fmt["url"].as_s).request_target) }
end
if params.local
fmt_stream.each { |fmt| fmt["url"] = JSON::Any.new(URI.parse(fmt["url"].as_s).request_target) }
adaptive_fmts.each { |fmt| fmt["url"] = JSON::Any.new(URI.parse(fmt["url"].as_s).request_target) }
end

video_streams = video.video_streams
audio_streams = video.audio_streams

# Older videos may not have audio sources available.
# We redirect here so they're not unplayable
if audio_streams.empty? && !video.live_now
if params.quality == "dash"
env.params.query.delete_all("quality")
env.params.query["quality"] = "medium"
return env.redirect "/watch?#{env.params.query}"
elsif params.listen
env.params.query.delete_all("listen")
env.params.query["listen"] = "0"
return env.redirect "/watch?#{env.params.query}"
video_streams = video.video_streams
audio_streams = video.audio_streams

# Older videos may not have audio sources available.
# We redirect here so they're not unplayable
if audio_streams.empty? && !video.live_now
if params.quality == "dash"
env.params.query.delete_all("quality")
env.params.query["quality"] = "medium"
return env.redirect "/watch?#{env.params.query}"
elsif params.listen
env.params.query.delete_all("listen")
env.params.query["listen"] = "0"
return env.redirect "/watch?#{env.params.query}"
end
end
end

captions = video.captions
captions = video.captions

preferred_captions = captions.select { |caption|
params.preferred_captions.includes?(caption.name) ||
params.preferred_captions.includes?(caption.language_code.split("-")[0])
}
preferred_captions.sort_by! { |caption|
(params.preferred_captions.index(caption.name) ||
params.preferred_captions.index(caption.language_code.split("-")[0])).not_nil!
}
captions = captions - preferred_captions
preferred_captions = captions.select { |caption|
params.preferred_captions.includes?(caption.name) ||
params.preferred_captions.includes?(caption.language_code.split("-")[0])
}
preferred_captions.sort_by! { |caption|
(params.preferred_captions.index(caption.name) ||
params.preferred_captions.index(caption.language_code.split("-")[0])).not_nil!
}
captions = captions - preferred_captions

aspect_ratio = "16:9"
aspect_ratio = "16:9"

thumbnail = "/vi/#{video.id}/maxres.jpg"
thumbnail = "/vi/#{video.id}/maxres.jpg"

if params.raw
if params.listen
url = audio_streams[0]["url"].as_s
if params.raw
if params.listen
url = audio_streams[0]["url"].as_s

if params.quality.ends_with? "k"
audio_streams.each do |fmt|
if fmt["bitrate"].as_i == params.quality.rchop("k").to_i
url = fmt["url"].as_s
if params.quality.ends_with? "k"
audio_streams.each do |fmt|
if fmt["bitrate"].as_i == params.quality.rchop("k").to_i
url = fmt["url"].as_s
end
end
end
end
else
url = fmt_stream[0]["url"].as_s
else
url = fmt_stream[0]["url"].as_s

fmt_stream.each do |fmt|
if fmt["quality"].as_s == params.quality
url = fmt["url"].as_s
fmt_stream.each do |fmt|
if fmt["quality"].as_s == params.quality
url = fmt["url"].as_s
end
end
end

return env.redirect url
end

return env.redirect url
# Structure used for the download widget
video_assets = Invidious::Frontend::WatchPage::VideoAssets.new(
full_videos: fmt_stream,
video_streams: video_streams,
audio_streams: audio_streams,
captions: video.captions
)
else
env.response.status_code = 500
end

# Structure used for the download widget
video_assets = Invidious::Frontend::WatchPage::VideoAssets.new(
full_videos: fmt_stream,
video_streams: video_streams,
audio_streams: audio_streams,
captions: video.captions
)

templated "watch"
end

Expand Down
18 changes: 11 additions & 7 deletions src/invidious/videos.cr
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ def get_video(id, refresh = true, region = nil, force_refresh = false)
end
else
video = fetch_video(id, region)
Invidious::Database::Videos.insert(video) if !region
Invidious::Database::Videos.insert(video) if !region && !video.info.dig?("reason")
end

return video
Expand All @@ -380,13 +380,17 @@ end
def fetch_video(id, region)
info = extract_video_info(video_id: id)

if reason = info["reason"]?
if info["reason"]?
reason = info["reason"].as_s
if info.dig?("subreason")
subreason = info["subreason"].as_s
else
subreason = "No additional reason"
end
if reason == "Video unavailable"
raise NotFoundException.new(reason.as_s || "")
elsif !reason.as_s.starts_with? "Premieres"
# dont error when it's a premiere.
# we already parsed most of the data and display the premiere date
raise InfoException.new(reason.as_s || "")
raise NotFoundException.new(reason + ": " + subreason || "")
elsif {"Private video"}.any?(reason)
raise InfoException.new(reason + ": " + subreason || "")
end
end

Expand Down
63 changes: 37 additions & 26 deletions src/invidious/videos/parser.cr
Original file line number Diff line number Diff line change
Expand Up @@ -64,18 +64,19 @@ def extract_video_info(video_id : String)
playability_status = player_response.dig?("playabilityStatus", "status").try &.as_s

if playability_status != "OK"
subreason = player_response.dig?("playabilityStatus", "errorScreen", "playerErrorMessageRenderer", "subreason")
reason = subreason.try &.[]?("simpleText").try &.as_s
reason ||= subreason.try &.[]("runs").as_a.map(&.[]("text")).join("")
reason ||= player_response.dig("playabilityStatus", "reason").as_s
reason = player_response.dig?("playabilityStatus", "reason").try &.as_s
reason ||= player_response.dig("playabilityStatus", "errorScreen", "playerErrorMessageRenderer", "reason", "simpleText").as_s
subreason_main = player_response.dig?("playabilityStatus", "errorScreen", "playerErrorMessageRenderer", "subreason")
subreason = subreason_main.try &.[]?("simpleText").try &.as_s
subreason ||= subreason_main.try &.[]("runs").as_a.map(&.[]("text")).join("")

# Stop here if video is not a scheduled livestream or
# for LOGIN_REQUIRED when videoDetails element is not found because retrying won't help
if !{"LIVE_STREAM_OFFLINE", "LOGIN_REQUIRED"}.any?(playability_status) ||
playability_status == "LOGIN_REQUIRED" && !player_response.dig?("videoDetails")
if {"Private video", "Video unavailable"}.any?(reason)
return {
"version" => JSON::Any.new(Video::SCHEMA_VERSION.to_i64),
"reason" => JSON::Any.new(reason),
"version" => JSON::Any.new(Video::SCHEMA_VERSION.to_i64),
"reason" => JSON::Any.new(reason),
"subreason" => JSON::Any.new(subreason),
}
end
elsif video_id != player_response.dig("videoDetails", "videoId")
Expand All @@ -95,11 +96,8 @@ def extract_video_info(video_id : String)
reason = nil
end

# Don't fetch the next endpoint if the video is unavailable.
if {"OK", "LIVE_STREAM_OFFLINE", "LOGIN_REQUIRED"}.any?(playability_status)
next_response = YoutubeAPI.next({"videoId": video_id, "params": ""})
player_response = player_response.merge(next_response)
end
next_response = YoutubeAPI.next({"videoId": video_id, "params": ""})
player_response = player_response.merge(next_response)

params = parse_video_info(video_id, player_response)
params["reason"] = JSON::Any.new(reason) if reason
Expand Down Expand Up @@ -194,16 +192,19 @@ def parse_video_info(video_id : String, player_response : Hash(String, JSON::Any
raise BrokenTubeException.new("videoSecondaryInfoRenderer") if !video_secondary_renderer
end

video_details = player_response.dig?("videoDetails")
if !(video_details = player_response.dig?("videoDetails"))
video_details = {} of String => JSON::Any
end
if !(microformat = player_response.dig?("microformat", "playerMicroformatRenderer"))
microformat = {} of String => JSON::Any
end

raise BrokenTubeException.new("videoDetails") if !video_details

# Basic video infos

title = video_details["title"]?.try &.as_s
title = extract_text(
video_primary_renderer
.try &.dig?("title")
)

# We have to try to extract viewCount from videoPrimaryInfoRenderer first,
# then from videoDetails, as the latter is "0" for livestreams (we want
Expand All @@ -215,17 +216,27 @@ def parse_video_info(video_id : String, player_response : Hash(String, JSON::Any
views_txt ||= video_details["viewCount"]?.try &.as_s || ""
views = views_txt.gsub(/\D/, "").to_i64?

length_txt = (microformat["lengthSeconds"]? || video_details["lengthSeconds"])
length_txt = (microformat["lengthSeconds"]? || video_details["lengthSeconds"]?)
.try &.as_s.to_i64

published = microformat["publishDate"]?
.try { |t| Time.parse(t.as_s, "%Y-%m-%d", Time::Location::UTC) } || Time.utc
published_txt = video_primary_renderer
.try &.dig?("dateText", "simpleText")

if published_txt.try &.as_s.includes?("ago") && !published_txt.nil?
published = decode_date(published_txt.as_s.lchop("Started streaming "))
elsif published_txt && published_txt.try &.as_s.matches?(/(\w{3} \d{1,2}, \d{4})$/)
published = Time.parse(published_txt.as_s.match!(/(\w{3} \d{1,2}, \d{4})$/)[0], "%b %-d, %Y", Time::Location::UTC)
else
published = Time.utc
end

premiere_timestamp = microformat.dig?("liveBroadcastDetails", "startTimestamp")
.try { |t| Time.parse_rfc3339(t.as_s) }

live_now = microformat.dig?("liveBroadcastDetails", "isLiveNow")
.try &.as_bool || false
.try &.as_bool
live_now ||= video_primary_renderer
.try &.dig?("viewCount", "videoViewCountRenderer", "isLive").try &.as_bool || false

post_live_dvr = video_details.dig?("isPostLiveDvr")
.try &.as_bool || false
Expand Down Expand Up @@ -393,16 +404,16 @@ def parse_video_info(video_id : String, player_response : Hash(String, JSON::Any

# Author infos

author = video_details["author"]?.try &.as_s
ucid = video_details["channelId"]?.try &.as_s

if author_info = video_secondary_renderer.try &.dig?("owner", "videoOwnerRenderer")
author_thumbnail = author_info.dig?("thumbnail", "thumbnails", 0, "url")
author_verified = has_verified_badge?(author_info["badges"]?)

subs_text = author_info["subscriberCountText"]?
.try { |t| t["simpleText"]? || t.dig?("runs", 0, "text") }
.try &.as_s.split(" ", 2)[0]

author = author_info.dig?("title", "runs", 0, "text").try &.as_s
ucid = author_info.dig?("title", "runs", 0, "navigationEndpoint", "browseEndpoint", "browseId").try &.as_s
end

# Return data
Expand All @@ -427,7 +438,7 @@ def parse_video_info(video_id : String, player_response : Hash(String, JSON::Any
# Extra video infos
"allowedRegions" => JSON::Any.new(allowed_regions.map { |v| JSON::Any.new(v) }),
"allowRatings" => JSON::Any.new(allow_ratings || false),
"isFamilyFriendly" => JSON::Any.new(family_friendly || false),
"isFamilyFriendly" => JSON::Any.new(family_friendly || true),
"isListed" => JSON::Any.new(is_listed || false),
"isUpcoming" => JSON::Any.new(is_upcoming || false),
"keywords" => JSON::Any.new(keywords.map { |v| JSON::Any.new(v) }),
Expand All @@ -437,7 +448,7 @@ def parse_video_info(video_id : String, player_response : Hash(String, JSON::Any
# Description
"description" => JSON::Any.new(description || ""),
"descriptionHtml" => JSON::Any.new(description_html || "<p></p>"),
"shortDescription" => JSON::Any.new(short_description.try &.as_s || nil),
"shortDescription" => JSON::Any.new(short_description.try &.as_s || ""),
# Video metadata
"genre" => JSON::Any.new(genre.try &.as_s || ""),
"genreUcid" => JSON::Any.new(genre_ucid.try &.as_s?),
Expand Down
2 changes: 2 additions & 0 deletions src/invidious/views/components/player.ecr
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<% if audio_streams && fmt_stream && preferred_captions && captions %>
<video style="outline:none;width:100%;background-color:#000" playsinline poster="<%= thumbnail %>"
id="player" class="on-video_player video-js player-style-<%= params.player_style %>"
<% if params.autoplay %>autoplay<% end %>
Expand Down Expand Up @@ -78,3 +79,4 @@
%>
</script>
<script src="/js/player.js?v=<%= ASSET_COMMIT %>"></script>
<% end %>
10 changes: 9 additions & 1 deletion src/invidious/views/embed.ecr
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,15 @@
%>
</script>
<%= rendered "components/player" %>
<% if video.reason.nil? %>
<h3>
<%= video.reason %>
</h3>
<% else %>
<div id="player-container" class="h-box">
<%= rendered "components/player" %>
</div>
<% end %>
<script src="/js/embed.js?v=<%= ASSET_COMMIT %>"></script>
</body>
</html>
1 change: 0 additions & 1 deletion src/invidious/views/error.ecr
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,4 @@

<div class="h-box">
<%= error_message %>
<%= next_steps %>
</div>
Loading

0 comments on commit da00958

Please sign in to comment.