-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Storyboards: Various fixes and code cleaning #4153
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
6878822
Storyboards: Move parser to its own file
SamantazFox 8327862
Storyboards: Use replace the NamedTuple by a struct
SamantazFox da3d58f
Storyboards: Cleanup and document code
SamantazFox 7b50388
Storyboards: Fix broken first storyboard
SamantazFox a335bc0
Storyboards: Fix some small logic mistakes
SamantazFox 5b05f3b
Storyboards: Workarounds for videojs-vtt-thumbnails
SamantazFox 764965c
Storyboards: Fix lint error
SamantazFox 21ab5dc
Storyboard: Revert cue timing "fix"
SamantazFox File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
require "uri" | ||
require "http/params" | ||
|
||
module Invidious::Videos | ||
struct Storyboard | ||
# Template URL | ||
getter url : URI | ||
getter proxied_url : URI | ||
|
||
# Thumbnail parameters | ||
getter width : Int32 | ||
getter height : Int32 | ||
getter count : Int32 | ||
getter interval : Int32 | ||
|
||
# Image (storyboard) parameters | ||
getter rows : Int32 | ||
getter columns : Int32 | ||
getter images_count : Int32 | ||
|
||
def initialize( | ||
*, @url, @width, @height, @count, @interval, | ||
@rows, @columns, @images_count | ||
) | ||
authority = /(i\d?).ytimg.com/.match!(@url.host.not_nil!)[1]? | ||
|
||
@proxied_url = URI.parse(HOST_URL) | ||
@proxied_url.path = "/sb/#{authority}/#{@url.path.lchop("/sb/")}" | ||
@proxied_url.query = @url.query | ||
end | ||
|
||
# Parse the JSON structure from Youtube | ||
def self.from_yt_json(container : JSON::Any, length_seconds : Int32) : Array(Storyboard) | ||
# Livestream storyboards are a bit different | ||
# TODO: document exactly how | ||
if storyboard = container.dig?("playerLiveStoryboardSpecRenderer", "spec").try &.as_s | ||
return [Storyboard.new( | ||
url: URI.parse(storyboard.split("#")[0]), | ||
width: 106, | ||
height: 60, | ||
count: -1, | ||
interval: 5000, | ||
rows: 3, | ||
columns: 3, | ||
images_count: -1 | ||
)] | ||
end | ||
|
||
# Split the storyboard string into chunks | ||
# | ||
# General format (whitespaces added for legibility): | ||
# https://i.ytimg.com/sb/<video_id>/storyboard3_L$L/$N.jpg?sqp=<sig0> | ||
# | 48 # 27 # 100 # 10 # 10 # 0 # default # rs$<sig1> | ||
# | 80 # 45 # 95 # 10 # 10 # 10000 # M$M # rs$<sig2> | ||
# | 160 # 90 # 95 # 5 # 5 # 10000 # M$M # rs$<sig3> | ||
# | ||
storyboards = container.dig?("playerStoryboardSpecRenderer", "spec") | ||
.try &.as_s.split("|") | ||
|
||
return [] of Storyboard if !storyboards | ||
|
||
# The base URL is the first chunk | ||
base_url = URI.parse(storyboards.shift) | ||
|
||
return storyboards.map_with_index do |sb, i| | ||
# Separate the different storyboard parameters: | ||
# width/height: respective dimensions, in pixels, of a single thumbnail | ||
# count: how many thumbnails are displayed across the full video | ||
# columns/rows: maximum amount of thumbnails that can be stuffed in a | ||
# single image, horizontally and vertically. | ||
# interval: interval between two thumbnails, in milliseconds | ||
# name: storyboard filename. Usually "M$M" or "default" | ||
# sigh: URL cryptographic signature | ||
width, height, count, columns, rows, interval, name, sigh = sb.split("#") | ||
|
||
width = width.to_i | ||
height = height.to_i | ||
count = count.to_i | ||
interval = interval.to_i | ||
columns = columns.to_i | ||
rows = rows.to_i | ||
|
||
# Copy base URL object, so that we can modify it | ||
url = base_url.dup | ||
|
||
# Add the signature to the URL | ||
params = url.query_params | ||
params["sigh"] = sigh | ||
url.query_params = params | ||
|
||
# Replace the template parts with what we have | ||
url.path = url.path.sub("$L", i).sub("$N", name) | ||
|
||
# This value represents the maximum amount of thumbnails that can fit | ||
# in a single image. The last image (or the only one for short videos) | ||
# will contain less thumbnails than that. | ||
thumbnails_per_image = columns * rows | ||
|
||
# This value represents the total amount of storyboards required to | ||
# hold all of the thumbnails. It can't be less than 1. | ||
images_count = (count / thumbnails_per_image).ceil.to_i | ||
|
||
# Compute the interval when needed (in general, that's only required | ||
# for the first "default" storyboard). | ||
if interval == 0 | ||
interval = ((length_seconds / count) * 1_000).to_i | ||
end | ||
|
||
Storyboard.new( | ||
url: url, | ||
width: width, | ||
height: height, | ||
count: count, | ||
interval: interval, | ||
rows: rows, | ||
columns: columns, | ||
images_count: images_count, | ||
) | ||
end | ||
end | ||
end | ||
end |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tried messing around with them in shaka for FreeTube (as we can use the live dash manifest with shaka and get 4 hours of seeking) but the code I came up with based on reversing the player.js file and stepping through it with a debugger, ended up requesting loads of future not yet existing thumbnails (something in my assumptions or calculations must have been off), so I gave up and continued doing the rest of the shaka migration.
I'll let you know if I ever get it working in FreeTube.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, thanks for your research ^^
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implementing live storyboards is probably something better done on the client side anyway, as it will require information from the streaming URL response headers or the live DASH manifest (at least that's the only way I can think of figuring out the current live segment) and you'll have to update it every 5 seconds, so generating a VTT file on the server side doesn't seem like a viable option for that.
My previous attempt revolved around using shaka's manifest preprocessing function to inject the live storyboards into the DASH manifest based on information in the manifest. Although as shaka already struggles enough with YouTube's 4 hour DVR window (4 hours worth of 5 second segments = 2880, which it has to recalculate every 5 seconds x) ) in the live manifests, so I'm not sure if adding that extra delay is a good idea, even if my solution had worked, which it didn't.