From 44bc399ccbd7e37107ae754db7da3d918229422d Mon Sep 17 00:00:00 2001 From: dec05eba Date: Mon, 9 Aug 2021 18:22:43 +0200 Subject: Youtube: show search suggestions instead of immediate search Fix save file dialog not showing all files after navigating to another directory if the search is not empty. Fix matrix system message deletion reverting back to use message (for avatar) and text color. --- src/Body.cpp | 18 ++++-- src/QuickMedia.cpp | 64 +++++++++++--------- src/SearchBar.cpp | 86 +------------------------- src/plugins/MediaGeneric.cpp | 8 ++- src/plugins/Youtube.cpp | 139 ++++++++++++++++++++++++++++++------------- 5 files changed, 154 insertions(+), 161 deletions(-) (limited to 'src') diff --git a/src/Body.cpp b/src/Body.cpp index 766542a..c3cbcda 100644 --- a/src/Body.cpp +++ b/src/Body.cpp @@ -261,6 +261,11 @@ namespace QuickMedia { filter_search_fuzzy_item(current_filter, item.get()); } this->items = std::move(items); + if(attach_side == AttachSide::TOP) { + selected_item = 0; + prev_selected_item = selected_item; + page_scroll = 0.0f; + } } void Body::clear_items() { @@ -299,10 +304,6 @@ namespace QuickMedia { items.insert(items.begin() + index, std::move(body_item)); } - void Body::move_items_to(Body *other_body) { - other_body->set_items(std::move(items)); - } - void Body::move_item(size_t src_index, size_t dst_index) { assert(src_index < items.size()); assert(dst_index < items.size()); @@ -1661,11 +1662,20 @@ namespace QuickMedia { } void Body::filter_search_fuzzy_item(const std::string &text, BodyItem *body_item) { + const bool prev_visible = body_item->visible; + body_item->visible = string_find_fuzzy_case_insensitive(body_item->get_title(), text); if(!body_item->visible && !body_item->get_description().empty()) body_item->visible = string_find_fuzzy_case_insensitive(body_item->get_description(), text); if(!body_item->visible && !body_item->get_author().empty()) body_item->visible = string_find_fuzzy_case_insensitive(body_item->get_author(), text); + + if(prev_visible && !body_item->visible) { + clear_body_item_cache(body_item); + // TODO: Make sure the embedded item is not referencing another item in the |items| list + if(body_item->embedded_item) + clear_body_item_cache(body_item->embedded_item.get()); + } } bool Body::no_items_visible() const { diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 52452d4..dc23892 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -1222,7 +1222,7 @@ namespace QuickMedia { if(youtube_url.empty()) { start_tab_index = 1; tabs.push_back(Tab{create_body(false, true), std::make_unique(this), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); - tabs.push_back(Tab{create_body(false, true), std::make_unique(this), create_search_bar("Search...", 350)}); + tabs.push_back(Tab{create_body(false, false), std::make_unique(this), create_search_bar("Search...", 100)}); auto recommended_page = std::make_unique(this); tabs.push_back(Tab{create_body(false, true), std::move(recommended_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); @@ -1708,9 +1708,6 @@ namespace QuickMedia { tab_associated_data.push_back(std::move(data)); } - //std::string autocomplete_text; - //bool autocomplete_running = false; - double gradient_inc = 0.0; const float gradient_height = 5.0f; @@ -1733,12 +1730,19 @@ namespace QuickMedia { hide_virtual_keyboard(); std::vector new_tabs; + BodyItems new_body_items; + const bool search_suggestion_submitted = tab_associated_data[selected_tab].search_suggestion_submitted; auto prev_selected_item = tabs[selected_tab].page->submit_body_item; tabs[selected_tab].page->submit_body_item = selected_item; - auto plugin_submit_handler = [&tabs, selected_tab, &selected_item, &search_text, &new_tabs]() { - PluginResult plugin_result = tabs[selected_tab].page->submit(selected_item ? selected_item->get_title() : search_text, selected_item ? selected_item->url : search_text, new_tabs); - return plugin_result == PluginResult::OK; + auto plugin_submit_handler = [&tabs, selected_tab, &selected_item, &search_text, &new_tabs, &new_body_items, search_suggestion_submitted]() { + if(tabs[selected_tab].page->search_is_suggestion() && !search_suggestion_submitted) { + PluginResult plugin_result = tabs[selected_tab].page->submit_suggestion(selected_item ? selected_item->get_title() : search_text, selected_item ? selected_item->url : search_text, new_body_items); + return plugin_result == PluginResult::OK; + } else { + PluginResult plugin_result = tabs[selected_tab].page->submit(selected_item ? selected_item->get_title() : search_text, selected_item ? selected_item->url : search_text, new_tabs); + return plugin_result == PluginResult::OK; + } }; TaskResult submit_result; @@ -1769,11 +1773,11 @@ namespace QuickMedia { } } - if(tabs[selected_tab].page->is_single_page()) { + if(tabs[selected_tab].page->is_single_page() && !tabs[selected_tab].page->search_is_suggestion()) { if(tabs[selected_tab].search_bar) tabs[selected_tab].search_bar->clear(); if(new_tabs.size() == 1 && !new_tabs[0].page) { tabs[selected_tab].body = std::move(new_tabs[0].body); - tabs[selected_tab].page->submit_body_item = prev_selected_item; + tabs[selected_tab].page->submit_body_item = nullptr; return; } else if(new_tabs.empty()) { loop_running = false; @@ -1782,6 +1786,15 @@ namespace QuickMedia { } } + if(tabs[selected_tab].page->search_is_suggestion() && !search_suggestion_submitted) { + tabs[selected_tab].body->set_items(std::move(new_body_items)); + tabs[selected_tab].page->submit_body_item = nullptr; + tab_associated_data[selected_tab].search_suggestion_submitted = true; + if(tabs[selected_tab].search_bar) + tabs[selected_tab].search_bar->clear(); + return; + } + if(new_tabs.empty()) { tabs[selected_tab].page->submit_body_item = prev_selected_item; return; @@ -1909,6 +1922,7 @@ namespace QuickMedia { && !tab_associated_data[selected_tab].fetching_next_page_failed && (!tabs[selected_tab].search_bar || !tabs[selected_tab].page->search_is_filter() || tabs[selected_tab].search_bar->is_empty()) && tabs[selected_tab].body->get_num_visible_items() > 0 + && (!tabs[selected_tab].page->search_is_suggestion() || tab_associated_data[selected_tab].search_suggestion_submitted) && tabs[selected_tab].page->is_ready() && (!tabs[selected_tab].page->is_lazy_fetch_page() || tab_associated_data[selected_tab].lazy_fetch_finished)) { @@ -1940,12 +1954,6 @@ namespace QuickMedia { TabAssociatedData &associated_data = tab_associated_data[i]; if(tab.search_bar) { - // tab.search_bar->autocomplete_search_delay = current_plugin->get_autocomplete_delay(); - // tab.search_bar->onAutocompleteRequestCallback = [this, &tabs, &selected_tab, &autocomplete_text](const std::string &text) { - // if(tabs[selected_tab].body == body && !current_plugin->search_is_filter()) - // autocomplete_text = text; - // }; - tab.search_bar->onTextUpdateCallback = [&associated_data, &tabs, i](const std::string &text) { if(!tabs[i].page->search_is_filter()) { associated_data.update_search_text = text; @@ -2000,8 +2008,6 @@ namespace QuickMedia { else if(event.type == sf::Event::KeyPressed) { if(event.key.code == sf::Keyboard::Escape) { return false; - } else if(event.key.code == sf::Keyboard::Tab) { - if(tabs[selected_tab].search_bar) tabs[selected_tab].search_bar->set_to_autocomplete(); } else if(event.key.code == sf::Keyboard::Enter) { if(!tabs[selected_tab].search_bar) { BodyItem *selected_item = tabs[selected_tab].body->get_selected(); @@ -2162,11 +2168,13 @@ namespace QuickMedia { if(associated_data.search_text_updated && associated_data.fetch_status == FetchStatus::NONE && !associated_data.fetching_next_page_running) { std::string update_search_text = associated_data.update_search_text; + if(!tabs[i].page->search_is_suggestion() || associated_data.search_suggestion_submitted) + tabs[i].body->clear_items(); associated_data.search_text_updated = false; - tabs[i].body->clear_items(); associated_data.fetch_status = FetchStatus::LOADING; associated_data.fetch_type = FetchType::SEARCH; associated_data.search_result_text.setString("Searching..."); + associated_data.search_suggestion_submitted = false; Page *page = tabs[i].page.get(); associated_data.fetch_future = AsyncTask([update_search_text, page]() { FetchResult fetch_result; @@ -2937,18 +2945,18 @@ namespace QuickMedia { window.setMouseCursorVisible(true); cursor_visible = true; - int search_delay = 0; - auto search_page = video_page->create_search_page(this, search_delay); + Tab search_page_tab; + const bool search_page_created = video_page->create_search_page(this, search_page_tab); auto comments_page = video_page->create_comments_page(this); 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) { + if(search_page_created || related_videos_page || channels_page) { XUnmapWindow(disp, video_player_window); XSync(disp, False); std::vector tabs; - if(search_page) { - tabs.push_back(Tab{create_body(false, true), std::move(search_page), create_search_bar("Search...", search_delay)}); + if(search_page_created) { + tabs.push_back(std::move(search_page_tab)); } if(comments_page) { tabs.push_back(Tab{create_body(), std::move(comments_page), nullptr}); @@ -4755,12 +4763,15 @@ namespace QuickMedia { //message->related_event_id.clear(); //message->related_event_type = RelatedEventType::NONE; Message *original_message = static_cast(body_item->userdata); - if(original_message) { + if(original_message && !is_system_message_type(original_message->type)) { body_item->thumbnail_url = current_room->get_user_avatar_url(original_message->user); body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE; } body_item->set_description("Message deleted"); - body_item->set_description_color(get_current_theme().text_color); + if(original_message && is_system_message_type(original_message->type)) + body_item->set_description_color(get_current_theme().faded_text_color); + else + body_item->set_description_color(get_current_theme().text_color); body_item->thumbnail_size = AVATAR_THUMBNAIL_SIZE; body_item->url.clear(); }; @@ -6995,8 +7006,7 @@ namespace QuickMedia { if(task_result == TaskResult::TRUE) { if(!new_tabs.empty()) { - new_tabs[0].body->move_items_to(file_manager_body.get()); - file_manager_body->select_first_item(); + file_manager_body = std::move(new_tabs[0].body); search_bar->clear(); } } else if(task_result == TaskResult::FALSE) { diff --git a/src/SearchBar.cpp b/src/SearchBar.cpp index 5909635..c1f9ceb 100644 --- a/src/SearchBar.cpp +++ b/src/SearchBar.cpp @@ -21,17 +21,13 @@ namespace QuickMedia { onTextUpdateCallback(nullptr), onTextSubmitCallback(nullptr), onTextBeginTypingCallback(nullptr), - onAutocompleteRequestCallback(nullptr), text_autosearch_delay(50), - autocomplete_search_delay(250), caret_visible(true), text(placeholder, *FontLoader::get_font(FontLoader::FontType::LATIN), std::floor(16 * get_ui_scale())), - autocomplete_text("", *FontLoader::get_font(FontLoader::FontType::LATIN), std::floor(16 * get_ui_scale())), background(sf::Vector2f(1.0f, 1.0f), 10.0f, get_current_theme().selected_color, rounded_rectangle_shader), placeholder_str(placeholder), show_placeholder(true), updated_search(false), - updated_autocomplete(false), draw_logo(false), needs_update(true), input_masked(input_masked), @@ -44,7 +40,6 @@ namespace QuickMedia { padding_top = padding_top_default; padding_bottom = padding_bottom_default; text.setFillColor(get_current_theme().placeholder_text_color); - autocomplete_text.setFillColor(get_current_theme().placeholder_text_color); shade.setFillColor(get_current_theme().shade_color); if(plugin_logo && plugin_logo->getNativeHandle() != 0) plugin_logo_sprite.setTexture(*plugin_logo, true); @@ -65,8 +60,7 @@ namespace QuickMedia { window.draw(shade); background.draw(window); - // TODO: Render starting from the character after text length - window.draw(autocomplete_text); + if(input_masked && !show_placeholder) { std::string masked_str(text.getString().getSize(), '*'); sf::Text masked_text(std::move(masked_str), *text.getFont(), text.getCharacterSize()); @@ -117,7 +111,6 @@ namespace QuickMedia { if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::D && event.key.control) { clear(); updated_search = true; - updated_autocomplete = true; time_since_search_update.restart(); } @@ -144,13 +137,6 @@ namespace QuickMedia { if(onTextUpdateCallback) onTextUpdateCallback(*u8_str); typing = false; - } else if(updated_autocomplete && elapsed_time >= autocomplete_search_delay) { - updated_autocomplete = false; - if(!show_placeholder && onAutocompleteRequestCallback) { - auto u8 = text.getString().toUtf8(); - std::string *u8_str = (std::string*)&u8; - onAutocompleteRequestCallback(*u8_str); - } } } @@ -184,7 +170,6 @@ namespace QuickMedia { background.set_position(sf::Vector2f(pos.x + offset_x, pos.y + padding_top)); shade.setPosition(pos); sf::Vector2f font_position(std::floor(pos.x + offset_x + background_margin_horizontal), std::floor(pos.y + padding_top + background_margin_vertical)); - autocomplete_text.setPosition(font_position); text.setPosition(font_position); } @@ -201,9 +186,6 @@ namespace QuickMedia { show_placeholder = true; text.setString(placeholder_str); text.setFillColor(get_current_theme().placeholder_text_color); - autocomplete_text.setString(""); - } else { - clear_autocomplete_if_text_not_substring(); } if(!updated_search) { typing = true; @@ -211,7 +193,6 @@ namespace QuickMedia { onTextBeginTypingCallback(); } updated_search = true; - updated_autocomplete = true; time_since_search_update.restart(); } } else if(codepoint == 13) { // Return @@ -235,10 +216,8 @@ namespace QuickMedia { show_placeholder = true; text.setString(placeholder_str); text.setFillColor(get_current_theme().placeholder_text_color); - autocomplete_text.setString(""); needs_update = true; updated_search = false; - updated_autocomplete = false; backspace_pressed = false; } @@ -256,51 +235,18 @@ namespace QuickMedia { str += text_to_add; text.setString(str); - clear_autocomplete_if_text_not_substring(); if(!updated_search) { typing = true; if(onTextBeginTypingCallback) onTextBeginTypingCallback(); } updated_search = true; - updated_autocomplete = true; time_since_search_update.restart(); backspace_pressed = false; if(text_to_add.find('\n') != std::string::npos) needs_update = true; } - bool SearchBar::is_cursor_at_start_of_line() const { - // TODO: When it's possible to move the cursor, then check at the cursor position instead of end of the string - const sf::String &str = text.getString(); - return show_placeholder || str.getSize() == 0 || str[str.getSize() - 1] == '\n'; - } - - void SearchBar::set_to_autocomplete() { - const sf::String &autocomplete_str = autocomplete_text.getString(); - if(!autocomplete_str.isEmpty()) { - if(show_placeholder) { - show_placeholder = false; - text.setString(""); - text.setFillColor(sf::Color::White); - } - text.setString(autocomplete_str); - if(!updated_search) { - typing = true; - if(onTextBeginTypingCallback) - onTextBeginTypingCallback(); - } - updated_search = true; - updated_autocomplete = true; - time_since_search_update.restart(); - needs_update = true; - } - } - - void SearchBar::set_autocomplete_text(const std::string &text) { - autocomplete_text.setString(text); - } - void SearchBar::set_position(sf::Vector2f pos) { if(std::abs(this->pos.x - pos.x) > 1.0f || std::abs(this->pos.y - pos.y) > 1.0f) { this->pos = pos; @@ -316,36 +262,6 @@ namespace QuickMedia { return editable; } - void SearchBar::clear_autocomplete_if_text_not_substring() { - const sf::String &text_str = text.getString(); - const sf::String &autocomplete_str = autocomplete_text.getString(); - if(text_str.getSize() > autocomplete_str.getSize()) { - autocomplete_text.setString(""); - return; - } - - for(size_t i = 0; i < autocomplete_str.getSize(); ++i) { - if(text_str[i] != autocomplete_str[i]) { - autocomplete_text.setString(""); - return; - } - } - } - - void SearchBar::clear_autocomplete_if_last_char_not_substr() { - const sf::String &text_str = text.getString(); - const sf::String &autocomplete_str = autocomplete_text.getString(); - if(text_str.isEmpty() || text_str.getSize() > autocomplete_str.getSize()) { - autocomplete_text.setString(""); - return; - } - - if(autocomplete_str[text_str.getSize() - 1] != text_str[text_str.getSize() - 1]) { - autocomplete_text.setString(""); - return; - } - } - float SearchBar::getBottom() const { return getBottomWithoutShadow() + 5.0f;//background_shadow.getSize().y; } diff --git a/src/plugins/MediaGeneric.cpp b/src/plugins/MediaGeneric.cpp index c829a33..d536a09 100644 --- a/src/plugins/MediaGeneric.cpp +++ b/src/plugins/MediaGeneric.cpp @@ -191,9 +191,11 @@ namespace QuickMedia { return result_items; } - std::unique_ptr MediaGenericVideoPage::create_search_page(Program*, int &search_delay) { - search_delay = 500; // TODO: Make configurable? - return std::make_unique(*search_page); + bool MediaGenericVideoPage::create_search_page(Program*, Tab &tab) { + tab.body = create_body(false, true); + tab.page = std::make_unique(*search_page); + tab.search_bar = create_search_bar("Search...", 500); // TODO: Make search delay configurable? + return true; } std::unique_ptr MediaGenericVideoPage::create_related_videos_page(Program *program) { diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp index ea72f03..5b0591c 100644 --- a/src/plugins/Youtube.cpp +++ b/src/plugins/Youtube.cpp @@ -764,12 +764,98 @@ namespace QuickMedia { } SearchResult YoutubeSearchPage::search(const std::string &str, BodyItems &result_items) { + if(str.empty()) + return SearchResult::OK; + + // TODO: Find this search url from youtube.com/... searchbox.js, and the url to that script from youtube.com/ html + std::string url = "https://suggestqueries-clients6.youtube.com/complete/search?client=youtube&hl=en&gl=us&sugexp=rdcfrc%2Ccfro%3D1%2Cfp.cfr%3D1&gs_rn=64&gs_ri=youtube&ds=yt&cp=4&gs_id=f&xhr=t&xssi=t&q="; + url += url_param_encode(str); + if(!video_id.empty()) + url += "&video_id=" + video_id; + + std::vector additional_args = { + { "-H", "origin: https://www.youtube.com" }, + { "-H", "referer: https://www.youtube.com/" } + }; + + std::vector cookies = get_cookies(); + additional_args.insert(additional_args.end(), cookies.begin(), cookies.end()); + + std::string website_data; + DownloadResult result = download_to_string(url, website_data, std::move(additional_args), true); + if(result != DownloadResult::OK) return download_result_to_search_result(result); + + const size_t json_start_index = website_data.find('['); + if(json_start_index == std::string::npos) + return SearchResult::ERR; + + Json::Value json_root; + Json::CharReaderBuilder json_builder; + std::unique_ptr json_reader(json_builder.newCharReader()); + std::string json_errors; + if(!json_reader->parse(&website_data[json_start_index], &website_data[website_data.size()], &json_root, &json_errors)) { + fprintf(stderr, "youtube search error: %s\n", json_errors.c_str()); + return SearchResult::ERR; + } + + if(!json_root.isArray() || json_root.size() < 2) + return SearchResult::ERR; + + const Json::Value &search_result_list_json = json_root[1]; + if(!search_result_list_json.isArray()) + return SearchResult::ERR; + + for(const Json::Value &json_item : search_result_list_json) { + if(!json_item.isArray() || json_item.size() == 0) + continue; + + const Json::Value &search_result_json = json_item[0]; + if(!search_result_json.isString()) + continue; + + auto body_item = BodyItem::create(search_result_json.asString()); + body_item->url = body_item->get_title(); + result_items.push_back(std::move(body_item)); + } + + if(result_items.empty() || !strcase_equals(str.c_str(), result_items.front()->get_title().c_str())) { + auto body_item = BodyItem::create(str); + body_item->url = str; + result_items.insert(result_items.begin(), std::move(body_item)); + } + + return SearchResult::OK; + } + + PluginResult YoutubeSearchPage::get_page(const std::string&, int page, BodyItems &result_items) { + while(current_page < page) { + PluginResult plugin_result = search_get_continuation(search_url, continuation_token, result_items); + if(plugin_result != PluginResult::OK) return plugin_result; + ++current_page; + } + return PluginResult::OK; + } + + PluginResult YoutubeSearchPage::submit(const std::string &title, const std::string &url, std::vector &result_tabs) { + if(url.empty()) + return PluginResult::OK; + + if(strncmp(url.c_str(), "https://www.youtube.com/channel/", 32) == 0) { + // TODO: Make all pages (for all services) lazy fetch in a similar manner! + result_tabs.push_back(Tab{create_body(false, true), std::make_unique(program, url, "", title), create_search_bar("Search...", 350)}); + } else { + result_tabs.push_back(Tab{nullptr, std::make_unique(program, url), nullptr}); + } + return PluginResult::OK; + } + + PluginResult YoutubeSearchPage::submit_suggestion(const std::string&, const std::string &url, BodyItems &result_items) { continuation_token.clear(); current_page = 0; added_videos.clear(); search_url = "https://www.youtube.com/results?search_query="; - search_url += url_param_encode(str); + search_url += url_param_encode(url); std::vector additional_args = { { "-H", "x-spf-referer: " + search_url }, @@ -783,10 +869,10 @@ namespace QuickMedia { Json::Value json_root; DownloadResult result = download_json(json_root, search_url + "&pbj=1&gl=US&hl=en", std::move(additional_args), true); - if(result != DownloadResult::OK) return download_result_to_search_result(result); + if(result != DownloadResult::OK) return download_result_to_plugin_result(result); if(!json_root.isArray()) - return SearchResult::ERR; + return PluginResult::ERR; for(const Json::Value &json_item : json_root) { if(!json_item.isObject()) @@ -811,28 +897,6 @@ namespace QuickMedia { parse_section_list_renderer(primary_contents_json["sectionListRenderer"], continuation_token, result_items, added_videos); } - return SearchResult::OK; - } - - PluginResult YoutubeSearchPage::get_page(const std::string&, int page, BodyItems &result_items) { - while(current_page < page) { - PluginResult plugin_result = search_get_continuation(search_url, continuation_token, result_items); - if(plugin_result != PluginResult::OK) return plugin_result; - ++current_page; - } - return PluginResult::OK; - } - - PluginResult YoutubeSearchPage::submit(const std::string &title, const std::string &url, std::vector &result_tabs) { - if(url.empty()) - return PluginResult::OK; - - if(strncmp(url.c_str(), "https://www.youtube.com/channel/", 32) == 0) { - // TODO: Make all pages (for all services) lazy fetch in a similar manner! - result_tabs.push_back(Tab{create_body(false, true), std::make_unique(program, url, "", title), create_search_bar("Search...", 350)}); - } else { - result_tabs.push_back(Tab{nullptr, std::make_unique(program, url), nullptr}); - } return PluginResult::OK; } @@ -2061,9 +2125,14 @@ namespace QuickMedia { return result_items; } - std::unique_ptr YoutubeVideoPage::create_search_page(Program *program, int &search_delay) { - search_delay = 350; - return std::make_unique(program); + bool YoutubeVideoPage::create_search_page(Program *program, Tab &tab) { + std::string video_id; + youtube_url_extract_id(url, video_id); + + tab.body = create_body(false, false); + tab.page = std::make_unique(program, std::move(video_id)); + tab.search_bar = create_search_bar("Search...", 100); + return true; } std::unique_ptr YoutubeVideoPage::create_comments_page(Program *program) { @@ -2095,20 +2164,6 @@ namespace QuickMedia { return result; } - static std::string url_extract_param(const std::string &url, const std::string ¶m) { - 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); - } - static const YoutubeVideoFormat* get_highest_resolution_mp4_non_av1(const std::vector &video_formats, int max_height) { for(const YoutubeVideoFormat &video_format : video_formats) { if(video_format.height <= max_height && video_format.base.mime_type.find("mp4") != std::string::npos && video_format.base.mime_type.find("av01") == std::string::npos) -- cgit v1.2.3