diff options
-rw-r--r-- | README.md | 9 | ||||
-rw-r--r-- | example-config.json | 6 | ||||
-rw-r--r-- | include/Config.hpp | 8 | ||||
-rw-r--r-- | include/DownloadUtils.hpp | 2 | ||||
-rw-r--r-- | include/FileAnalyzer.hpp | 1 | ||||
-rw-r--r-- | include/QuickMedia.hpp | 6 | ||||
-rw-r--r-- | src/Config.cpp | 39 | ||||
-rw-r--r-- | src/DownloadUtils.cpp | 4 | ||||
-rw-r--r-- | src/FileAnalyzer.cpp | 20 | ||||
-rw-r--r-- | src/QuickMedia.cpp | 68 |
10 files changed, 133 insertions, 30 deletions
@@ -74,7 +74,8 @@ Type text and then wait and QuickMedia will automatically search.\ `Esc`/`Backspace`/`Q`: Close the video.\ `Ctrl+F`/`Double left mouse click`: Toggle between fullscreen mode and window mode.\ `Ctrl+R`: Show pages related to the video, such as comments, related videos or channel videos (if supported).\ -`Ctrl+S`: Save the video/music.\ +`Ctrl+S`: Save the video/music (with dialog).\ +`Ctrl+Shift+S`: Save the video/music (without dialog).\ `Ctrl+C`: Copy the url of the currently playing video to the clipboard (with timestamp, if supported).\ `F5`: Reload the video/music. ### Youtube channel controls @@ -115,7 +116,8 @@ Type text and then wait and QuickMedia will automatically search.\ `Ctrl+D`: Delete the selected message (if it was posted by you).\ `Enter`: View image/video attached to the message, or if its a file then download it. Brings up a list of urls in the selected item if there are any.\ `Ctrl+I`: Reverse image search the selected image.\ -`Ctrl+S`: Save the selected file.\ +`Ctrl+S`: Save the selected file (with dialog).\ +`Ctrl+Shift+S`: Save the selected file (without dialog).\ `Ctrl+V`: Uploads the file specified in the clipboard.\ `U`: Bring up the file manager and select a file to upload to the room, `Esc` to cancel.\ `Ctrl+U`: Bring up the file manager and select a file to upload to the message you are replying to, `Esc` to cancel.\ @@ -156,7 +158,8 @@ Type text and then wait and QuickMedia will automatically search.\ `Ctrl+D`: Remove the selected file from the post.\ `Ctrl+I`: Reverse image search the selected image or select an url to open in the browser.\ `Arrow left/right`: Move captcha slider left/right.\ -`Ctrl+S`: Save the image/video attached to the selected post. +`Ctrl+S`: Save the image/video attached to the selected post (with dialog).\ +`Ctrl+S`: Save the image/video attached to the selected post (without dialog). ### 4chan image controls `Mouse scroll`: Zoom.\ `Left mouse button + move`: Pan (move) the image.\ diff --git a/example-config.json b/example-config.json index 56e7279..f217793 100644 --- a/example-config.json +++ b/example-config.json @@ -73,6 +73,12 @@ "https://neogenesis.tv" ] }, + "download": { + "video_directory": "~/Videos", + "image_directory": "~/Pictures", + "music_directory": "~/Music", + "file_directory": "~/Downloads" + }, "use_system_fonts": false, "use_system_mpv_config": false, "enable_shaders": true, diff --git a/include/Config.hpp b/include/Config.hpp index 4d9fd91..d7bfddf 100644 --- a/include/Config.hpp +++ b/include/Config.hpp @@ -55,6 +55,13 @@ namespace QuickMedia { std::vector<std::string> known_instances; }; + struct Download { + std::string video_directory; + std::string image_directory; + std::string music_directory; + std::string file_directory; + }; + struct Config { Config() = default; Config(const Config&) = delete; @@ -70,6 +77,7 @@ namespace QuickMedia { YoutubeConfig youtube; MatrixConfig matrix; Peertube peertube; + Download download; bool use_system_fonts = false; bool use_system_mpv_config = false; bool enable_shaders = true; diff --git a/include/DownloadUtils.hpp b/include/DownloadUtils.hpp index 48ee309..d8aba2e 100644 --- a/include/DownloadUtils.hpp +++ b/include/DownloadUtils.hpp @@ -32,6 +32,6 @@ namespace QuickMedia { // Note: if |cloudflare_bypass| is set to true then tls is limited to version 1.1 and the user agent is changed. DownloadResult download_to_file(const std::string &url, const std::string &destination_filepath, const std::vector<CommandArg> &additional_args, bool use_browser_useragent = false, bool cloudflare_bypass = false); // Returns false if there was an error trying to create the download process - bool download_async_gui(const std::string &url, const std::string &file_manager_start_dir, bool no_video, const std::string &filename); + bool download_async_gui(const std::string &url, const std::string &file_manager_start_dir, bool no_video, const std::string &filename, bool download_no_dialog); DownloadResult download_to_json(const std::string &url, rapidjson::Document &result, const std::vector<CommandArg> &additional_args, bool use_browser_useragent = false, bool fail_on_error = true); }
\ No newline at end of file diff --git a/include/FileAnalyzer.hpp b/include/FileAnalyzer.hpp index 9f3dbb9..4ae95ff 100644 --- a/include/FileAnalyzer.hpp +++ b/include/FileAnalyzer.hpp @@ -41,6 +41,7 @@ namespace QuickMedia { const char* content_type_to_string(ContentType content_type); bool is_image_ext(const char *ext); bool is_video_ext(const char *ext); + bool is_music_ext(const char *ext); // Set |width| or |height| to 0 to disable scaling. // TODO: Make this async diff --git a/include/QuickMedia.hpp b/include/QuickMedia.hpp index d2ca1f3..675fa0e 100644 --- a/include/QuickMedia.hpp +++ b/include/QuickMedia.hpp @@ -118,7 +118,7 @@ namespace QuickMedia { void set_clipboard(const std::string &str); private: - void init(mgl::WindowHandle parent_window, std::string &program_path); + void init(mgl::WindowHandle parent_window, std::string &program_path, bool no_dialog); void load_plugin_by_name(std::vector<Tab> &tabs, int &start_tab_index, FileManagerMimeType fm_mime_type, FileSelectionHandler file_selection_handler, std::string instance); void common_event_handler(mgl::Event &event); void handle_x11_events(); @@ -134,7 +134,7 @@ namespace QuickMedia { bool page_loop(std::vector<Tab> &tabs, int start_tab_index = 0, PageLoopSubmitHandler after_submit_handler = nullptr, bool go_to_previous_on_escape = true); void redirect_focus_to_video_player_window(mgl::WindowHandle video_player_window); void show_video_player_window(mgl::WindowHandle video_player_window); - void video_page_download_video(const std::string &url, const std::string &filename, mgl::WindowHandle video_player_window = 0); + void video_page_download_video(const std::string &url, const std::string &filename, mgl::WindowHandle video_player_window = 0, bool download_no_dialog = false); bool video_download_if_non_streamable(std::string &video_url, std::string &audio_url, bool &is_audio_only, bool &has_embedded_audio, PageType previous_page); int video_get_max_height(); void video_content_page(Page *parent_page, VideoPage *video_page, std::string video_title, bool download_if_streaming_fails, Body *parent_body, int play_index, int *parent_body_page = nullptr, const std::string &parent_page_search = ""); @@ -147,7 +147,7 @@ namespace QuickMedia { void chat_login_page(); bool chat_page(MatrixChatPage *matrix_chat_page, RoomData *current_room); void after_matrix_login_page(); - void download_page(std::string url, std::string download_filename); + void download_page(std::string url, std::string download_filename, bool no_dialog); // Returns the full path where the file should be saved, or an empty string if the operation was cancelled std::string file_save_page(const std::string &filename); diff --git a/src/Config.cpp b/src/Config.cpp index 540286f..3f2d68c 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -256,6 +256,45 @@ namespace QuickMedia { if(!has_known_peertube_homeservers_config) peertube_known_instances_fallback(); + const std::string home_dir = get_home_dir().data; + + struct DownloadPaths { + const char *json_field; + std::string fallback_dir; + const char *xdg_var_name; + std::string *config_var; + }; + + const int num_download_paths = 4; + DownloadPaths download_paths_list[num_download_paths] = { + { "video_directory", home_dir + "/Videos", "XDG_VIDEOS_DIR", &config->download.video_directory }, + { "image_directory", home_dir + "/Pictures", "XDG_PICTURES_DIR", &config->download.image_directory }, + { "music_directory", home_dir + "/Music", "XDG_MUSIC_DIR", &config->download.music_directory }, + { "file_directory", home_dir + "/Downloads", "XDG_DOWNLOAD_DIR", &config->download.file_directory }, + }; + + const Json::Value &download_json = json_root["download"]; + if(download_json.isObject()) { + for(const DownloadPaths &download_paths : download_paths_list) { + const Json::Value &directory_json = download_json[download_paths.json_field]; + if(!directory_json.isString()) { + fprintf(stderr, "Warning: config variable config.download.%s is not a string, using path \"%s\" instead\n", download_paths.json_field, download_paths.fallback_dir.c_str()); + continue; + } + *download_paths.config_var = path_expanduser(directory_json.asString()); + } + } + + for(const DownloadPaths &download_paths : download_paths_list) { + if(download_paths.config_var->empty()) { + std::string dir = download_paths.fallback_dir; + const char *xdg_var = getenv(download_paths.xdg_var_name); + if(xdg_var) + dir = xdg_var; + *download_paths.config_var = std::move(dir); + } + } + get_json_value(json_root, "use_system_fonts", config->use_system_fonts); get_json_value(json_root, "use_system_mpv_config", config->use_system_mpv_config); get_json_value(json_root, "enable_shaders", config->enable_shaders); diff --git a/src/DownloadUtils.cpp b/src/DownloadUtils.cpp index 484afeb..3fb8b19 100644 --- a/src/DownloadUtils.cpp +++ b/src/DownloadUtils.cpp @@ -318,7 +318,7 @@ namespace QuickMedia { return DownloadResult::OK; } - bool download_async_gui(const std::string &url, const std::string &file_manager_start_dir, bool no_video, const std::string &filename) { + bool download_async_gui(const std::string &url, const std::string &file_manager_start_dir, bool no_video, const std::string &filename, bool download_no_dialog) { char quickmedia_path[PATH_MAX]; ssize_t bytes_written = readlink("/proc/self/exe", quickmedia_path, sizeof(quickmedia_path) - 1); if(bytes_written == -1) @@ -329,6 +329,8 @@ namespace QuickMedia { std::vector<const char*> args = { quickmedia_path, "download", "-u", url.c_str(), "--dir", file_manager_start_dir.c_str() }; if(no_video) args.push_back("--no-video"); + if(download_no_dialog) + args.push_back("--no-dialog"); if(!filename.empty()) args.insert(args.end(), { "--download-filename", filename.c_str() }); args.push_back(nullptr); diff --git a/src/FileAnalyzer.cpp b/src/FileAnalyzer.cpp index 0f312cf..b02d0c2 100644 --- a/src/FileAnalyzer.cpp +++ b/src/FileAnalyzer.cpp @@ -108,7 +108,7 @@ namespace QuickMedia { || strcase_equals(ext, ".flv") || strcase_equals(ext, ".vob") || strcase_equals(ext, ".ogv") - || strcase_equals(ext, ".ogg") + //|| strcase_equals(ext, ".ogg") || strcase_equals(ext, ".avi") //|| strcase_equals(ext, ".ts") || strcase_equals(ext, ".mov") @@ -121,6 +121,24 @@ namespace QuickMedia { || strcase_equals(ext, ".3gp"); } + bool is_music_ext(const char *ext) { + return strcase_equals(ext, ".aac") + || strcase_equals(ext, ".alac") + || strcase_equals(ext, ".flac") + || strcase_equals(ext, ".m4a") + || strcase_equals(ext, ".m4p") + || strcase_equals(ext, ".mp3") + || strcase_equals(ext, ".ogg") + || strcase_equals(ext, ".oga") + || strcase_equals(ext, ".mogg") + || strcase_equals(ext, ".opus") + || strcase_equals(ext, ".vox") + || strcase_equals(ext, ".wav") + || strcase_equals(ext, ".wma") + || strcase_equals(ext, ".mid"); + + } + static int accumulate_string(char *data, int size, void *userdata) { std::string *str = (std::string*)userdata; str->append(data, size); diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 44b1315..fbf2006 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -384,6 +384,7 @@ namespace QuickMedia { std::string program_path = dirname(argv[0]); std::string instance; std::string download_filename; + bool no_dialog = false; for(int i = 1; i < argc; ++i) { if(!plugin_name) { @@ -481,6 +482,8 @@ namespace QuickMedia { usage(); return -1; } + } else if(strcmp(argv[i], "--no-dialog") == 0) { + no_dialog = true; } else if(strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { usage(); return 0; @@ -560,7 +563,7 @@ namespace QuickMedia { }; no_video = force_no_video; - init(parent_window, program_path); + init(parent_window, program_path, no_dialog); if(strcmp(plugin_name, "download") == 0) { if(!url) { @@ -568,7 +571,7 @@ namespace QuickMedia { usage(); return -1; } - download_page(url, download_filename); + download_page(url, download_filename, no_dialog); return exit_code; } @@ -641,7 +644,7 @@ namespace QuickMedia { return focused_monitor_center; } - void Program::init(mgl::WindowHandle parent_window, std::string &program_path) { + void Program::init(mgl::WindowHandle parent_window, std::string &program_path, bool no_dialog) { disp = XOpenDisplay(NULL); if (!disp) { show_notification("QuickMedia", "Failed to open display to X11 server", Urgency::CRITICAL); @@ -669,6 +672,7 @@ namespace QuickMedia { window_create_params.min_size = window_size; window_create_params.max_size = window_size; } + window_create_params.hidden = no_dialog; window_create_params.parent_window = parent_window; if(!window.create("QuickMedia", std::move(window_create_params))) { show_notification("QuickMedia", "Failed to create opengl window", Urgency::CRITICAL); @@ -3058,7 +3062,7 @@ namespace QuickMedia { redirect_focus_to_video_player_window(video_player_window); } - void Program::video_page_download_video(const std::string &url, const std::string &filename, mgl::WindowHandle video_player_window) { + void Program::video_page_download_video(const std::string &url, const std::string &filename, mgl::WindowHandle video_player_window, bool download_no_dialog) { bool separate_audio_option = url_should_download_with_youtube_dl(url); std::string video_id; separate_audio_option |= youtube_url_extract_id(url, video_id); @@ -3067,7 +3071,7 @@ namespace QuickMedia { separate_audio_option = false; if(!separate_audio_option) { - download_async_gui(url, file_manager_start_dir.string(), no_video, filename); + download_async_gui(url, file_manager_start_dir.string(), no_video, filename, download_no_dialog); return; } @@ -3097,7 +3101,7 @@ namespace QuickMedia { if(!selected) return; - download_async_gui(url, file_manager_start_dir.string(), audio_only, filename); + download_async_gui(url, file_manager_start_dir.string(), audio_only, filename, download_no_dialog); } bool Program::video_download_if_non_streamable(std::string &video_url, std::string &audio_url, bool &is_audio_only, bool &has_embedded_audio, PageType previous_page) { @@ -3595,7 +3599,8 @@ namespace QuickMedia { #pragma GCC diagnostic ignored "-Wdeprecated-declarations" KeySym pressed_keysym = XKeycodeToKeysym(disp, xev.xkey.keycode, 0); #pragma GCC diagnostic pop - bool pressing_ctrl = (CLEANMASK(xev.xkey.state) == ControlMask); + const bool pressing_ctrl = (CLEANMASK(xev.xkey.state) & ControlMask); + const bool pressing_shift = (CLEANMASK(xev.xkey.state) & ShiftMask); if(pressed_keysym == XK_q && pressing_ctrl) { window.close(); } else if(pressed_keysym == XK_Escape || pressed_keysym == XK_BackSpace) { @@ -3614,7 +3619,8 @@ namespace QuickMedia { } else if(pressed_keysym == XK_f && pressing_ctrl) { video_player->cycle_fullscreen(); } else if(pressed_keysym == XK_s && pressing_ctrl && !video_page->is_local()) { - video_page_download_video(video_page->get_download_url(video_get_max_height()), video_page->get_filename(), video_player_window); + const bool download_no_dialog = pressing_shift; + video_page_download_video(video_page->get_download_url(video_get_max_height()), video_page->get_filename(), video_player_window, download_no_dialog); } else if(pressed_keysym == XK_F5 && !video_page->is_local()) { double resume_start_time = 0.0; video_player->get_time_in_file(&resume_start_time); @@ -4777,7 +4783,7 @@ namespace QuickMedia { } else if(event.key.code == mgl::Keyboard::S && event.key.control) { BodyItem *selected_item = thread_body->get_selected(); if(selected_item && !selected_item->url.empty()) - download_async_gui(selected_item->url, file_manager_start_dir.string(), false, ""); + download_async_gui(selected_item->url, file_manager_start_dir.string(), false, "", event.key.shift); } BodyItem *selected_item = thread_body->get_selected(); @@ -4905,7 +4911,7 @@ namespace QuickMedia { redraw = true; frame_skip_text_entry = true; } else if(event.key.code == mgl::Keyboard::S && event.key.control) { - download_async_gui(attached_image_url, file_manager_start_dir.string(), false, ""); + download_async_gui(attached_image_url, file_manager_start_dir.string(), false, "", event.key.shift); } } } @@ -6556,7 +6562,7 @@ namespace QuickMedia { avatar_applied = false; return true; } else if(message_type == MessageType::FILE) { - download_async_gui(selected->url, file_manager_start_dir.string(), no_video, filename); + download_async_gui(selected->url, file_manager_start_dir.string(), no_video, filename, false); return true; } @@ -6582,7 +6588,7 @@ namespace QuickMedia { return false; }; - auto download_selected_item = [this, &ui_tabs, PINNED_TAB_INDEX, MESSAGES_TAB_INDEX](BodyItem *selected) { + auto download_selected_item = [this, &ui_tabs, PINNED_TAB_INDEX, MESSAGES_TAB_INDEX](BodyItem *selected, bool no_dialog) { if(!selected) return false; @@ -6609,7 +6615,7 @@ namespace QuickMedia { else if(string_starts_with(filename, file_prefix)) filename.erase(filename.begin(), filename.begin() + strlen(file_prefix)); - download_async_gui(selected->url, file_manager_start_dir.string(), no_video, filename); + download_async_gui(selected->url, file_manager_start_dir.string(), no_video, filename, no_dialog); return true; } } @@ -6890,8 +6896,8 @@ namespace QuickMedia { } else if(event.key.code == mgl::Keyboard::S && event.key.control) { BodyItem *selected = tabs[selected_tab].body->get_selected(); if(selected) { - if(!download_selected_item(selected)) - download_selected_item(selected->embedded_item.get()); + if(!download_selected_item(selected, event.key.shift)) + download_selected_item(selected->embedded_item.get(), event.key.shift); } } } @@ -7819,7 +7825,7 @@ namespace QuickMedia { return 0; } - void Program::download_page(std::string url, std::string download_filename) { + void Program::download_page(std::string url, std::string download_filename, bool no_dialog) { window.set_title(("QuickMedia - Select where you want to save " + std::string(url)).c_str()); url = invidious_url_to_youtube_url(url); @@ -7970,10 +7976,25 @@ namespace QuickMedia { } string_replace_all(filename, '/', '_'); - std::string output_filepath = file_save_page(filename); - if(!window.is_open() || output_filepath.empty()) { - exit_code = 1; - return; + std::string output_filepath; + if(no_dialog) { + std::string filename_ext = Path(filename).ext(); + if(is_video_ext(filename_ext.c_str())) + output_filepath = get_config().download.video_directory; + else if(is_image_ext(filename_ext.c_str())) + output_filepath = get_config().download.image_directory; + else if(is_music_ext(filename_ext.c_str())) + output_filepath = get_config().download.music_directory; + else + output_filepath = get_config().download.file_directory; + output_filepath += "/" + filename; + } else { + window.set_visible(true); + output_filepath = file_save_page(filename); + if(!window.is_open() || output_filepath.empty()) { + exit_code = 1; + return; + } } mgl::vec2i monitor_size; @@ -7984,10 +8005,15 @@ namespace QuickMedia { window.set_size_limits(window_size, window_size); window.set_position(mgl::vec2i(focused_monitor_center.x - window_size.x * 0.5f, focused_monitor_center.y - window_size.y * 0.5f)); + std::string window_title = "QuickMedia - downloading "; + window_title += Path(output_filepath).filename(); + window.set_title(window_title.c_str()); + window.set_visible(true); + std::string output_filepath_s = output_filepath; char *output_dir = dirname(output_filepath_s.data()); if(create_directory_recursive(output_dir) != 0) { - show_notification("QuickMedia", std::string("Failed to download ") + url + " to " + output_filepath, Urgency::CRITICAL); + show_notification("QuickMedia", std::string("Failed to download ") + url + " to " + output_filepath + " (failed to create directory)", Urgency::CRITICAL); exit_code = 1; return; } |