aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2021-08-24 18:32:26 +0200
committerdec05eba <dec05eba@protonmail.com>2021-08-24 18:32:26 +0200
commit62f918559616138de1cc0ab8f5759f5d714e9287 (patch)
tree7c8886051590b8f0b0806633563fd120ebc6726f /src
parent591c78ff6b148ddd3c97ad48dce15ec697456fe5 (diff)
Youtube: load english subtitles when available
Diffstat (limited to 'src')
-rw-r--r--src/QuickMedia.cpp11
-rw-r--r--src/VideoPlayer.cpp24
-rw-r--r--src/plugins/Youtube.cpp55
3 files changed, 85 insertions, 5 deletions
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index c85f031..1bc260c 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -2622,7 +2622,7 @@ namespace QuickMedia {
}
sf::WindowHandle video_player_window = None;
- auto on_window_create = [this, &video_player_window, &video_loaded](sf::WindowHandle _video_player_window) mutable {
+ auto on_window_create = [this, &video_player_window, &video_loaded, &video_page](sf::WindowHandle _video_player_window) mutable {
video_player_window = _video_player_window;
XSelectInput(disp, video_player_window, KeyPressMask | PointerMotionMask);
XSync(disp, False);
@@ -2633,6 +2633,11 @@ namespace QuickMedia {
video_player->get_time_in_file(&time_in_file);
if(time_in_file > 0.00001)
video_loaded = true;
+
+ SubtitleData subtitle_data;
+ video_page->get_subtitles(subtitle_data);
+ if(!subtitle_data.url.empty())
+ video_player->add_subtitle(subtitle_data.url, subtitle_data.title, "eng");
};
std::unique_ptr<YoutubeMediaProxy> youtube_video_media_proxy;
@@ -2658,7 +2663,7 @@ namespace QuickMedia {
throttled = true;
};
- auto load_video_error_check = [this, &throttle_handler, &throttled, &youtube_downloader_task, &youtube_video_media_proxy, &youtube_audio_media_proxy, &youtube_video_content_length, &youtube_audio_content_length, &prev_start_time, &media_chapters, &video_url, &audio_url, &has_embedded_audio, &video_title, &video_tasks, &channel_url, previous_page, &go_to_previous_page, &video_loaded, video_page, &video_event_callback, &on_window_create, &video_player_window, is_youtube, is_matrix, download_if_streaming_fails](std::string start_time = "", bool reuse_media_source = false) mutable {
+ auto load_video_error_check = [&](std::string start_time = "", bool reuse_media_source = false) mutable {
video_player.reset();
channel_url.clear();
video_loaded = false;
@@ -2677,7 +2682,7 @@ namespace QuickMedia {
bool load_successful = false;
for(int i = 0; i < num_retries; ++i) {
bool cancelled = false;
- TaskResult load_result = run_task_with_loading_screen([this, video_page, &cancelled, &new_title, &channel_url, &media_chapters, &youtube_video_content_length, &youtube_audio_content_length, largest_monitor_height, &has_embedded_audio, &video_url, &audio_url, &is_audio_only, &previous_page, is_youtube, download_if_streaming_fails]() {
+ TaskResult load_result = run_task_with_loading_screen([&]() {
if(video_page->load(new_title, channel_url, media_chapters) != PluginResult::OK)
return false;
diff --git a/src/VideoPlayer.cpp b/src/VideoPlayer.cpp
index 3e22b8b..4b3ec92 100644
--- a/src/VideoPlayer.cpp
+++ b/src/VideoPlayer.cpp
@@ -409,7 +409,7 @@ namespace QuickMedia {
command_data.append(property_name);
command_data.append(value);
Json::Value command(Json::objectValue);
- command["command"] = command_data;
+ command["command"] = std::move(command_data);
Json::StreamWriterBuilder builder;
builder["commentStyle"] = "None";
@@ -429,7 +429,7 @@ namespace QuickMedia {
command_data.append("get_property");
command_data.append(property_name);
Json::Value command(Json::objectValue);
- command["command"] = command_data;
+ command["command"] = std::move(command_data);
command["request_id"] = cmd_request_id;
Json::StreamWriterBuilder builder;
@@ -472,6 +472,26 @@ namespace QuickMedia {
return err;
}
+ VideoPlayer::Error VideoPlayer::add_subtitle(const std::string &url, const std::string &title, const std::string &lang) {
+ Json::Value command_data(Json::arrayValue);
+ command_data.append("sub-add");
+ command_data.append(url);
+ command_data.append("auto");
+ if(!title.empty()) {
+ command_data.append(title);
+ if(!lang.empty())
+ command_data.append(lang);
+ }
+ Json::Value command(Json::objectValue);
+ command["command"] = std::move(command_data);
+
+ Json::StreamWriterBuilder builder;
+ builder["commentStyle"] = "None";
+ builder["indentation"] = "";
+ const std::string cmd_str = Json::writeString(builder, command) + "\n";
+ return send_command(cmd_str.c_str(), cmd_str.size());
+ }
+
VideoPlayer::Error VideoPlayer::send_command(const char *cmd, size_t size) {
if(!connected_to_ipc)
return Error::FAIL_NOT_CONNECTED;
diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp
index be405b1..2b2a7ee 100644
--- a/src/plugins/Youtube.cpp
+++ b/src/plugins/Youtube.cpp
@@ -2298,10 +2298,51 @@ namespace QuickMedia {
return result;
}
+ static void subtitle_url_set_vtt_format(std::string &subtitle_url) {
+ const size_t index = subtitle_url.find("&fmt=");
+ if(index == std::string::npos)
+ return;
+
+ size_t end_index = subtitle_url.find('&');
+ if(end_index == std::string::npos)
+ end_index = subtitle_url.size();
+
+ subtitle_url.replace(index, end_index - index, "&fmt=vtt");
+ }
+
+ static void parse_caption_tracks(const Json::Value &caption_tracks, std::map<std::string, SubtitleData> &subtitle_urls_by_lang_code) {
+ if(!caption_tracks.isArray())
+ return;
+
+ for(const Json::Value &caption_track : caption_tracks) {
+ if(!caption_track.isObject())
+ continue;
+
+ const Json::Value &base_url_json = caption_track["baseUrl"];
+ const Json::Value &language_code_json = caption_track["languageCode"];
+ if(!base_url_json.isString() || !language_code_json.isString())
+ continue;
+
+ std::string base_url = base_url_json.asString();
+ subtitle_url_set_vtt_format(base_url);
+ std::string language_code = language_code_json.asString();
+
+ std::optional<std::string> title = yt_json_get_text(caption_track, "name");
+ SubtitleData subtitle_data;
+ subtitle_data.url = std::move(base_url);
+ if(title)
+ subtitle_data.title = std::move(title.value());
+ else
+ subtitle_data.title = language_code;
+ subtitle_urls_by_lang_code[std::move(language_code)] = std::move(subtitle_data);
+ }
+ }
+
PluginResult YoutubeVideoPage::parse_video_response(const Json::Value &json_root, std::string &title, std::string &channel_url, std::vector<MediaChapter> &chapters) {
livestream_url.clear();
video_formats.clear();
audio_formats.clear();
+ subtitle_urls_by_lang_code.clear();
title.clear();
channel_url.clear();
chapters.clear();
@@ -2315,6 +2356,7 @@ namespace QuickMedia {
if(status_json.isString() && (strcmp(status_json.asCString(), "UNPLAYABLE") == 0 || strcmp(status_json.asCString(), "LOGIN_REQUIRED") == 0)) {
const Json::Value &reason_json = playability_status_json["reason"];
fprintf(stderr, "Unable to play video, status: %s, reason: %s\n", status_json.asCString(), reason_json.isString() ? reason_json.asCString() : "Unknown");
+ return PluginResult::ERR;
}
}
@@ -2359,6 +2401,13 @@ namespace QuickMedia {
}
}
+ const Json::Value &captions_json = json_root["captions"];
+ if(captions_json.isObject()) {
+ const Json::Value &player_captions_tracklist_renderer_json = captions_json["playerCaptionsTracklistRenderer"];
+ if(player_captions_tracklist_renderer_json.isObject())
+ parse_caption_tracks(player_captions_tracklist_renderer_json["captionTracks"], subtitle_urls_by_lang_code);
+ }
+
const Json::Value &playback_tracing_json = json_root["playbackTracking"];
if(playback_tracing_json.isObject()) {
if(playback_url.empty()) {
@@ -2472,6 +2521,12 @@ R"END(
}
}
+ void YoutubeVideoPage::get_subtitles(SubtitleData &subtitle_data) {
+ auto it = subtitle_urls_by_lang_code.find("en");
+ if(it != subtitle_urls_by_lang_code.end())
+ subtitle_data = it->second;
+ }
+
static bool parse_cipher_format(const Json::Value &format, YoutubeFormat &youtube_format) {
std::map<std::string, std::string> cipher_params;
const Json::Value &cipher_json = format["cipher"];