2023-07-23 17:52:53 +09:00
|
|
|
module Invidious::Videos
|
|
|
|
# Namespace for methods primarily relating to Transcripts
|
|
|
|
module Transcript
|
2023-07-23 19:22:19 +09:00
|
|
|
record TranscriptLine, start_ms : Time::Span, end_ms : Time::Span, line : String
|
|
|
|
|
2023-07-23 17:52:53 +09:00
|
|
|
def self.generate_param(video_id : String, language_code : String, auto_generated : Bool) : String
|
2023-08-25 08:20:20 +09:00
|
|
|
kind = auto_generated ? "asr" : ""
|
2023-07-23 17:52:53 +09:00
|
|
|
|
|
|
|
object = {
|
|
|
|
"1:0:string" => video_id,
|
|
|
|
|
|
|
|
"2:base64" => {
|
2023-08-25 08:20:20 +09:00
|
|
|
"1:string" => kind,
|
2023-07-23 17:52:53 +09:00
|
|
|
"2:string" => language_code,
|
|
|
|
"3:string" => "",
|
|
|
|
},
|
|
|
|
|
|
|
|
"3:varint" => 1_i64,
|
|
|
|
"5:string" => "engagement-panel-searchable-transcript-search-panel",
|
|
|
|
"6:varint" => 1_i64,
|
|
|
|
"7:varint" => 1_i64,
|
|
|
|
"8:varint" => 1_i64,
|
|
|
|
}
|
|
|
|
|
|
|
|
params = object.try { |i| Protodec::Any.cast_json(i) }
|
|
|
|
.try { |i| Protodec::Any.from_json(i) }
|
|
|
|
.try { |i| Base64.urlsafe_encode(i) }
|
|
|
|
.try { |i| URI.encode_www_form(i) }
|
|
|
|
|
|
|
|
return params
|
|
|
|
end
|
2023-07-23 19:22:19 +09:00
|
|
|
|
2023-07-23 19:52:26 +09:00
|
|
|
def self.convert_transcripts_to_vtt(initial_data : Hash(String, JSON::Any), target_language : String) : String
|
|
|
|
# Convert into array of TranscriptLine
|
|
|
|
lines = self.parse(initial_data)
|
2023-07-23 19:22:19 +09:00
|
|
|
|
2023-08-25 07:10:50 +09:00
|
|
|
settings_field = {
|
2023-09-23 22:57:26 +09:00
|
|
|
"Kind" => "captions",
|
|
|
|
"Language" => target_language,
|
2023-08-25 07:10:50 +09:00
|
|
|
}
|
2023-07-23 19:52:26 +09:00
|
|
|
|
2023-08-25 07:10:50 +09:00
|
|
|
# Taken from Invidious::Videos::Captions::Metadata.timedtext_to_vtt()
|
|
|
|
vtt = WebVTT.build(settings_field) do |vtt|
|
2023-07-23 19:52:26 +09:00
|
|
|
lines.each do |line|
|
2023-08-25 07:42:42 +09:00
|
|
|
vtt.cue(line.start_ms, line.end_ms, line.line)
|
2023-07-23 19:52:26 +09:00
|
|
|
end
|
2023-07-23 19:22:19 +09:00
|
|
|
end
|
2023-07-23 19:52:26 +09:00
|
|
|
|
|
|
|
return vtt
|
2023-07-23 19:22:19 +09:00
|
|
|
end
|
|
|
|
|
2023-07-23 19:52:26 +09:00
|
|
|
private def self.parse(initial_data : Hash(String, JSON::Any))
|
2023-07-23 19:22:19 +09:00
|
|
|
body = initial_data.dig("actions", 0, "updateEngagementPanelAction", "content", "transcriptRenderer",
|
|
|
|
"content", "transcriptSearchPanelRenderer", "body", "transcriptSegmentListRenderer",
|
|
|
|
"initialSegments").as_a
|
|
|
|
|
|
|
|
lines = [] of TranscriptLine
|
|
|
|
body.each do |line|
|
2023-07-23 21:02:02 +09:00
|
|
|
# Transcript section headers. They are not apart of the captions and as such we can safely skip them.
|
|
|
|
if line.as_h.has_key?("transcriptSectionHeaderRenderer")
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
2023-07-23 19:22:19 +09:00
|
|
|
line = line["transcriptSegmentRenderer"]
|
2023-07-23 21:02:02 +09:00
|
|
|
|
2023-07-23 19:22:19 +09:00
|
|
|
start_ms = line["startMs"].as_s.to_i.millisecond
|
|
|
|
end_ms = line["endMs"].as_s.to_i.millisecond
|
|
|
|
|
|
|
|
text = extract_text(line["snippet"]) || ""
|
|
|
|
|
|
|
|
lines << TranscriptLine.new(start_ms, end_ms, text)
|
|
|
|
end
|
|
|
|
|
|
|
|
return lines
|
|
|
|
end
|
2023-07-23 17:52:53 +09:00
|
|
|
end
|
|
|
|
end
|