aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2021-06-11 04:51:03 +0200
committerdec05eba <dec05eba@protonmail.com>2021-06-11 04:51:03 +0200
commit89383cff1ba5d8a928262fcb4c40382a981c78c8 (patch)
treeda8b6062cc6770fd37e7a6f7b8c09fb61f46f7b2 /src
parent5b3becc79461d4ecf015e33515871cc09e26e04e (diff)
Remove dependency on youtube-dl for streaming youtube, resulting in faster video startup
Diffstat (limited to 'src')
-rw-r--r--src/AsyncImageLoader.cpp17
-rw-r--r--src/DownloadUtils.cpp4
-rw-r--r--src/Program.cpp4
-rw-r--r--src/QuickMedia.cpp229
-rw-r--r--src/StringUtils.cpp16
-rw-r--r--src/VideoPlayer.cpp14
-rw-r--r--src/plugins/ImageBoard.cpp2
-rw-r--r--src/plugins/MediaGeneric.cpp2
-rw-r--r--src/plugins/Youtube.cpp269
-rw-r--r--src/plugins/youtube/Signature.cpp307
10 files changed, 740 insertions, 124 deletions
diff --git a/src/AsyncImageLoader.cpp b/src/AsyncImageLoader.cpp
index 1a1e120..9e8d090 100644
--- a/src/AsyncImageLoader.cpp
+++ b/src/AsyncImageLoader.cpp
@@ -87,7 +87,7 @@ namespace QuickMedia {
loading_image[i] = false;
}
- load_image_thread = std::thread([this]() mutable {
+ load_image_thread = AsyncTask<void>([this]() mutable {
std::optional<ThumbnailLoadData> thumbnail_load_data_opt;
while(true) {
thumbnail_load_data_opt = image_load_queue.pop_wait();
@@ -126,17 +126,6 @@ namespace QuickMedia {
AsyncImageLoader::~AsyncImageLoader() {
image_load_queue.close();
- if(load_image_thread.joinable()) {
- program_kill_in_thread(load_image_thread.get_id());
- load_image_thread.join();
- }
-
- for(size_t i = 0; i < NUM_IMAGE_LOAD_THREADS; ++i) {
- if(download_image_thread[i].joinable()) {
- program_kill_in_thread(download_image_thread[i].get_id());
- download_image_thread[i].join();
- }
- }
}
void AsyncImageLoader::load_thumbnail(const std::string &url, bool local, sf::Vector2i resize_target_size, std::shared_ptr<ThumbnailData> thumbnail_data) {
@@ -178,11 +167,9 @@ namespace QuickMedia {
loading_image[free_index] = true;
thumbnail_data->loading_state = LoadingState::LOADING;
- if(download_image_thread[free_index].joinable())
- download_image_thread[free_index].join();
// TODO: Keep the thread running and use conditional variable instead to sleep until a new image should be loaded. Same in ImageViewer.
- download_image_thread[free_index] = std::thread([this, free_index, thumbnail_path, url, resize_target_size, thumbnail_data]() mutable {
+ download_image_thread[free_index] = AsyncTask<void>([this, free_index, thumbnail_path, url, resize_target_size, thumbnail_data]() mutable {
thumbnail_data->image = std::make_unique<sf::Image>();
Path thumbnail_path_resized = thumbnail_path;
diff --git a/src/DownloadUtils.cpp b/src/DownloadUtils.cpp
index 774fba9..0977b78 100644
--- a/src/DownloadUtils.cpp
+++ b/src/DownloadUtils.cpp
@@ -24,6 +24,7 @@ static const char *useragent_str = "user-agent: Mozilla/5.0 (X11; Linux x86_64)
namespace QuickMedia {
DownloadResult download_head_to_string(const std::string &url, std::string &result, bool use_browser_useragent, bool fail_on_error) {
+ result.clear();
sf::Clock timer;
std::vector<const char*> args;
args.insert(args.end(), { "curl", "-I", "-g", "-H", "Accept-Language: en-US,en;q=0.5", "-H", "Connection: keep-alive", "--compressed", "-s" });
@@ -50,6 +51,7 @@ namespace QuickMedia {
}
DownloadResult url_get_remote_name(const std::string &url, std::string &result, bool use_browser_useragent) {
+ result.clear();
sf::Clock timer;
std::vector<const char*> args;
args.insert(args.end(), { "curl", "-I", "-g", "-H", "Accept-Language: en-US,en;q=0.5", "-H", "Connection: keep-alive", "--compressed", "-s" });
@@ -131,6 +133,7 @@ namespace QuickMedia {
// TODO: Add timeout
DownloadResult download_to_string(const std::string &url, std::string &result, const std::vector<CommandArg> &additional_args, bool use_browser_useragent, bool fail_on_error) {
+ result.clear();
sf::Clock timer;
std::vector<const char*> args;
args.insert(args.end(), { "curl", "-H", "Accept-Language: en-US,en;q=0.5", "-H", "Connection: keep-alive", "--compressed", "-g", "-s", "-L" });
@@ -162,6 +165,7 @@ namespace QuickMedia {
}
DownloadResult download_to_string_cache(const std::string &url, std::string &result, const std::vector<CommandArg> &additional_args, bool use_browser_useragent, DownloadErrorHandler error_handler, Path cache_path) {
+ result.clear();
Path media_file_path;
if(cache_path.data.empty()) {
SHA256 sha256;
diff --git a/src/Program.cpp b/src/Program.cpp
index a8189ae..5220a4c 100644
--- a/src/Program.cpp
+++ b/src/Program.cpp
@@ -280,3 +280,7 @@ void program_clear_current_thread() {
void program_kill_in_thread(const std::thread::id &thread_id) {
current_thread_program.kill_in_thread(thread_id);
}
+
+bool program_is_dead_in_current_thread() {
+ return current_thread_program.is_killed();
+}
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index fa0f0d8..7479299 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -2145,30 +2145,6 @@ namespace QuickMedia {
return false;
}
- static bool youtube_url_extract_id(const std::string &youtube_url, std::string &youtube_video_id) {
- size_t index = youtube_url.find("youtube.com/watch?v=");
- if(index != std::string::npos) {
- index += 20;
- size_t end_index = youtube_url.find("&", index);
- if(end_index == std::string::npos)
- end_index = youtube_url.size();
- youtube_video_id = youtube_url.substr(index, end_index - index);
- return true;
- }
-
- index = youtube_url.find("youtu.be/");
- if(index != std::string::npos) {
- index += 9;
- size_t end_index = youtube_url.find("?", index);
- if(end_index == std::string::npos)
- end_index = youtube_url.size();
- youtube_video_id = youtube_url.substr(index, end_index - index);
- return true;
- }
-
- return false;
- }
-
static int watch_history_get_item_by_id(const Json::Value &video_history_json, const char *id) {
assert(video_history_json.isArray());
@@ -2372,68 +2348,14 @@ namespace QuickMedia {
void Program::video_content_page(Page *parent_page, VideoPage *video_page, std::string video_title, bool download_if_streaming_fails, BodyItems &next_play_items, int play_index, int *parent_body_page, const std::string &parent_page_search) {
PageType previous_page = pop_page_stack();
- std::string video_url = video_page->get_url();
- std::string original_video_url = video_url;
sf::Clock time_watched_timer;
bool video_loaded = false;
- std::string youtube_video_id;
- const bool is_youtube = youtube_url_extract_id(video_url, youtube_video_id);
+ std::string youtube_video_id_dummy;
+ const bool is_youtube = youtube_url_extract_id(video_page->get_url(), youtube_video_id_dummy);
const bool is_matrix = strcmp(plugin_name, "matrix") == 0;
- bool video_url_is_local = false;
- if(!is_youtube && download_if_streaming_fails) {
- Path video_cache_dir = get_cache_dir().join("media");
- Path video_path = video_cache_dir;
- SHA256 sha256;
- sha256.add(video_url.data(), video_url.size());
- video_path.join(sha256.getHash());
- if(get_file_type(video_path) == FileType::REGULAR) {
- fprintf(stderr, "%s is found in cache. Playing from cache...\n", video_url.c_str());
- video_url = std::move(video_path.data);
- video_url_is_local = true;
- } else {
- TaskResult video_is_not_streamble_result = run_task_with_loading_screen([video_url]() {
- return video_url_is_non_streamable_mp4(video_url.c_str());
- });
- if(video_is_not_streamble_result == TaskResult::TRUE) {
- fprintf(stderr, "%s is detected to be a non-streamable mp4 file, downloading it before playing it...\n", video_url.c_str());
- if(create_directory_recursive(video_cache_dir) != 0) {
- show_notification("QuickMedia", "Failed to create video cache directory", Urgency::CRITICAL);
- current_page = previous_page;
- return;
- }
-
- TaskResult download_file_result = run_task_with_loading_screen([&video_path, video_url]() {
- return download_to_file(video_url, video_path.data, {}, true) == DownloadResult::OK;
- });
- switch(download_file_result) {
- case TaskResult::TRUE: {
- video_url = std::move(video_path.data);
- video_url_is_local = true;
- break;
- }
- case TaskResult::FALSE: {
- show_notification("QuickMedia", "Failed to download " + video_url, Urgency::CRITICAL);
- current_page = previous_page;
- return;
- }
- case TaskResult::CANCEL: {
- current_page = previous_page;
- return;
- }
- }
- } else if(video_is_not_streamble_result == TaskResult::CANCEL) {
- current_page = previous_page;
- return;
- }
- }
- }
-
- window.setFramerateLimit(FPS_IDLE);
- idle = true;
-
- time_watched_timer.restart();
+ idle_active_handler();
std::unique_ptr<VideoPlayer> video_player;
BodyItems related_videos;
@@ -2451,14 +2373,110 @@ namespace QuickMedia {
std::function<void(const char*)> video_event_callback;
- auto load_video_error_check = [this, &related_videos, &channel_url, &video_url, &video_title, &video_player, previous_page, &time_watched_timer, &video_loaded, video_page, &video_event_callback, &on_window_create, &video_player_window, is_youtube, is_matrix](bool resume_video) mutable {
+ auto load_video_error_check = [this, &related_videos, &channel_url, &video_title, &video_player, previous_page, &time_watched_timer, &video_loaded, video_page, &video_event_callback, &on_window_create, &video_player_window, is_youtube, is_matrix, download_if_streaming_fails](bool resume_video) mutable {
+ TaskResult load_result = run_task_with_loading_screen([video_page]() {
+ return video_page->load() == PluginResult::OK;
+ });
+
+ if(load_result == TaskResult::CANCEL) {
+ current_page = previous_page;
+ return;
+ } else if(load_result == TaskResult::FALSE) {
+ show_notification("QuickMedia", "Failed to load media", Urgency::CRITICAL);
+ current_page = previous_page;
+ return;
+ }
+
+ bool is_audio_only = no_video;
+ bool has_embedded_audio = true;
+ const int largest_monitor_height = get_largest_monitor_height(disp);
+ std::string video_url = video_page->get_video_url(largest_monitor_height, has_embedded_audio);
+ std::string audio_url;
+ if(video_url.empty() || no_video) {
+ video_url = video_page->get_audio_url();
+ if(video_url.empty()) {
+ video_url = video_page->get_url();
+ has_embedded_audio = true;
+ } else {
+ is_audio_only = true;
+ has_embedded_audio = false;
+ }
+ } else if(!has_embedded_audio) {
+ audio_url = video_page->get_audio_url();
+ }
+
+ bool video_url_is_local = false;
+ if(!is_youtube && download_if_streaming_fails) {
+ Path video_cache_dir = get_cache_dir().join("media");
+ Path video_path = video_cache_dir;
+ SHA256 sha256;
+ sha256.add(video_url.data(), video_url.size());
+ video_path.join(sha256.getHash());
+ if(get_file_type(video_path) == FileType::REGULAR) {
+ fprintf(stderr, "%s is found in cache. Playing from cache...\n", video_url.c_str());
+ video_url = std::move(video_path.data);
+ video_url_is_local = true;
+ audio_url.clear();
+ if(no_video) {
+ is_audio_only = true;
+ has_embedded_audio = false;
+ } else {
+ is_audio_only = false;
+ has_embedded_audio = true;
+ }
+ } else {
+ TaskResult video_is_not_streamble_result = run_task_with_loading_screen([video_url]() {
+ return video_url_is_non_streamable_mp4(video_url.c_str());
+ });
+ if(video_is_not_streamble_result == TaskResult::TRUE) {
+ fprintf(stderr, "%s is detected to be a non-streamable mp4 file, downloading it before playing it...\n", video_url.c_str());
+ if(create_directory_recursive(video_cache_dir) != 0) {
+ show_notification("QuickMedia", "Failed to create video cache directory", Urgency::CRITICAL);
+ current_page = previous_page;
+ return;
+ }
+
+ TaskResult download_file_result = run_task_with_loading_screen([&video_path, video_url]() {
+ return download_to_file(video_url, video_path.data, {}, true) == DownloadResult::OK;
+ });
+ switch(download_file_result) {
+ case TaskResult::TRUE: {
+ video_url = std::move(video_path.data);
+ video_url_is_local = true;
+ audio_url.clear();
+ if(no_video) {
+ is_audio_only = true;
+ has_embedded_audio = false;
+ } else {
+ is_audio_only = false;
+ has_embedded_audio = true;
+ }
+ break;
+ }
+ case TaskResult::FALSE: {
+ show_notification("QuickMedia", "Failed to download " + video_url, Urgency::CRITICAL);
+ current_page = previous_page;
+ return;
+ }
+ case TaskResult::CANCEL: {
+ current_page = previous_page;
+ return;
+ }
+ }
+ } else if(video_is_not_streamble_result == TaskResult::CANCEL) {
+ current_page = previous_page;
+ return;
+ }
+ }
+ }
+
time_watched_timer.restart();
video_loaded = false;
video_player_window = None;
watched_videos.insert(video_url);
- video_player = std::make_unique<VideoPlayer>(no_video, use_system_mpv_config, resume_video, is_matrix && !is_youtube, video_event_callback, on_window_create, resources_root, get_largest_monitor_height(disp));
- VideoPlayer::Error err = video_player->load_video(video_url.c_str(), window.getSystemHandle(), plugin_name, video_title);
+ video_player = std::make_unique<VideoPlayer>(is_audio_only, use_system_mpv_config, resume_video, is_matrix && !is_youtube, video_event_callback, on_window_create, resources_root, largest_monitor_height);
+ VideoPlayer::Error err = video_player->load_video(video_url.c_str(), audio_url.c_str(), window.getSystemHandle(), plugin_name, video_title);
if(err != VideoPlayer::Error::OK) {
std::string err_msg = "Failed to play url: ";
err_msg += video_url;
@@ -2469,8 +2487,9 @@ namespace QuickMedia {
return;
channel_url.clear();
- TaskResult load_related_media_result = run_task_with_loading_screen([video_page, &related_videos, &video_url, &channel_url]{
- related_videos = video_page->get_related_media(video_url, channel_url);
+ TaskResult load_related_media_result = run_task_with_loading_screen([video_page, &related_videos, &channel_url]{
+ // TODO: Do async
+ related_videos = video_page->get_related_media(video_page->get_url(), channel_url);
return true;
});
@@ -2484,9 +2503,9 @@ namespace QuickMedia {
return;
std::string video_id;
- if(!youtube_url_extract_id(video_url, video_id)) {
+ if(!youtube_url_extract_id(video_page->get_url(), video_id)) {
std::string err_msg = "Failed to extract id of youtube url ";
- err_msg += video_url;
+ err_msg += video_page->get_url();
err_msg + ", video wont be saved in history";
show_notification("QuickMedia", err_msg.c_str(), Urgency::LOW);
return;
@@ -2545,20 +2564,21 @@ namespace QuickMedia {
bool cursor_visible = true;
sf::Clock cursor_hide_timer;
- auto save_video_url_to_clipboard = [&original_video_url, &video_player]() {
- if(video_url_supports_timestamp(original_video_url)) {
+ auto save_video_url_to_clipboard = [video_page, &video_player]() {
+ std::string url = video_page->get_url();
+ if(video_url_supports_timestamp(url)) {
// TODO: Remove timestamp (&t= or ?t=) from video_url
double time_in_file = 0.0;
if(video_player && (video_player->get_time_in_file(&time_in_file) != VideoPlayer::Error::OK))
time_in_file = 0.0;
- std::string clipboard = original_video_url;
+ std::string clipboard = std::move(url);
if((int)time_in_file > 0)
clipboard += "&t=" + std::to_string((int)time_in_file);
sf::Clipboard::setString(sf::String::fromUtf8(clipboard.begin(), clipboard.end()));
} else {
- sf::Clipboard::setString(sf::String::fromUtf8(original_video_url.begin(), original_video_url.end()));
+ sf::Clipboard::setString(sf::String::fromUtf8(url.begin(), url.end()));
}
};
@@ -2600,7 +2620,7 @@ namespace QuickMedia {
} else if(pressed_keysym == XK_f && pressing_ctrl) {
window_set_fullscreen(disp, window.getSystemHandle(), WindowFullscreenState::TOGGLE);
} else if(pressed_keysym == XK_s && pressing_ctrl) {
- video_page_download_video(original_video_url, !is_matrix || is_youtube, video_player_window);
+ video_page_download_video(video_page->get_url(), !is_matrix || is_youtube, video_player_window);
} else if(pressed_keysym == XK_r && pressing_ctrl) {
if(!cursor_visible)
window.setMouseCursorVisible(true);
@@ -2609,7 +2629,7 @@ namespace QuickMedia {
int search_delay = 0;
auto search_page = video_page->create_search_page(this, search_delay);
auto comments_page = video_page->create_comments_page(this);
- auto related_videos_page = video_page->create_related_videos_page(this, video_url, video_title);
+ auto related_videos_page = video_page->create_related_videos_page(this);
auto channels_page = video_page->create_channels_page(this, channel_url);
if(search_page || related_videos_page || channels_page) {
XUnmapWindow(disp, video_player_window);
@@ -2654,7 +2674,6 @@ namespace QuickMedia {
if(page_changed) {
current_page = PageType::VIDEO_CONTENT;
- //video_player = std::make_unique<VideoPlayer>(no_video, use_system_mpv_config, true, video_event_callback, on_window_create, resources_root);
load_video_error_check(true);
} else {
XMapWindow(disp, video_player_window);
@@ -2729,8 +2748,20 @@ namespace QuickMedia {
break;
}
- video_url = video_page->url_get_playable_url(new_video_url);
- original_video_url = video_url;
+ TaskResult get_playable_url_result = run_task_with_loading_screen([video_page, &new_video_url]() {
+ video_page->set_url(video_page->url_get_playable_url(new_video_url));
+ return true;
+ });
+
+ if(get_playable_url_result == TaskResult::CANCEL) {
+ current_page = previous_page;
+ return;
+ } else if(get_playable_url_result == TaskResult::FALSE) {
+ show_notification("QuickMedia", "Failed to get playable url", Urgency::CRITICAL);
+ current_page = previous_page;
+ return;
+ }
+
video_title = std::move(new_video_title);
load_video_error_check(false);
} else if(update_err != VideoPlayer::Error::OK) {
@@ -3516,7 +3547,7 @@ namespace QuickMedia {
page_stack.push(PageType::IMAGE_BOARD_THREAD);
current_page = PageType::VIDEO_CONTENT;
watched_videos.clear();
- thread_page->video_url = selected_item->url;
+ thread_page->set_url(selected_item->url);
BodyItems next_items;
// TODO: Use real title
video_content_page(thread_page, thread_page, selected_item->get_title(), true, thread_body->items, thread_body->get_selected_item());
@@ -5054,7 +5085,7 @@ namespace QuickMedia {
bool is_audio = (message_type == MessageType::AUDIO);
bool prev_no_video = no_video;
no_video = is_audio;
- video_page->url = selected->url;
+ video_page->set_url(selected->url);
BodyItems next_items;
// TODO: Add title
video_content_page(matrix_chat_page, video_page.get(), "", message_type == MessageType::VIDEO || message_type == MessageType::AUDIO, next_items, 0);
diff --git a/src/StringUtils.cpp b/src/StringUtils.cpp
index 0345558..8ed142f 100644
--- a/src/StringUtils.cpp
+++ b/src/StringUtils.cpp
@@ -2,7 +2,8 @@
#include <string.h>
namespace QuickMedia {
- void string_split(const std::string &str, char delimiter, StringSplitCallback callback_func) {
+ template <typename T>
+ static void string_split_t(const std::string &str, const T &delimiter, StringSplitCallback callback_func) {
size_t index = 0;
while(index < str.size()) {
size_t new_index = str.find(delimiter, index);
@@ -12,10 +13,21 @@ namespace QuickMedia {
if(!callback_func(str.data() + index, new_index - index))
break;
- index = new_index + 1;
+ if constexpr(std::is_same<char, T>::value)
+ index = new_index + 1;
+ else
+ index = new_index + delimiter.size();
}
}
+ void string_split(const std::string &str, const std::string &delimiter, StringSplitCallback callback_func) {
+ string_split_t(str, delimiter, callback_func);
+ }
+
+ void string_split(const std::string &str, char delimiter, StringSplitCallback callback_func) {
+ string_split_t(str, delimiter, callback_func);
+ }
+
size_t string_replace_all(std::string &str, char old_char, char new_char) {
size_t num_replaced_substrings = 0;
for(char &c : str) {
diff --git a/src/VideoPlayer.cpp b/src/VideoPlayer.cpp
index adebddc..2b0653c 100644
--- a/src/VideoPlayer.cpp
+++ b/src/VideoPlayer.cpp
@@ -80,7 +80,7 @@ namespace QuickMedia {
return result;
}
- VideoPlayer::Error VideoPlayer::launch_video_process(const char *path, sf::WindowHandle _parent_window, const std::string &plugin_name, const std::string &) {
+ VideoPlayer::Error VideoPlayer::launch_video_process(const char *path, const char *audio_path, sf::WindowHandle _parent_window, const std::string &plugin_name, const std::string &) {
parent_window = _parent_window;
if(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == -1) {
@@ -172,6 +172,12 @@ namespace QuickMedia {
if(no_video)
args.push_back("--no-video");
+ std::string audio_file_arg = "--audio-file=";
+ if(audio_path && audio_path[0] != '\0') {
+ audio_file_arg += audio_path;
+ args.push_back(audio_file_arg.c_str());
+ }
+
args.insert(args.end(), { "--", path, nullptr });
if(exec_program_async(args.data(), &video_process_id) != 0) {
@@ -190,12 +196,14 @@ namespace QuickMedia {
return Error::OK;
}
- VideoPlayer::Error VideoPlayer::load_video(const char *path, sf::WindowHandle _parent_window, const std::string &plugin_name, const std::string &title) {
+ VideoPlayer::Error VideoPlayer::load_video(const char *path, const char *audio_path, sf::WindowHandle _parent_window, const std::string &plugin_name, const std::string &title) {
// This check is to make sure we dont change window that the video belongs to. This is not a usecase we will have so
// no need to support it for now at least.
assert(parent_window == 0 || parent_window == _parent_window);
+ assert(path);
+ fprintf(stderr, "Playing video: %s, audio: %s\n", path ? path : "", audio_path ? audio_path : "");
if(video_process_id == -1)
- return launch_video_process(path, _parent_window, plugin_name, title);
+ return launch_video_process(path, audio_path, _parent_window, plugin_name, title);
Json::Value command_data(Json::arrayValue);
command_data.append("loadfile");
diff --git a/src/plugins/ImageBoard.cpp b/src/plugins/ImageBoard.cpp
index 881d8ff..a2ffca7 100644
--- a/src/plugins/ImageBoard.cpp
+++ b/src/plugins/ImageBoard.cpp
@@ -1,7 +1,7 @@
#include "../../plugins/ImageBoard.hpp"
namespace QuickMedia {
- std::unique_ptr<RelatedVideosPage> ImageBoardThreadPage::create_related_videos_page(Program*, const std::string&, const std::string&) {
+ std::unique_ptr<RelatedVideosPage> ImageBoardThreadPage::create_related_videos_page(Program*) {
return nullptr;
}
diff --git a/src/plugins/MediaGeneric.cpp b/src/plugins/MediaGeneric.cpp
index 19ad87c..c3a8d8e 100644
--- a/src/plugins/MediaGeneric.cpp
+++ b/src/plugins/MediaGeneric.cpp
@@ -191,7 +191,7 @@ namespace QuickMedia {
return std::make_unique<MediaGenericSearchPage>(*search_page);
}
- std::unique_ptr<RelatedVideosPage> MediaGenericVideoPage::create_related_videos_page(Program *program, const std::string&, const std::string&) {
+ std::unique_ptr<RelatedVideosPage> MediaGenericVideoPage::create_related_videos_page(Program *program) {
return std::make_unique<MediaGenericRelatedPage>(program, search_page);
}
} \ No newline at end of file
diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp
index d9df409..9cec69c 100644
--- a/src/plugins/Youtube.cpp
+++ b/src/plugins/Youtube.cpp
@@ -1,10 +1,12 @@
#include "../../plugins/Youtube.hpp"
+#include "../../plugins/youtube/Signature.hpp"
#include "../../include/Storage.hpp"
#include "../../include/NetUtils.hpp"
#include "../../include/StringUtils.hpp"
#include "../../include/Scale.hpp"
#include "../../include/Notification.hpp"
#include "../../include/Utils.hpp"
+#include <json/reader.h>
extern "C" {
#include <HtmlParser.h>
}
@@ -13,6 +15,30 @@ extern "C" {
#include <unistd.h>
namespace QuickMedia {
+ bool youtube_url_extract_id(const std::string &youtube_url, std::string &youtube_video_id) {
+ size_t index = youtube_url.find("youtube.com/watch?v=");
+ if(index != std::string::npos) {
+ index += 20;
+ size_t end_index = youtube_url.find("&", index);
+ if(end_index == std::string::npos)
+ end_index = youtube_url.size();
+ youtube_video_id = youtube_url.substr(index, end_index - index);
+ return true;
+ }
+
+ index = youtube_url.find("youtu.be/");
+ if(index != std::string::npos) {
+ index += 9;
+ size_t end_index = youtube_url.find("?", index);
+ if(end_index == std::string::npos)
+ end_index = youtube_url.size();
+ youtube_video_id = youtube_url.substr(index, end_index - index);
+ return true;
+ }
+
+ return false;
+ }
+
// This is a common setup of text in the youtube json
static std::optional<std::string> yt_json_get_text(const Json::Value &json, const char *root_name) {
if(!json.isObject())
@@ -257,7 +283,10 @@ namespace QuickMedia {
body_item->set_description_color(sf::Color(179, 179, 179));
body_item->url = "https://www.youtube.com/channel/" + channel_id_json.asString();
if(thumbnail) {
- body_item->thumbnail_url = std::string("https:") + thumbnail->url;
+ if(string_starts_with(thumbnail->url, "https:"))
+ body_item->thumbnail_url = thumbnail->url;
+ else
+ body_item->thumbnail_url = std::string("https:") + thumbnail->url;
body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE;
body_item->thumbnail_size.x = thumbnail->width;
body_item->thumbnail_size.y = thumbnail->height;
@@ -386,13 +415,16 @@ namespace QuickMedia {
static std::vector<CommandArg> get_cookies() {
std::lock_guard<std::mutex> lock(cookies_mutex);
if(cookies_filepath.empty()) {
+ YoutubeSignatureDecryptor::get_instance();
+
Path cookies_filepath_p;
if(get_cookies_filepath(cookies_filepath_p, "youtube") != 0) {
show_notification("QuickMedia", "Failed to create youtube cookies file", Urgency::CRITICAL);
return {};
}
- // TODO: Re-enable this if the api key ever changes in the future
+ // TODO: Re-enable this if the api key ever changes in the future.
+ // Maybe also put signature decryption in the same request? since it requests the same page.
#if 0
//api_key = youtube_page_find_api_key();
#else
@@ -655,6 +687,11 @@ namespace QuickMedia {
return PluginResult::OK;
}
+ PluginResult YoutubeSearchPage::lazy_fetch(BodyItems&) {
+ get_cookies();
+ return PluginResult::OK;
+ }
+
PluginResult YoutubeSearchPage::search_get_continuation(const std::string &url, const std::string &current_continuation_token, BodyItems &result_items) {
std::string next_url = url + "&pbj=1&ctoken=" + current_continuation_token;
@@ -1800,11 +1837,237 @@ namespace QuickMedia {
return std::make_unique<YoutubeCommentsPage>(program, xsrf_token, comments_continuation_token);
}
- std::unique_ptr<RelatedVideosPage> YoutubeVideoPage::create_related_videos_page(Program *program, const std::string&, const std::string&) {
+ std::unique_ptr<RelatedVideosPage> YoutubeVideoPage::create_related_videos_page(Program *program) {
return std::make_unique<YoutubeRelatedVideosPage>(program);
}
std::unique_ptr<Page> YoutubeVideoPage::create_channels_page(Program *program, const std::string &channel_url) {
return std::make_unique<YoutubeChannelPage>(program, channel_url, "", "Channel videos");
}
+
+ static std::map<std::string, std::string> http_params_parse(const std::string &http_params) {
+ std::map<std::string, std::string> result;
+ string_split(http_params, '&', [&result](const char *str, size_t size) {
+ const void *split_p = memchr(str, '=', size);
+ if(split_p == nullptr)
+ return true;
+
+ std::string key(str, (const char*)split_p - str);
+ std::string value((const char*)split_p + 1, (str + size) - ((const char*)split_p + 1));
+ key = url_param_decode(key);
+ value = url_param_decode(value);
+ result[std::move(key)] = std::move(value);
+ return true;
+ });
+ return result;
+ }
+
+ static std::string url_extract_param(const std::string &url, const std::string &param) {
+ std::string param_s = param + "=";
+ size_t index = url.find(param_s);
+ if(index == std::string::npos)
+ return "";
+
+ index += param_s.size();
+ size_t end = url.find('&', index);
+ if(end == std::string::npos)
+ end = url.size();
+
+ return url.substr(index, end - index);
+ }
+
+ std::string YoutubeVideoPage::get_video_url(int max_height, bool &has_embedded_audio) {
+ if(video_formats.empty()) {
+ has_embedded_audio = true;
+ return "";
+ }
+
+ for(const auto &video_format : video_formats) {
+ if(video_format.height <= max_height) {
+ has_embedded_audio = video_format.has_embedded_audio;
+ return video_format.base.url;
+ }
+ }
+
+ has_embedded_audio = video_formats.back().has_embedded_audio;
+ return video_formats.back().base.url;
+ }
+
+ std::string YoutubeVideoPage::get_audio_url() {
+ if(audio_formats.empty())
+ return "";
+
+ return audio_formats.front().base.url;
+ }
+
+ PluginResult YoutubeVideoPage::load() {
+ video_formats.clear();
+ audio_formats.clear();
+
+ std::string video_id;
+ if(!youtube_url_extract_id(url, video_id)) {
+ fprintf(stderr, "Failed to extract youtube id from %s\n", url.c_str());
+ return PluginResult::ERR;
+ }
+
+ std::vector<CommandArg> additional_args = {
+ { "-H", "x-youtube-client-name: 1" },
+ { "-H", "x-youtube-client-version: 2.20200626.03.00" }
+ };
+
+ std::vector<CommandArg> cookies = get_cookies();
+ additional_args.insert(additional_args.end(), cookies.begin(), cookies.end());
+
+ std::string response;
+ DownloadResult download_result = download_to_string("https://www.youtube.com/get_video_info?html5=1&video_id=" + video_id + "&eurl=https://www.youtube.googleapis.com/v/" + video_id, response, std::move(additional_args), true); // TODO: true?
+ if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result);
+
+ std::string player_response_param = url_extract_param(response, "player_response");
+ player_response_param = url_param_decode(player_response_param);
+
+ Json::Value json_root;
+ Json::CharReaderBuilder json_builder;
+ std::unique_ptr<Json::CharReader> json_reader(json_builder.newCharReader());
+ std::string json_errors;
+ if(!json_reader->parse(player_response_param.data(), player_response_param.data() + player_response_param.size(), &json_root, &json_errors)) {
+ fprintf(stderr, "Failed to read param as json, error: %s\n", json_errors.c_str());
+ return PluginResult::ERR;
+ }
+
+ if(!json_root.isObject())
+ return PluginResult::ERR;
+
+ const Json::Value &streaming_data_json = json_root["streamingData"];
+ if(!streaming_data_json.isObject())
+ return PluginResult::ERR;
+
+ parse_formats(streaming_data_json);
+
+ if(video_formats.empty() && audio_formats.empty())
+ return PluginResult::ERR;
+
+ std::sort(video_formats.begin(), video_formats.end(), [](const YoutubeVideoFormat &format1, const YoutubeVideoFormat &format2) {
+ return format1.base.bitrate > format2.base.bitrate;
+ });
+
+ std::sort(audio_formats.begin(), audio_formats.end(), [](const YoutubeAudioFormat &format1, const YoutubeAudioFormat &format2) {
+ return format1.base.bitrate > format2.base.bitrate;
+ });
+
+ return PluginResult::OK;
+ }
+
+ 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"];
+ if(cipher_json.isString()) {
+ cipher_params = http_params_parse(cipher_json.asString());
+ } else {
+ const Json::Value &signature_cipher_json = format["signatureCipher"];
+ if(signature_cipher_json.isString())
+ cipher_params = http_params_parse(signature_cipher_json.asString());
+ }
+
+ const std::string &url = cipher_params["url"];
+ if(cipher_params.empty() || url.empty())
+ return false;
+
+ std::string url_decoded = url_param_decode(url);
+
+ const std::string &s = cipher_params["s"];
+ const std::string &sp = cipher_params["sp"];
+ std::string sig_key;
+ std::string sig_value;
+ if(YoutubeSignatureDecryptor::get_instance().decrypt(s, sp, sig_key, sig_value))
+ url_decoded += "&" + std::move(sig_key) + "=" + std::move(sig_value);
+
+ youtube_format.url = std::move(url_decoded);
+ return true;
+ }
+
+ void YoutubeVideoPage::parse_format(const Json::Value &format_json, bool is_adaptive) {
+ if(!format_json.isArray())
+ return;
+
+ for(const Json::Value &format : format_json) {
+ if(!format.isObject())
+ continue;
+
+ if(is_adaptive) {
+ // TODO: Fix. Some streams use sq/ instead of index
+ const Json::Value &index_range_json = format["indexRange"];
+ if(index_range_json.isNull()) {
+ fprintf(stderr, "Ignoring adaptive stream without indexRange\n");
+ continue;
+ }
+ }
+
+ YoutubeFormat youtube_format_base;
+
+ const Json::Value &mime_type_json = format["mimeType"];
+ if(!mime_type_json.isString()) continue;
+
+ const Json::Value &bitrate_json = format["bitrate"];
+ if(!bitrate_json.isInt()) continue;
+ youtube_format_base.bitrate = bitrate_json.asInt();
+
+ if(strncmp(mime_type_json.asCString(), "video/", 6) == 0) {
+ bool has_embedded_audio = false;
+ const char *codecs_p = strstr(mime_type_json.asCString(), "codecs=\"");
+ if(codecs_p) {
+ codecs_p += 8;
+ const char *codecs_sep_p = strchr(codecs_p, ',');
+ const char *codecs_end_p = strchr(codecs_p, '"');
+ has_embedded_audio = (codecs_sep_p != nullptr && (!codecs_end_p || codecs_sep_p < codecs_end_p));
+ }
+
+ YoutubeVideoFormat video_format;
+ video_format.base = std::move(youtube_format_base);
+ video_format.has_embedded_audio = has_embedded_audio;
+
+ const Json::Value &width_json = format["width"];
+ if(!width_json.isInt()) continue;
+ video_format.width = width_json.asInt();
+
+ const Json::Value &height_json = format["height"];
+ if(!height_json.isInt()) continue;
+ video_format.height = height_json.asInt();
+
+ const Json::Value &fps_json = format["fps"];
+ if(!fps_json.isInt()) continue;
+ video_format.fps = fps_json.asInt();
+
+ const Json::Value &url_json = format["url"];
+ if(url_json.isString()) {
+ video_format.base.url = url_json.asString();
+ } else {
+ if(!parse_cipher_format(format, video_format.base))
+ continue;
+ }
+
+ video_formats.push_back(std::move(video_format));
+ } else if(strncmp(mime_type_json.asCString(), "audio/", 6) == 0) {
+ YoutubeAudioFormat audio_format;
+ audio_format.base = std::move(youtube_format_base);
+
+ const Json::Value &url_json = format["url"];
+ if(url_json.isString()) {
+ audio_format.base.url = url_json.asString();
+ } else {
+ if(!parse_cipher_format(format, audio_format.base))
+ continue;
+ }
+
+ audio_formats.push_back(std::move(audio_format));
+ }
+ }
+ }
+
+ void YoutubeVideoPage::parse_formats(const Json::Value &streaming_data_json) {
+ const Json::Value &formats_json = streaming_data_json["formats"];
+ parse_format(formats_json, false);
+
+ const Json::Value &adaptive_formats_json = streaming_data_json["adaptiveFormats"];
+ parse_format(adaptive_formats_json, false);
+ }
} \ No newline at end of file
diff --git a/src/plugins/youtube/Signature.cpp b/src/plugins/youtube/Signature.cpp
new file mode 100644
index 0000000..30093d5
--- /dev/null
+++ b/src/plugins/youtube/Signature.cpp
@@ -0,0 +1,307 @@
+#include "../../../plugins/youtube/Signature.hpp"
+#include "../../../include/Storage.hpp"
+#include "../../../include/Notification.hpp"
+#include "../../../include/DownloadUtils.hpp"
+#include "../../../include/StringUtils.hpp"
+#include "../../../include/Program.hpp"
+#include <regex>
+#include <unistd.h>
+
+namespace QuickMedia {
+ enum UpdateDecryptFunctionError {
+ U_DEC_FUN_NET_ERR = 1,
+ U_DEC_FUN_FAILED_MATCH_ERR = 2
+ };
+
+ static YoutubeSignatureDecryptor *instance = nullptr;
+ static const int timeout_default_sec = 60;
+
+ bool YoutubeSignatureDecryptor::js_code_to_operations(const std::string &function_body_str, const std::string &var_body_str, std::vector<DecryptFuncCall> &new_func_calls, std::map<std::string, DecryptFunction> &new_func_decls) {
+ std::vector<std::string> function_body;
+ string_split(function_body_str, ';', [&function_body](const char *str, size_t size) {
+ function_body.push_back(std::string(str, size));
+ return true;
+ });
+
+ if(function_body.empty())
+ return false;
+
+ std::vector<std::string> var_body;
+ string_split(var_body_str, "},", [&var_body](const char *str, size_t size) {
+ var_body.push_back(std::string(str, size));
+ return true;
+ });
+
+ if(var_body.empty())
+ return false;
+
+ //fprintf(stderr, "function body: %s\n", function_body_str.c_str());
+ for(const std::string &func_call_str : function_body) {
+ const size_t var_name_split_index = func_call_str.find('.');
+ if(var_name_split_index == std::string::npos) return false;
+
+ const size_t func_name_end_index = func_call_str.find('(', var_name_split_index + 1);
+ if(func_name_end_index == std::string::npos) return false;
+
+ const size_t values_index = func_call_str.find(',', func_name_end_index + 1);
+ if(values_index == std::string::npos) return false;
+
+ const size_t values_end_index = func_call_str.find(')', values_index + 1);
+ if(values_end_index == std::string::npos) return false;
+
+ std::string func_name = func_call_str.substr(var_name_split_index + 1, func_name_end_index - (var_name_split_index + 1));
+ func_name = strip(func_name);
+ std::string value_args = func_call_str.substr(values_index + 1, values_end_index - (values_index + 1));
+
+ errno = 0;
+ char *endptr;
+ const long value_int = strtol(value_args.c_str(), &endptr, 10);
+ if(endptr != value_args.c_str() && errno == 0)
+ new_func_calls.push_back({ std::move(func_name), value_int });
+ else
+ return false;
+
+ //fprintf(stderr, "func_call: %s, value: %ld\n", new_func_calls.back().func_name.c_str(), value_int);
+ }
+
+ //fprintf(stderr, "declaration body: %s\n", var_body_str.c_str());
+ for(const std::string &func_decl_str : var_body) {
+ const size_t func_name_split_index = func_decl_str.find(':');
+ if(func_name_split_index == std::string::npos) return false;
+
+ const size_t func_start_index = func_decl_str.find('{', func_name_split_index + 1);
+ if(func_start_index == std::string::npos) return false;
+
+ std::string func_name = func_decl_str.substr(0, func_name_split_index);
+ func_name = strip(func_name);
+ std::string function_body = func_decl_str.substr(func_start_index + 1);
+ function_body = strip(function_body);
+ if(!function_body.empty() && function_body.back() == '}')
+ function_body.pop_back();
+
+ DecryptFunction decrypt_function;
+ if(function_body == "a.reverse()")
+ decrypt_function = DecryptFunction::REVERSE;
+ else if(function_body == "a.splice(0,b)")
+ decrypt_function = DecryptFunction::SPLICE;
+ else
+ decrypt_function = DecryptFunction::SWAP;
+ //fprintf(stderr, "declared function: %s, body: |%s|\n", func_name.c_str(), function_body.c_str());
+ new_func_decls[std::move(func_name)] = decrypt_function;
+ }
+
+ for(const auto &func_call : new_func_calls) {
+ if(new_func_decls.find(func_call.func_name) == new_func_decls.end()) {
+ fprintf(stderr, "YoutubeSignatureDecryptor: declaration for decryption function %s not found\n", func_call.func_name.c_str());
+ fprintf(stderr, "YoutubeSignatureDecryptor: Regex match 10 invalid. Youtube likely updated and QuickMedia needs to be fixed?\n");
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ YoutubeSignatureDecryptor::YoutubeSignatureDecryptor() {
+ {
+ Path youtube_cache_dir = get_cache_dir().join("youtube");
+ if(create_directory_recursive(youtube_cache_dir) != 0) {
+ show_notification("QuickMedia", "Failed to create youtube cache directory", Urgency::CRITICAL);
+ return;
+ }
+
+ youtube_cache_dir.join("decryption_function");
+ if(file_get_content(youtube_cache_dir, decryption_function) == 0) {
+ file_get_last_modified_time_seconds(youtube_cache_dir.data.c_str(), &decrypt_function_last_updated);
+
+ size_t newline_index = decryption_function.find('\n');
+ if(newline_index != std::string::npos) {
+ std::string function_body_str = decryption_function.substr(0, newline_index);
+ std::string var_body_str = decryption_function.substr(newline_index + 1);
+
+ std::vector<DecryptFuncCall> new_func_calls;
+ std::map<std::string, DecryptFunction> new_func_decls;
+ if(js_code_to_operations(function_body_str, var_body_str, new_func_calls, new_func_decls)) {
+ func_calls = std::move(new_func_calls);
+ func_decls = std::move(new_func_decls);
+
+ time_t time_now = time(nullptr);
+ if(time_now - decrypt_function_last_updated <= timeout_default_sec)
+ up_to_date = true;
+ }
+ }
+ }
+ }
+
+ running = true;
+ poll_task = AsyncTask<void>([this]() mutable {
+ bool has_notified_error = false;
+ int decrypt_function_update_timeout_seconds = timeout_default_sec;
+ while(running) {
+ time_t time_now = time(nullptr);
+ if(time_now - decrypt_function_last_updated > decrypt_function_update_timeout_seconds) {
+ int update_res = update_decrypt_function();
+ if(update_res != 0) {
+ if(!has_notified_error) {
+ if(program_is_dead_in_current_thread()) {
+ running = false;
+ return;
+ } else if(update_res == U_DEC_FUN_NET_ERR) {
+ show_notification("QuickMedia", "Failed to decrypt youtube signature. Is your internet down?", Urgency::CRITICAL);
+ decrypt_function_update_timeout_seconds = 10;
+ } else if(update_res == U_DEC_FUN_FAILED_MATCH_ERR) {
+ show_notification("QuickMedia", "Failed to decrypt youtube signature. Make sure you are running the latest version of QuickMedia", Urgency::CRITICAL);
+ running = false;
+ return;
+ }
+ }
+ has_notified_error = true;
+ } else {
+ decrypt_function_update_timeout_seconds = timeout_default_sec;
+ }
+ }
+ usleep(1 * 1000 * 1000); // 1 second
+ }
+ });
+ }
+
+ YoutubeSignatureDecryptor::~YoutubeSignatureDecryptor() {
+ running = false;
+ }
+
+ // static
+ YoutubeSignatureDecryptor& YoutubeSignatureDecryptor::get_instance() {
+ if(!instance)
+ instance = new YoutubeSignatureDecryptor();
+ return *instance;
+ }
+
+ bool YoutubeSignatureDecryptor::decrypt(const std::string &s, const std::string &sp, std::string &sig_key, std::string &sig_value) {
+ if(s.empty() || sp.empty())
+ return false;
+
+ if(!up_to_date) {
+ int num_tries = 0;
+ const int max_tries = 30;
+ while(running && num_tries < max_tries && !program_is_dead_in_current_thread()) { // 6 seconds in total
+ std::lock_guard<std::mutex> lock(update_signature_mutex);
+ if(up_to_date)
+ break;
+ ++num_tries;
+ usleep(200 * 1000); // 200 milliseconds
+ }
+
+ if(num_tries == max_tries) {
+ show_notification("QuickMedia", "Failed to get decryption function for youtube. Make sure your internet is up and that you are running the latest version of QuickMedia", Urgency::CRITICAL);
+ return false;
+ }
+ }
+
+ std::lock_guard<std::mutex> lock(update_signature_mutex);
+ std::string sig = s;
+ for(const auto &func_call : func_calls) {
+ auto func_decl_it = func_decls.find(func_call.func_name);
+ assert(func_decl_it != func_decls.end());
+
+ switch(func_decl_it->second) {
+ case DecryptFunction::REVERSE: {
+ std::reverse(sig.begin(), sig.end());
+ break;
+ }
+ case DecryptFunction::SPLICE: {
+ long int erase_index = func_call.arg;
+ if(erase_index > 0 && (size_t)erase_index < sig.size())
+ sig.erase(0, erase_index);
+ break;
+ }
+ case DecryptFunction::SWAP: {
+ if(sig.empty()) {
+ fprintf(stderr, "YoutubeSignatureDecryptor: sig unexpectedly empty in swap\n");
+ } else {
+ char c = sig[0];
+ sig[0] = sig[func_call.arg % sig.size()];
+ sig[func_call.arg % sig.size()] = c;
+ }
+ break;
+ }
+ }
+ }
+
+ sig_key = sp;
+ sig_value = sig;
+ return true;
+ }
+
+ int YoutubeSignatureDecryptor::update_decrypt_function() {
+ std::string response;
+ DownloadResult download_result = download_to_string("https://www.youtube.com/watch?v=CvFH_6DNRCY&gl=US&hl=en", response, {}, true);
+ if(download_result != DownloadResult::OK) {
+ fprintf(stderr, "YoutubeSignatureDecryptor::update_decrypt_function failed. Failed to get youtube page\n");
+ return U_DEC_FUN_NET_ERR;
+ }
+
+ std::smatch base_js_match;
+ if(!std::regex_search(response, base_js_match, std::regex(R"END((\/s\/player\/[^\/]+\/player_ias[^\/]+\/en_US\/base\.js))END", std::regex::ECMAScript)) || base_js_match.size() != 2) {
+ fprintf(stderr, "YoutubeSignatureDecryptor: Regex match 1 invalid. Youtube likely updated and QuickMedia needs to be fixed?\n");
+ return U_DEC_FUN_FAILED_MATCH_ERR;
+ }
+
+ const std::string &url = base_js_match[1].str();
+ download_result = download_to_string("https://www.youtube.com" + url, response, {}, true);
+ if(download_result != DownloadResult::OK) {
+ fprintf(stderr, "YoutubeSignatureDecryptor::update_decrypt_function failed. Failed to get https://www.youtube.com%s\n", url.c_str());
+ return U_DEC_FUN_NET_ERR;
+ }
+
+ std::smatch function_match;
+ if(!std::regex_search(response, function_match, std::regex(R"END((^|\n)\w+=function\(\w\)\{\w=\w\.split\(""\);([^\}]*)\})END", std::regex::ECMAScript)) || function_match.size() != 3) {
+ fprintf(stderr, "YoutubeSignatureDecryptor: Regex match 2 invalid. Youtube likely updated and QuickMedia needs to be fixed?\n");
+ return U_DEC_FUN_FAILED_MATCH_ERR;
+ }
+
+ std::string function_body_str = function_match[2].str();
+ size_t last_semicolon_index = function_body_str.rfind(';');
+ if(last_semicolon_index == std::string::npos) {
+ fprintf(stderr, "YoutubeSignatureDecryptor: Regex match 3 invalid. Youtube likely updated and QuickMedia needs to be fixed?\n");
+ return U_DEC_FUN_FAILED_MATCH_ERR;
+ }
+
+ function_body_str.erase(last_semicolon_index, function_body_str.size());
+ string_replace_all(function_body_str, '\n', ' ');
+
+ size_t var_dot_index = function_body_str.find('.');
+ if(var_dot_index == std::string::npos) {
+ fprintf(stderr, "YoutubeSignatureDecryptor: Regex match 5 invalid. Youtube likely updated and QuickMedia needs to be fixed?\n");
+ return U_DEC_FUN_FAILED_MATCH_ERR;
+ }
+
+ std::string var_name = function_body_str.substr(0, var_dot_index);
+ string_replace_all(response, '\n', ' ');
+ std::smatch var_body_match;
+ if(!std::regex_search(response, var_body_match, std::regex("var " + var_name + "=\\{(.*?)\\};", std::regex::ECMAScript)) || var_body_match.size() != 2) {
+ fprintf(stderr, "YoutubeSignatureDecryptor: Regex match 6 invalid. Youtube likely updated and QuickMedia needs to be fixed?\n");
+ return U_DEC_FUN_FAILED_MATCH_ERR;
+ }
+
+ std::string var_body_str = var_body_match[1].str();
+ string_replace_all(var_body_str, '\n', ' ');
+
+ std::vector<DecryptFuncCall> new_func_calls;
+ std::map<std::string, DecryptFunction> new_func_decls;
+ if(!js_code_to_operations(function_body_str, var_body_str, new_func_calls, new_func_decls)) {
+ fprintf(stderr, "YoutubeSignatureDecryptor: Regex match 7 invalid. Youtube likely updated and QuickMedia needs to be fixed?\n");
+ return U_DEC_FUN_FAILED_MATCH_ERR;
+ }
+
+ {
+ std::lock_guard<std::mutex> lock(update_signature_mutex);
+ decryption_function = function_body_str + "\n" + var_body_str;
+ decrypt_function_last_updated = time(nullptr);
+ up_to_date = true;
+ func_calls = std::move(new_func_calls);
+ func_decls = std::move(new_func_decls);
+ }
+
+ file_overwrite_atomic(get_cache_dir().join("youtube").join("decryption_function"), decryption_function);
+ return 0;
+ }
+} \ No newline at end of file