aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md9
-rw-r--r--example-config.json6
-rw-r--r--include/Config.hpp8
-rw-r--r--include/DownloadUtils.hpp2
-rw-r--r--include/FileAnalyzer.hpp1
-rw-r--r--include/QuickMedia.hpp6
-rw-r--r--src/Config.cpp39
-rw-r--r--src/DownloadUtils.cpp4
-rw-r--r--src/FileAnalyzer.cpp20
-rw-r--r--src/QuickMedia.cpp68
10 files changed, 133 insertions, 30 deletions
diff --git a/README.md b/README.md
index 898a3ea..bdf9024 100644
--- a/README.md
+++ b/README.md
@@ -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;
}