mirror of
https://gitea.invidious.io/iv-org/invidious-copy-2023-06-08.git
synced 2024-08-15 00:53:38 +00:00
Videos: Use the new Stream classes across the code
This commit is contained in:
parent
0d37faf284
commit
1adb035312
9 changed files with 123 additions and 177 deletions
|
@ -4,10 +4,10 @@ module Invidious::Frontend::WatchPage
|
|||
# A handy structure to pass many elements at
|
||||
# once to the download widget function
|
||||
struct VideoAssets
|
||||
getter full_videos : Array(Hash(String, JSON::Any))
|
||||
getter video_streams : Array(Hash(String, JSON::Any))
|
||||
getter audio_streams : Array(Hash(String, JSON::Any))
|
||||
getter captions : Array(Invidious::Videos::Caption)
|
||||
getter full_videos : Array(Videos::ProgressiveHttpStream)
|
||||
getter video_streams : Array(Videos::AdaptativeVideoStream)
|
||||
getter audio_streams : Array(Videos::AdaptativeAudioStream)
|
||||
getter captions : Array(Videos::Caption)
|
||||
|
||||
def initialize(
|
||||
@full_videos,
|
||||
|
@ -48,38 +48,33 @@ module Invidious::Frontend::WatchPage
|
|||
# Non-DASH videos (audio+video)
|
||||
|
||||
video_assets.full_videos.each do |option|
|
||||
mimetype = option["mimeType"].as_s.split(";")[0]
|
||||
height = Invidious::Videos::Formats.itag_to_metadata?(option.itag).try &.["height"]?
|
||||
|
||||
height = Invidious::Videos::Formats.itag_to_metadata?(option["itag"]).try &.["height"]?
|
||||
|
||||
value = {"itag": option["itag"], "ext": mimetype.split("/")[1]}.to_json
|
||||
value = {"itag": option.itag, "ext": option.mime_type.split("/")[1]}.to_json
|
||||
|
||||
str << "\t\t\t<option value='" << value << "'>"
|
||||
str << (height || "~240") << "p - " << mimetype
|
||||
str << (height || option.video_height) << "p - " << option.mime_type
|
||||
str << "</option>\n"
|
||||
end
|
||||
|
||||
# DASH video streams
|
||||
|
||||
video_assets.video_streams.each do |option|
|
||||
mimetype = option["mimeType"].as_s.split(";")[0]
|
||||
|
||||
value = {"itag": option["itag"], "ext": mimetype.split("/")[1]}.to_json
|
||||
value = {"itag": option.itag, "ext": option.mime_type.split("/")[1]}.to_json
|
||||
|
||||
str << "\t\t\t<option value='" << value << "'>"
|
||||
str << option["qualityLabel"] << " - " << mimetype << " @ " << option["fps"] << "fps - video only"
|
||||
str << option.label << " - " << option.mime_type
|
||||
str << " @ " << option.video_fps << "fps - video only"
|
||||
str << "</option>\n"
|
||||
end
|
||||
|
||||
# DASH audio streams
|
||||
|
||||
video_assets.audio_streams.each do |option|
|
||||
mimetype = option["mimeType"].as_s.split(";")[0]
|
||||
|
||||
value = {"itag": option["itag"], "ext": mimetype.split("/")[1]}.to_json
|
||||
value = {"itag": option.itag, "ext": option.mime_type.split("/")[1]}.to_json
|
||||
|
||||
str << "\t\t\t<option value='" << value << "'>"
|
||||
str << mimetype << " @ " << (option["bitrate"]?.try &.as_i./ 1000) << "k - audio only"
|
||||
str << option.mime_type << " @ " << (option.bitrate // 1000) << "kbps - audio only"
|
||||
str << "</option>\n"
|
||||
end
|
||||
|
||||
|
|
|
@ -79,72 +79,67 @@ module Invidious::JSONify::APIv1
|
|||
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
|
||||
json.field "init", fmt.init_range.to_s if fmt.init_range
|
||||
json.field "index", fmt.index_range.to_s if fmt.index_range
|
||||
|
||||
# Livestream chunk infos. Should be present when `init` and `index` aren't
|
||||
json.field "targetDurationSec", fmt.target_duration if fmt.target_duration
|
||||
json.field "maxDvrDurationSec", fmt.max_dvr_duration if fmt.max_dvr_duration
|
||||
|
||||
# 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 "bitrate", fmt.bitrate.to_s if fmt.responds_to?(:bitrate)
|
||||
|
||||
if proxy
|
||||
json.field "url", Invidious::HttpServer::Utils.proxy_video_url(
|
||||
fmt["url"].to_s, absolute: true
|
||||
fmt.url, absolute: true
|
||||
)
|
||||
else
|
||||
json.field "url", fmt["url"]
|
||||
json.field "url", fmt.url
|
||||
end
|
||||
|
||||
json.field "itag", fmt["itag"].as_i.to_s
|
||||
json.field "type", fmt["mimeType"]
|
||||
json.field "clen", fmt["contentLength"]? || "-1"
|
||||
json.field "itag", fmt.itag.to_s
|
||||
json.field "type", fmt.raw_mime_type
|
||||
json.field "clen", fmt.content_length if fmt.responds_to?(:content_length)
|
||||
|
||||
json.field "encoding", fmt.codecs
|
||||
|
||||
# Last modified is a unix timestamp with µS, with the dot omitted.
|
||||
# E.g: 1638056732(.)141582
|
||||
#
|
||||
# On livestreams, it's not present, so always fall back to the
|
||||
# current unix timestamp (up to mS precision) for compatibility.
|
||||
last_modified = fmt["lastModified"]?
|
||||
last_modified ||= "#{Time.utc.to_unix_ms.to_s}000"
|
||||
json.field "lmt", last_modified
|
||||
last_modified = fmt.last_modified || Time.utc
|
||||
json.field "lmt", "#{last_modified.to_unix_ms}000"
|
||||
|
||||
json.field "projectionType", fmt["projectionType"]
|
||||
json.field "projectionType", fmt.projection_type.to_s.upcase
|
||||
|
||||
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
|
||||
if fmt.is_a?(Videos::AdaptativeVideoStream)
|
||||
json.field "fps", fmt.video_fps
|
||||
json.field "size", "#{fmt.video_width}x#{fmt.video_height}"
|
||||
end
|
||||
|
||||
if fmt_info = Invidious::Videos::Formats.itag_to_metadata?(fmt.itag)
|
||||
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
|
||||
quality_label += "60" if fmt.responds_to?(:video_fps) && fmt.video_fps > 30
|
||||
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")
|
||||
if fmt.is_a?(Videos::AdaptativeAudioStream)
|
||||
json.field "audioQuality", fmt.audio_quality
|
||||
json.field "audioSampleRate", fmt.audio_sample_rate
|
||||
json.field "audioChannels", fmt.audio_channels
|
||||
end
|
||||
|
||||
# Extra misc stuff
|
||||
json.field "colorInfo", fmt["colorInfo"] if fmt.has_key?("colorInfo")
|
||||
json.field "captionTrack", fmt["captionTrack"] if fmt.has_key?("captionTrack")
|
||||
# json.field "colorInfo", fmt["colorInfo"] if fmt.has_key?("colorInfo")
|
||||
# json.field "captionTrack", fmt["captionTrack"] if fmt.has_key?("captionTrack")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -154,30 +149,25 @@ module Invidious::JSONify::APIv1
|
|||
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"]
|
||||
json.field "url", fmt.url
|
||||
json.field "itag", fmt.itag.to_s
|
||||
json.field "type", fmt.raw_mime_type
|
||||
json.field "quality", fmt.label
|
||||
|
||||
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 "encoding", fmt.codecs
|
||||
|
||||
json.field "size", "#{fmt.video_width}x#{fmt.video_height}"
|
||||
json.field "fps", fmt.video_fps
|
||||
|
||||
if fmt_info = Invidious::Videos::Formats.itag_to_metadata?(fmt.itag)
|
||||
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
|
||||
quality_label += "60" if fmt.video_fps > 30
|
||||
json.field "qualityLabel", quality_label
|
||||
|
||||
if fmt_info["width"]?
|
||||
json.field "size", "#{fmt_info["width"]}x#{fmt_info["height"]}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,7 +4,7 @@ module Invidious::Routes::API::Manifest
|
|||
env.response.headers.add("Access-Control-Allow-Origin", "*")
|
||||
env.response.content_type = "application/dash+xml"
|
||||
|
||||
local = env.params.query["local"]?.try &.== "true"
|
||||
local = (env.params.query["local"]? == "true")
|
||||
id = env.params.url["id"]
|
||||
region = env.params.query["region"]?
|
||||
|
||||
|
@ -38,18 +38,18 @@ module Invidious::Routes::API::Manifest
|
|||
return manifest
|
||||
end
|
||||
|
||||
adaptive_fmts = video.adaptive_fmts
|
||||
|
||||
# Transform URLs for proxying
|
||||
if local
|
||||
adaptive_fmts.each do |fmt|
|
||||
fmt["url"] = JSON::Any.new("#{HOST_URL}#{URI.parse(fmt["url"].as_s).request_target}")
|
||||
video.adaptive_fmts.each do |fmt|
|
||||
fmt.url = Invidious::HttpServer::Utils.proxy_video_url(fmt.url, absolute: true)
|
||||
end
|
||||
end
|
||||
|
||||
audio_streams = video.audio_streams.sort_by { |stream| {stream["bitrate"].as_i} }.reverse!
|
||||
video_streams = video.video_streams.sort_by { |stream| {stream["width"].as_i, stream["fps"].as_i} }.reverse!
|
||||
audio_streams = video.audio_streams.sort_by(&.bitrate).reverse!
|
||||
video_streams = video.video_streams.sort_by { |fmt| {fmt.video_width, fmt.video_fps} }.reverse!
|
||||
|
||||
manifest = XML.build(indent: " ", encoding: "UTF-8") do |xml|
|
||||
# Build the manifest
|
||||
return XML.build(indent: " ", encoding: "UTF-8") do |xml|
|
||||
xml.element("MPD", "xmlns": "urn:mpeg:dash:schema:mpd:2011",
|
||||
"profiles": "urn:mpeg:dash:profile:full:2011", minBufferTime: "PT1.5S", type: "static",
|
||||
mediaPresentationDuration: "PT#{video.length_seconds}S") do
|
||||
|
@ -57,34 +57,28 @@ module Invidious::Routes::API::Manifest
|
|||
i = 0
|
||||
|
||||
{"audio/mp4"}.each do |mime_type|
|
||||
mime_streams = audio_streams.select { |stream| stream["mimeType"].as_s.starts_with? mime_type }
|
||||
next if mime_streams.empty?
|
||||
formats = video.audio_streams.select(&.mime_type.== mime_type)
|
||||
next if formats.empty?
|
||||
|
||||
mime_streams.each do |fmt|
|
||||
formats.each do |fmt|
|
||||
# OTF streams aren't supported yet (See https://github.com/TeamNewPipe/NewPipe/issues/2415)
|
||||
next if !(fmt.has_key?("indexRange") && fmt.has_key?("initRange"))
|
||||
next if (fmt.index_range.nil? || fmt.init_range.nil?)
|
||||
|
||||
# Different representations of the same audio should be groupped into one AdaptationSet.
|
||||
# However, most players don't support auto quality switching, so we have to trick them
|
||||
# into providing a quality selector.
|
||||
# See https://github.com/iv-org/invidious/issues/3074 for more details.
|
||||
xml.element("AdaptationSet", id: i, mimeType: mime_type, startWithSAP: 1, subsegmentAlignment: true, label: fmt["bitrate"].to_s + "k") do
|
||||
codecs = fmt["mimeType"].as_s.split("codecs=")[1].strip('"')
|
||||
bandwidth = fmt["bitrate"].as_i
|
||||
itag = fmt["itag"].as_i
|
||||
url = fmt["url"].as_s
|
||||
|
||||
xml.element("AdaptationSet", id: i, mimeType: mime_type, startWithSAP: 1, subsegmentAlignment: true, label: "#{(fmt.bitrate // 1000)} kbps") do
|
||||
xml.element("Role", schemeIdUri: "urn:mpeg:dash:role:2011", value: i == 0 ? "main" : "alternate")
|
||||
|
||||
xml.element("Representation", id: fmt["itag"], codecs: codecs, bandwidth: bandwidth) do
|
||||
xml.element("AudioChannelConfiguration", schemeIdUri: "urn:mpeg:dash:23003:3:audio_channel_configuration:2011",
|
||||
value: "2")
|
||||
xml.element("BaseURL") { xml.text url }
|
||||
xml.element("SegmentBase", indexRange: "#{fmt["indexRange"]["start"]}-#{fmt["indexRange"]["end"]}") do
|
||||
xml.element("Initialization", range: "#{fmt["initRange"]["start"]}-#{fmt["initRange"]["end"]}")
|
||||
xml.element("Representation", id: fmt.itag, codecs: fmt.codecs, bandwidth: fmt.bitrate) do
|
||||
xml.element("AudioChannelConfiguration", schemeIdUri: "urn:mpeg:dash:23003:3:audio_channel_configuration:2011", value: fmt.audio_channels)
|
||||
xml.element("BaseURL") { xml.text fmt.url }
|
||||
xml.element("SegmentBase", indexRange: fmt.index_range.to_s) do
|
||||
xml.element("Initialization", range: fmt.init_range.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
@ -92,33 +86,26 @@ module Invidious::Routes::API::Manifest
|
|||
potential_heights = {4320, 2160, 1440, 1080, 720, 480, 360, 240, 144}
|
||||
|
||||
{"video/mp4"}.each do |mime_type|
|
||||
mime_streams = video_streams.select { |stream| stream["mimeType"].as_s.starts_with? mime_type }
|
||||
mime_streams = video.video_streams.select(&.mime_type.== mime_type)
|
||||
next if mime_streams.empty?
|
||||
|
||||
heights = [] of Int32
|
||||
|
||||
xml.element("AdaptationSet", id: i, mimeType: mime_type, startWithSAP: 1, subsegmentAlignment: true, scanType: "progressive") do
|
||||
mime_streams.each do |fmt|
|
||||
# OTF streams aren't supported yet (See https://github.com/TeamNewPipe/NewPipe/issues/2415)
|
||||
next if !(fmt.has_key?("indexRange") && fmt.has_key?("initRange"))
|
||||
|
||||
codecs = fmt["mimeType"].as_s.split("codecs=")[1].strip('"')
|
||||
bandwidth = fmt["bitrate"].as_i
|
||||
itag = fmt["itag"].as_i
|
||||
url = fmt["url"].as_s
|
||||
width = fmt["width"].as_i
|
||||
height = fmt["height"].as_i
|
||||
next if (fmt.index_range.nil? || fmt.init_range.nil?)
|
||||
|
||||
# Resolutions reported by YouTube player (may not accurately reflect source)
|
||||
height = potential_heights.min_by { |x| (height - x).abs }
|
||||
height = potential_heights.min_by { |x| (fmt.video_height - x).abs }
|
||||
next if unique_res && heights.includes? height
|
||||
heights << height
|
||||
|
||||
xml.element("Representation", id: itag, codecs: codecs, width: width, height: height,
|
||||
startWithSAP: "1", maxPlayoutRate: "1",
|
||||
bandwidth: bandwidth, frameRate: fmt["fps"]) do
|
||||
xml.element("BaseURL") { xml.text url }
|
||||
xml.element("SegmentBase", indexRange: "#{fmt["indexRange"]["start"]}-#{fmt["indexRange"]["end"]}") do
|
||||
xml.element("Initialization", range: "#{fmt["initRange"]["start"]}-#{fmt["initRange"]["end"]}")
|
||||
xml.element("Representation", id: fmt.itag, codecs: fmt.codecs, width: fmt.video_width, height: height,
|
||||
startWithSAP: "1", maxPlayoutRate: "1", bandwidth: fmt.bitrate, frameRate: fmt.video_fps) do
|
||||
xml.element("BaseURL") { xml.text fmt.url }
|
||||
xml.element("SegmentBase", indexRange: fmt.index_range.to_s) do
|
||||
xml.element("Initialization", range: fmt.init_range.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -129,8 +116,6 @@ module Invidious::Routes::API::Manifest
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
return manifest
|
||||
end
|
||||
|
||||
# /api/manifest/dash/id/videoplayback
|
||||
|
|
|
@ -157,8 +157,8 @@ module Invidious::Routes::Embed
|
|||
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) }
|
||||
fmt_stream.each { |fmt| fmt.url = HttpServer::Utils.proxy_video_url(fmt.url) }
|
||||
adaptive_fmts.each { |fmt| fmt.url = HttpServer::Utils.proxy_video_url(fmt.url) }
|
||||
end
|
||||
|
||||
video_streams = video.video_streams
|
||||
|
@ -192,10 +192,10 @@ module Invidious::Routes::Embed
|
|||
thumbnail = "/vi/#{video.id}/maxres.jpg"
|
||||
|
||||
if params.raw
|
||||
url = fmt_stream[0]["url"].as_s
|
||||
url = fmt_stream[0].url
|
||||
|
||||
fmt_stream.each do |fmt|
|
||||
url = fmt["url"].as_s if fmt["quality"].as_s == params.quality
|
||||
url = fmt.url if fmt.label == params.quality
|
||||
end
|
||||
|
||||
return env.redirect url
|
||||
|
|
|
@ -277,13 +277,14 @@ module Invidious::Routes::VideoPlayback
|
|||
return error_template(500, ex)
|
||||
end
|
||||
|
||||
fmt = video.fmt_stream.find(nil) { |f| f["itag"].as_i == itag } || video.adaptive_fmts.find(nil) { |f| f["itag"].as_i == itag }
|
||||
url = fmt.try &.["url"]?.try &.as_s
|
||||
fmt = video.fmt_stream.find(nil, &.itag.== itag) || video.adaptive_fmts.find(nil, &.itag.== itag)
|
||||
|
||||
if !url
|
||||
if !fmt
|
||||
haltf env, status_code: 404
|
||||
end
|
||||
|
||||
url = fmt.url
|
||||
|
||||
if local
|
||||
url = URI.parse(url).request_target.not_nil!
|
||||
url += "&title=#{URI.encode_www_form(title, space_to_plus: false)}" if title
|
||||
|
|
|
@ -129,8 +129,8 @@ module Invidious::Routes::Watch
|
|||
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) }
|
||||
fmt_stream.each { |fmt| fmt.url = HttpServer::Utils.proxy_video_url(fmt.url) }
|
||||
adaptive_fmts.each { |fmt| fmt.url = HttpServer::Utils.proxy_video_url(fmt.url) }
|
||||
end
|
||||
|
||||
video_streams = video.video_streams
|
||||
|
@ -168,21 +168,21 @@ module Invidious::Routes::Watch
|
|||
|
||||
if params.raw
|
||||
if params.listen
|
||||
url = audio_streams[0]["url"].as_s
|
||||
url = audio_streams[0].url
|
||||
|
||||
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 fmt.bitrate == params.quality.rchop("k").to_i
|
||||
url = fmt.url
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
url = fmt_stream[0]["url"].as_s
|
||||
url = fmt_stream[0].url
|
||||
|
||||
fmt_stream.each do |fmt|
|
||||
if fmt["quality"].as_s == params.quality
|
||||
url = fmt["url"].as_s
|
||||
if fmt.label == params.quality
|
||||
url = fmt.url
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -94,53 +94,28 @@ struct Video
|
|||
|
||||
# Methods for parsing streaming data
|
||||
|
||||
def fmt_stream
|
||||
return @fmt_stream.as(Array(Hash(String, JSON::Any))) if @fmt_stream
|
||||
|
||||
fmt_stream = info["streamingData"]?.try &.["formats"]?.try &.as_a.map &.as_h || [] of Hash(String, JSON::Any)
|
||||
fmt_stream.each do |fmt|
|
||||
if s = (fmt["cipher"]? || fmt["signatureCipher"]?).try { |h| HTTP::Params.parse(h.as_s) }
|
||||
s.each do |k, v|
|
||||
fmt[k] = JSON::Any.new(v)
|
||||
end
|
||||
fmt["url"] = JSON::Any.new("#{fmt["url"]}#{DECRYPT_FUNCTION.decrypt_signature(fmt)}")
|
||||
end
|
||||
|
||||
fmt["url"] = JSON::Any.new("#{fmt["url"]}&host=#{URI.parse(fmt["url"].as_s).host}")
|
||||
fmt["url"] = JSON::Any.new("#{fmt["url"]}®ion=#{self.info["region"]}") if self.info["region"]?
|
||||
def fmt_stream : Array(IV::Videos::ProgressiveHttpStream)
|
||||
if formats = info.dig?("streamingData", "formats")
|
||||
return IV::Videos.parse_progressive_formats(formats)
|
||||
else
|
||||
return [] of IV::Videos::ProgressiveHttpStream
|
||||
end
|
||||
|
||||
fmt_stream.sort_by! { |f| f["width"]?.try &.as_i || 0 }
|
||||
@fmt_stream = fmt_stream
|
||||
return @fmt_stream.as(Array(Hash(String, JSON::Any)))
|
||||
end
|
||||
|
||||
def adaptive_fmts
|
||||
return @adaptive_fmts.as(Array(Hash(String, JSON::Any))) if @adaptive_fmts
|
||||
fmt_stream = info["streamingData"]?.try &.["adaptiveFormats"]?.try &.as_a.map &.as_h || [] of Hash(String, JSON::Any)
|
||||
fmt_stream.each do |fmt|
|
||||
if s = (fmt["cipher"]? || fmt["signatureCipher"]?).try { |h| HTTP::Params.parse(h.as_s) }
|
||||
s.each do |k, v|
|
||||
fmt[k] = JSON::Any.new(v)
|
||||
end
|
||||
fmt["url"] = JSON::Any.new("#{fmt["url"]}#{DECRYPT_FUNCTION.decrypt_signature(fmt)}")
|
||||
end
|
||||
|
||||
fmt["url"] = JSON::Any.new("#{fmt["url"]}&host=#{URI.parse(fmt["url"].as_s).host}")
|
||||
fmt["url"] = JSON::Any.new("#{fmt["url"]}®ion=#{self.info["region"]}") if self.info["region"]?
|
||||
def adaptive_fmts : Array(IV::Videos::AdaptativeStream)
|
||||
if formats = info.dig?("streamingData", "adaptiveFormats")
|
||||
return IV::Videos.parse_adaptative_formats(formats)
|
||||
else
|
||||
return [] of IV::Videos::AdaptativeStream
|
||||
end
|
||||
|
||||
fmt_stream.sort_by! { |f| f["width"]?.try &.as_i || 0 }
|
||||
@adaptive_fmts = fmt_stream
|
||||
return @adaptive_fmts.as(Array(Hash(String, JSON::Any)))
|
||||
end
|
||||
|
||||
def video_streams
|
||||
adaptive_fmts.select &.["mimeType"]?.try &.as_s.starts_with?("video")
|
||||
def video_streams : Array(IV::Videos::AdaptativeVideoStream)
|
||||
adaptive_fmts.select(IV::Videos::AdaptativeVideoStream)
|
||||
end
|
||||
|
||||
def audio_streams
|
||||
adaptive_fmts.select &.["mimeType"]?.try &.as_s.starts_with?("audio")
|
||||
def audio_streams : Array(IV::Videos::AdaptativeAudioStream)
|
||||
adaptive_fmts.select(IV::Videos::AdaptativeAudioStream)
|
||||
end
|
||||
|
||||
# Misc. methods
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
module Invidious::Videos::Formats
|
||||
def self.itag_to_metadata?(itag : JSON::Any)
|
||||
def self.itag_to_metadata?(itag : Int)
|
||||
return FORMATS[itag.to_s]?
|
||||
end
|
||||
|
||||
|
|
|
@ -11,19 +11,19 @@
|
|||
best_m4a_stream_index = 0
|
||||
best_m4a_stream_bitrate = 0
|
||||
audio_streams.each_with_index do |fmt, i|
|
||||
bandwidth = fmt["bitrate"].as_i
|
||||
if (fmt["mimeType"].as_s.starts_with?("audio/mp4") && bandwidth > best_m4a_stream_bitrate)
|
||||
bandwidth = fmt.bitrate
|
||||
if (fmt.mime_type == "audio/mp4" && bandwidth > best_m4a_stream_bitrate)
|
||||
best_m4a_stream_bitrate = bandwidth
|
||||
best_m4a_stream_index = i
|
||||
end
|
||||
end
|
||||
|
||||
audio_streams.each_with_index do |fmt, i|
|
||||
src_url = "/latest_version?id=#{video.id}&itag=#{fmt["itag"]}"
|
||||
src_url = "/latest_version?id=#{video.id}&itag=#{fmt.itag}"
|
||||
src_url += "&local=true" if params.local
|
||||
|
||||
bitrate = fmt["bitrate"]
|
||||
mimetype = HTML.escape(fmt["mimeType"].as_s)
|
||||
bitrate = fmt.bitrate // 1000
|
||||
mimetype = HTML.escape(fmt.raw_mime_type)
|
||||
|
||||
selected = (i == best_m4a_stream_index)
|
||||
%>
|
||||
|
@ -38,14 +38,14 @@
|
|||
<% end %>
|
||||
|
||||
<%
|
||||
fmt_stream.reject! { |f| f["itag"] == 17 }
|
||||
fmt_stream.sort_by! {|f| params.quality == f["quality"] ? 0 : 1 }
|
||||
fmt_stream.reject!(&.itag.== 17)
|
||||
fmt_stream.sort_by! { |f| params.quality == f.label ? 0 : 1 }
|
||||
fmt_stream.each_with_index do |fmt, i|
|
||||
src_url = "/latest_version?id=#{video.id}&itag=#{fmt["itag"]}"
|
||||
src_url = "/latest_version?id=#{video.id}&itag=#{fmt.itag}"
|
||||
src_url += "&local=true" if params.local
|
||||
|
||||
quality = fmt["quality"]
|
||||
mimetype = HTML.escape(fmt["mimeType"].as_s)
|
||||
quality = fmt.label
|
||||
mimetype = HTML.escape(fmt.raw_mime_type)
|
||||
|
||||
selected = params.quality ? (params.quality == quality) : (i == 0)
|
||||
%>
|
||||
|
|
Loading…
Reference in a new issue