From d4cd63129ae5dff8fd69525424e0f8cb9ae1a905 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Wed, 16 Feb 2022 21:54:47 +0100 Subject: Wip: fix video duration not working for some analyzed files, get frame in middle of video instead of first frame for thumbnail --- README.md | 5 ++-- example-config.json | 5 ++++ include/Config.hpp | 7 +++++ include/FileAnalyzer.hpp | 10 +++++-- include/VideoPlayer.hpp | 33 ++++++++++++++-------- plugins/LocalAnime.hpp | 21 ++++++++++++++ src/AsyncImageLoader.cpp | 6 ++-- src/Config.cpp | 45 ++++++++++++++++++++---------- src/FileAnalyzer.cpp | 64 +++++++++++++++++++++++++----------------- src/QuickMedia.cpp | 20 ++++++++++++-- src/VideoPlayer.cpp | 69 ++++++++++++++++++++-------------------------- src/plugins/LocalAnime.cpp | 42 ++++++++++++++++++++++++++++ src/plugins/Matrix.cpp | 4 +-- 13 files changed, 230 insertions(+), 101 deletions(-) create mode 100644 plugins/LocalAnime.hpp create mode 100644 src/plugins/LocalAnime.cpp diff --git a/README.md b/README.md index 91f89bb..9533bc0 100644 --- a/README.md +++ b/README.md @@ -161,11 +161,12 @@ Type text and then wait and QuickMedia will automatically search.\ `/id`: Show the room id. ## Config Config is loaded from `~/.config/quickmedia/config.json` if it exists. See [example-config.json](https://git.dec05eba.com/QuickMedia/plain/example-config.json) for an example config. All fields in the config file are optional.\ -If `use_system_mpv_config` is set to `true` then your systems mpv config in `~/.config/mpv/mpv.conf` and plugins will be used. If you have a mpv plugin installed that uses `input-ipc-server` (such as `mpv-discord`) then it will break quickmedia integration with mpv (especially key inputs such as ctrl+r). +If `use_system_mpv_config` is set to `true` then your systems mpv config in `~/.config/mpv/mpv.conf` and plugins will be used. ## Theme Theme is loaded from `~/.config/quickmedia/themes/.json` if it exists or from `/usr/share/quickmedia/themes`. Theme name is set in `~/.config/quickmedia/config.json` under the variable `theme`.\ Default themes available: `default, nord`.\ -See [default.json](https://git.dec05eba.com/QuickMedia/plain/themes/default.json) for an example theme. +See [default.json](https://git.dec05eba.com/QuickMedia/plain/themes/default.json) for an example theme.\ +The `default` there is used by default. ## Environment variables If `xdg-open` is not installed then the `BROWSER` environment variable is used to open links in a browser.\ Set `QM_PHONE_FACTOR=1` to disable the room list side panel in matrix. diff --git a/example-config.json b/example-config.json index 566bbae..321f331 100644 --- a/example-config.json +++ b/example-config.json @@ -26,6 +26,11 @@ "sort_by_name": false, "sort_chapters_by_name": false }, + "local_anime": { + "directory": "", + "sort_by_name": false, + "sort_episodes_by_name": true + }, "use_system_fonts": false, "use_system_mpv_config": false, "theme": "default", diff --git a/include/Config.hpp b/include/Config.hpp index b2abbf1..ee9e190 100644 --- a/include/Config.hpp +++ b/include/Config.hpp @@ -36,6 +36,12 @@ namespace QuickMedia { bool sort_chapters_by_name = false; }; + struct LocalAnimeConfig { + std::string directory; + bool sort_by_name = false; + bool sort_episodes_by_name = true; + }; + struct Config { Config() = default; Config(const Config&) = delete; @@ -47,6 +53,7 @@ namespace QuickMedia { InputConfig input; VideoConfig video; LocalMangaConfig local_manga; + LocalAnimeConfig local_anime; bool use_system_fonts = false; bool use_system_mpv_config = false; std::string theme = "default"; diff --git a/include/FileAnalyzer.hpp b/include/FileAnalyzer.hpp index b06ef91..acc62d7 100644 --- a/include/FileAnalyzer.hpp +++ b/include/FileAnalyzer.hpp @@ -5,6 +5,8 @@ #include namespace QuickMedia { + class FileAnalyzer; + struct Dimensions { int width; int height; @@ -17,6 +19,7 @@ namespace QuickMedia { VIDEO_MPEG, VIDEO_WEBM, VIDEO_FLV, + VIDEO_WMV, AUDIO_BASIC, AUDIO_AIFF, AUDIO_MPEG, @@ -40,13 +43,15 @@ namespace QuickMedia { bool is_video_ext(const char *ext); // Set |width| or |height| to 0 to disable scaling. - // This function is async. - bool video_get_first_frame(const char *filepath, const char *destination_path, int width = 0, int height = 0); + // TODO: Make this async + bool video_get_first_frame(const FileAnalyzer &file, const char *destination_path, int width = 0, int height = 0); class FileAnalyzer { public: FileAnalyzer(); bool load_file(const char *filepath, bool load_file_metadata = true); + + const std::string& get_filepath() const; ContentType get_content_type() const; size_t get_file_size() const; std::optional get_dimensions() const; @@ -55,6 +60,7 @@ namespace QuickMedia { FileAnalyzer(FileAnalyzer&) = delete; FileAnalyzer& operator=(FileAnalyzer&) = delete; private: + std::string filepath; ContentType content_type; size_t file_size; std::optional dimensions; diff --git a/include/VideoPlayer.hpp b/include/VideoPlayer.hpp index f75d76f..989bfd2 100644 --- a/include/VideoPlayer.hpp +++ b/include/VideoPlayer.hpp @@ -33,14 +33,31 @@ namespace QuickMedia { EXITED }; + struct StartupArgs { + std::string path; + std::string audio_path; + mgl::WindowHandle parent_window; + bool no_video = false; + bool use_system_mpv_config = false; + bool use_system_input_config = false; // use_system_mpv_config has to be true for this + bool keep_open = false; + std::string resource_root; + int monitor_height = 1080; + bool use_youtube_dl = false; + std::string title; + std::string start_time; + std::vector chapters; + std::string plugin_name; + }; + // @event_callback is called from another thread - VideoPlayer(bool no_video, bool use_system_mpv_config, bool keep_open, EventCallbackFunc event_callback, VideoPlayerWindowCreateCallback window_create_callback, const std::string &resource_root, int monitor_height, std::string plugin_name); + VideoPlayer(StartupArgs startup_args, EventCallbackFunc event_callback, VideoPlayerWindowCreateCallback window_create_callback); ~VideoPlayer(); VideoPlayer(const VideoPlayer&) = delete; VideoPlayer& operator=(const VideoPlayer&) = delete; // |audio_path| is only set when video and audio are separate files/urls. - Error load_video(const char *path, const char *audio_path, mgl::WindowHandle parent_window, bool use_youtube_dl, const std::string &title, const std::string &start_time = "", const std::vector &chapters = {}); + Error load_video(); // Should be called every update frame Error update(); @@ -52,24 +69,19 @@ namespace QuickMedia { private: uint32_t get_next_request_id(); Error send_command(Json::Value &json_root, Json::Value *result, Json::ValueType result_type); - Error launch_video_process(const char *path, const char *audio_path, mgl::WindowHandle parent_window, const std::string &title, const std::string &start_time); + Error launch_video_process(); VideoPlayer::Error read_ipc_func(); private: - std::string plugin_name; - bool no_video; - bool use_system_mpv_config; - bool keep_open; - bool use_youtube_dl; + StartupArgs startup_args; + pid_t video_process_id; mgl::Clock retry_timer; int connect_tries; int find_window_tries; - int monitor_height; int ipc_socket = -1; EventCallbackFunc event_callback; VideoPlayerWindowCreateCallback window_create_callback; mgl::WindowHandle window_handle; - mgl::WindowHandle parent_window; Display *display; unsigned int request_id_counter; unsigned int expected_request_id; @@ -81,7 +93,6 @@ namespace QuickMedia { ERROR }; ResponseDataStatus response_data_status; - std::string resource_root; char tmp_chapters_filepath[27]; }; } diff --git a/plugins/LocalAnime.hpp b/plugins/LocalAnime.hpp new file mode 100644 index 0000000..cd240d3 --- /dev/null +++ b/plugins/LocalAnime.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "Page.hpp" + +// TODO: Progress > 90% = fully watched (because ending might have been skipped) + +namespace QuickMedia { + class LocalAnimeSearchPage : public LazyFetchPage { + public: + LocalAnimeSearchPage(Program *program) : LazyFetchPage(program) {} + const char* get_title() const override { return "Search"; } + bool search_is_filter() override { return true; } + PluginResult submit(const SubmitArgs &args, std::vector &result_tabs) override; + PluginResult lazy_fetch(BodyItems &result_items) override; + const char* get_bookmark_name() const override { return "local-anime"; } + bool reload_on_page_change() override { return true; } + bool reseek_to_body_item_by_url() override { return true; } + std::shared_ptr get_bookmark_body_item(BodyItem *selected_item) override; + void toggle_read(BodyItem *selected_item) override; + }; +} \ No newline at end of file diff --git a/src/AsyncImageLoader.cpp b/src/AsyncImageLoader.cpp index ddcb604..d7fbfbe 100644 --- a/src/AsyncImageLoader.cpp +++ b/src/AsyncImageLoader.cpp @@ -161,7 +161,7 @@ namespace QuickMedia { void AsyncImageLoader::load_create_thumbnail(const Path &thumbnail_path, const Path &thumbnail_path_resized, ThumbnailData *thumbnail_data, mgl::vec2i resize_target_size) { FileAnalyzer file_analyzer; - if(!file_analyzer.load_file(thumbnail_path.data.c_str(), false)) { + if(!file_analyzer.load_file(thumbnail_path.data.c_str(), true)) { fprintf(stderr, "Failed to convert %s to a thumbnail\n", thumbnail_path.data.c_str()); thumbnail_data->image = std::make_unique(); thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; @@ -169,10 +169,10 @@ namespace QuickMedia { } if(is_content_type_video(file_analyzer.get_content_type())) { - if(video_get_first_frame(thumbnail_path.data.c_str(), thumbnail_path_resized.data.c_str(), resize_target_size.x, resize_target_size.y)) { + if(video_get_first_frame(file_analyzer, thumbnail_path_resized.data.c_str(), resize_target_size.x, resize_target_size.y)) { thumbnail_data->loading_state = LoadingState::READY_TO_LOAD; } else { - fprintf(stderr, "Failed to get first frame of %s\n", thumbnail_path.data.c_str()); + fprintf(stderr, "Failed to get video frame of %s\n", thumbnail_path.data.c_str()); thumbnail_data->image = std::make_unique(); thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; } diff --git a/src/Config.cpp b/src/Config.cpp index 780bf32..e98f4c7 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -131,6 +131,36 @@ namespace QuickMedia { config->video.max_height = max_height_json.asInt(); } + const Json::Value &local_manga_json = json_root["local_manga"]; + if(local_manga_json.isObject()) { + const Json::Value &directory_json = local_manga_json["directory"]; + if(directory_json.isString()) + config->local_manga.directory = directory_json.asString(); + + const Json::Value &sort_by_name_json = local_manga_json["sort_by_name"]; + if(sort_by_name_json.isBool()) + config->local_manga.sort_by_name = sort_by_name_json.asBool(); + + const Json::Value &sort_chapters_by_name_json = local_manga_json["sort_chapters_by_name"]; + if(sort_chapters_by_name_json.isBool()) + config->local_manga.sort_chapters_by_name = sort_chapters_by_name_json.asBool(); + } + + const Json::Value &local_anime_json = json_root["local_anime"]; + if(local_anime_json.isObject()) { + const Json::Value &directory_json = local_anime_json["directory"]; + if(directory_json.isString()) + config->local_anime.directory = directory_json.asString(); + + const Json::Value &sort_by_name_json = local_anime_json["sort_by_name"]; + if(sort_by_name_json.isBool()) + config->local_anime.sort_by_name = sort_by_name_json.asBool(); + + const Json::Value &sort_episodes_by_name_json = local_anime_json["sort_episodes_by_name"]; + if(sort_episodes_by_name_json.isBool()) + config->local_anime.sort_episodes_by_name = sort_episodes_by_name_json.asBool(); + } + const Json::Value &use_system_fonts_json = json_root["use_system_fonts"]; if(use_system_fonts_json.isBool()) config->use_system_fonts = use_system_fonts_json.asBool(); @@ -154,21 +184,6 @@ namespace QuickMedia { const Json::Value &spacing_scale = json_root["spacing_scale"]; if(spacing_scale.isNumeric()) config->spacing_scale = spacing_scale.asFloat(); - - const Json::Value &local_manga_json = json_root["local_manga"]; - if(local_manga_json.isObject()) { - const Json::Value &directory_json = local_manga_json["directory"]; - if(directory_json.isString()) - config->local_manga.directory = directory_json.asString(); - - const Json::Value &sort_by_name_json = local_manga_json["sort_by_name"]; - if(sort_by_name_json.isBool()) - config->local_manga.sort_by_name = sort_by_name_json.asBool(); - - const Json::Value &sort_chapters_by_name_json = local_manga_json["sort_chapters_by_name"]; - if(sort_chapters_by_name_json.isBool()) - config->local_manga.sort_chapters_by_name = sort_chapters_by_name_json.asBool(); - } } const Config& get_config() { diff --git a/src/FileAnalyzer.cpp b/src/FileAnalyzer.cpp index 6f1c1ee..4e4470e 100644 --- a/src/FileAnalyzer.cpp +++ b/src/FileAnalyzer.cpp @@ -21,7 +21,7 @@ namespace QuickMedia { // https://mimesniff.spec.whatwg.org/ // TODO: Test all of these - static const std::array magic_numbers = { + static const std::array magic_numbers = { MagicNumber{ {'R', 'I', 'F', 'F', -1, -1, -1, -1, 'A', 'V', 'I', ' '}, 12, ContentType::VIDEO_AVI }, MagicNumber{ {0x00, 0x00, 0x00, -1, 'f', 't', 'y', 'p', 'i', 's', 'o', 'm'}, 12, ContentType::VIDEO_MP4 }, MagicNumber{ {0x00, 0x00, 0x00, -1, 'f', 't', 'y', 'p', 'm', 'p', '4', '2'}, 12, ContentType::VIDEO_MP4 }, @@ -34,6 +34,7 @@ namespace QuickMedia { MagicNumber{ {0x00, 0x00, 0x01, 0xB3}, 4, ContentType::VIDEO_MPEG }, MagicNumber{ {0x1A, 0x45, 0xDF, 0xA3}, 4, ContentType::VIDEO_WEBM }, MagicNumber{ {'F', 'L', 'V', 0x01}, 4, ContentType::VIDEO_FLV }, + MagicNumber{ {0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF}, 7, ContentType::VIDEO_WMV }, MagicNumber{ {'.', 's', 'n', 'd'}, 4, ContentType::AUDIO_BASIC }, MagicNumber{ {'F', 'O', 'R', 'M', -1, -1, -1, -1, 'A', 'I', 'F', 'F'}, 12, ContentType::AUDIO_AIFF }, MagicNumber{ { 'I', 'D', '3' }, 3, ContentType::AUDIO_MPEG }, @@ -56,7 +57,7 @@ namespace QuickMedia { }; bool is_content_type_video(ContentType content_type) { - return content_type >= ContentType::VIDEO_AVI && content_type <= ContentType::VIDEO_FLV; + return content_type >= ContentType::VIDEO_AVI && content_type <= ContentType::VIDEO_WMV; } bool is_content_type_audio(ContentType content_type) { @@ -75,6 +76,7 @@ namespace QuickMedia { case ContentType::VIDEO_MPEG: return "video/mpeg"; case ContentType::VIDEO_WEBM: return "video/webm"; case ContentType::VIDEO_FLV: return "video/x-flv"; + case ContentType::VIDEO_WMV: return "video/x-ms-asf"; case ContentType::AUDIO_BASIC: return "audio/basic"; case ContentType::AUDIO_AIFF: return "audio/aiff"; case ContentType::AUDIO_MPEG: return "audio/mpeg"; @@ -106,6 +108,7 @@ namespace QuickMedia { || strcase_equals(ext, ".flv") || strcase_equals(ext, ".vob") || strcase_equals(ext, ".ogv") + || strcase_equals(ext, ".ogg") || strcase_equals(ext, ".avi") //|| strcase_equals(ext, ".ts") || strcase_equals(ext, ".mov") @@ -124,30 +127,37 @@ namespace QuickMedia { return 0; } - bool video_get_first_frame(const char *filepath, const char *destination_path, int width, int height) { + bool video_get_first_frame(const FileAnalyzer &file, const char *destination_path, int width, int height) { Path destination_path_tmp = destination_path; destination_path_tmp.append(".ftmp"); - const char *program_args[] = { "ffmpeg", "-y", "-v", "quiet", "-i", filepath, "-vframes", "1", "-f", "singlejpeg", "--", destination_path_tmp.data.c_str(), nullptr }; - std::string ffmpeg_result; - if(exec_program(program_args, nullptr, nullptr) != 0) { - fprintf(stderr, "Failed to execute ffmpeg, maybe its not installed?\n"); - return false; - } + const int middle_seconds = file.get_duration_seconds().value_or(0.0) / 2.0; + char middle_seconds_str[32]; + snprintf(middle_seconds_str, sizeof(middle_seconds_str), "%d", middle_seconds); if(width > 0 && height > 0) { - FileAnalyzer file_analyzer; - const bool success = file_analyzer.load_file(destination_path_tmp.data.c_str(), false) && create_thumbnail(destination_path_tmp, destination_path, mgl::vec2i(width, height), file_analyzer.get_content_type(), false); - remove(destination_path_tmp.data.c_str()); - return success; + char framesize[128]; + snprintf(framesize, sizeof(framesize), "%dx%d", width, height); + + const char *program_args[] = { "ffmpeg", "-y", "-v", "quiet", "-ss", middle_seconds_str, "-i", file.get_filepath().c_str(), "-vframes", "1", "-f", "singlejpeg", "-s", framesize, "--", destination_path_tmp.data.c_str(), nullptr }; + if(exec_program(program_args, nullptr, nullptr) != 0) { + fprintf(stderr, "Failed to execute ffmpeg, maybe its not installed?\n"); + return false; + } } else { - return rename_atomic(destination_path_tmp.data.c_str(), destination_path) == 0; + const char *program_args[] = { "ffmpeg", "-y", "-v", "quiet", "-ss", middle_seconds_str, "-i", file.get_filepath().c_str(), "-vframes", "1", "-f", "singlejpeg", "--", destination_path_tmp.data.c_str(), nullptr }; + if(exec_program(program_args, nullptr, nullptr) != 0) { + fprintf(stderr, "Failed to execute ffmpeg, maybe its not installed?\n"); + return false; + } } + + return rename_atomic(destination_path_tmp.data.c_str(), destination_path) == 0; } // TODO: Remove dependency on ffprobe static bool ffprobe_extract_metadata(const char *filepath, std::optional &dimensions, std::optional &duration_seconds) { - const char *program_args[] = { "ffprobe", "-v", "quiet", "-print_format", "json", "-show_streams", "--", filepath, nullptr }; + const char *program_args[] = { "ffprobe", "-v", "quiet", "-print_format", "json", "-show_streams", "-show_format", "--", filepath, nullptr }; std::string ffprobe_result; if(exec_program(program_args, accumulate_string, &ffprobe_result) != 0) { fprintf(stderr, "Failed to execute ffprobe, maybe its not installed?\n"); @@ -181,20 +191,19 @@ namespace QuickMedia { if(strcmp(codec_type.asCString(), "video") == 0) { const Json::Value &width_json = stream_json["width"]; const Json::Value &height_json = stream_json["height"]; - const Json::Value &duration_json = stream_json["duration"]; if(width_json.isNumeric() && height_json.isNumeric()) dimensions = { width_json.asInt(), height_json.asInt() }; - if(duration_json.isString()) - duration_seconds = atof(duration_json.asCString()); - break; - } else if(strcmp(codec_type.asCString(), "audio") == 0) { - const Json::Value &duration_json = stream_json["duration"]; - if(duration_json.isString()) - duration_seconds = atof(duration_json.asCString()); - // No break here because if there is video after this, we want it to overwrite this } } + const Json::Value &format_json = json_root["format"]; + if(!format_json.isObject()) + return true; + + const Json::Value &duration_json = format_json["duration"]; + if(duration_json.isString()) + duration_seconds = atof(duration_json.asCString()); + return true; } @@ -231,7 +240,7 @@ namespace QuickMedia { unsigned char magic_number_buffer[MAGIC_NUMBER_BUFFER_SIZE]; size_t num_bytes_read = fread(magic_number_buffer, 1, sizeof(magic_number_buffer), file); - if(feof(file)) { + if(feof(file) || num_bytes_read != sizeof(magic_number_buffer)) { perror(filepath); fclose(file); return false; @@ -263,10 +272,15 @@ namespace QuickMedia { duration_seconds = std::nullopt; } + this->filepath = filepath; loaded = true; return true; } + const std::string& FileAnalyzer::get_filepath() const { + return filepath; + } + ContentType FileAnalyzer::get_content_type() const { return content_type; } diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index e317ce3..99e8f1b 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -2892,8 +2892,24 @@ namespace QuickMedia { } } - video_player = std::make_unique(is_audio_only, get_config().use_system_mpv_config, is_matrix && !is_youtube, video_event_callback, on_window_create, resources_root, video_max_height, plugin_name); - VideoPlayer::Error err = video_player->load_video(v.c_str(), a.c_str(), window.get_system_handle(), use_youtube_dl, video_title, start_time, media_chapters); + VideoPlayer::StartupArgs startup_args; + startup_args.path = v; + startup_args.audio_path = a; + startup_args.parent_window = window.get_system_handle(); + startup_args.no_video = is_audio_only; + startup_args.use_system_mpv_config = get_config().use_system_mpv_config; + startup_args.use_system_input_config = false; + startup_args.keep_open = is_matrix && !is_youtube; + startup_args.resource_root = resources_root; + startup_args.monitor_height = video_max_height; + startup_args.use_youtube_dl = use_youtube_dl; + startup_args.title = video_title; + startup_args.start_time = start_time; + startup_args.chapters = std::move(media_chapters); + startup_args.plugin_name = plugin_name; + + video_player = std::make_unique(std::move(startup_args), video_event_callback, on_window_create); + VideoPlayer::Error err = video_player->load_video(); if(err != VideoPlayer::Error::OK) { std::string err_msg = "Failed to play url: "; err_msg += video_page->get_url(); diff --git a/src/VideoPlayer.cpp b/src/VideoPlayer.cpp index 76fa82a..4be0671 100644 --- a/src/VideoPlayer.cpp +++ b/src/VideoPlayer.cpp @@ -90,27 +90,20 @@ namespace QuickMedia { } } - VideoPlayer::VideoPlayer(bool no_video, bool use_system_mpv_config, bool keep_open, EventCallbackFunc _event_callback, VideoPlayerWindowCreateCallback _window_create_callback, const std::string &resource_root, int monitor_height, std::string plugin_name) : + VideoPlayer::VideoPlayer(StartupArgs startup_args, EventCallbackFunc _event_callback, VideoPlayerWindowCreateCallback _window_create_callback) : exit_status(0), - plugin_name(std::move(plugin_name)), - no_video(no_video), - use_system_mpv_config(use_system_mpv_config), - keep_open(keep_open), - use_youtube_dl(true), + startup_args(std::move(startup_args)), video_process_id(-1), connect_tries(0), find_window_tries(0), - monitor_height(monitor_height), event_callback(_event_callback), window_create_callback(_window_create_callback), window_handle(0), - parent_window(0), display(nullptr), request_id_counter(1), expected_request_id(0), request_response_data(Json::nullValue), - response_data_status(ResponseDataStatus::NONE), - resource_root(resource_root) + response_data_status(ResponseDataStatus::NONE) { tmp_chapters_filepath[0] = '\0'; display = XOpenDisplay(NULL); @@ -118,7 +111,7 @@ namespace QuickMedia { show_notification("QuickMedia", "Failed to open display to X11 server", Urgency::CRITICAL); abort(); } - fprintf(stderr, "Video max height: %d\n", monitor_height); + fprintf(stderr, "Video max height: %d\n", startup_args.monitor_height); } VideoPlayer::~VideoPlayer() { @@ -147,9 +140,7 @@ namespace QuickMedia { return path; } - VideoPlayer::Error VideoPlayer::launch_video_process(const char *path, const char *audio_path, mgl::WindowHandle _parent_window, const std::string &title, const std::string &start_time) { - parent_window = _parent_window; - + VideoPlayer::Error VideoPlayer::launch_video_process() { int fd[2]; if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0) { perror("Failed to create socketpair for video player"); @@ -162,13 +153,13 @@ namespace QuickMedia { fcntl(ipc_socket, F_SETFL, flags | O_NONBLOCK); const std::string ipc_fd = std::to_string(fd[1]); - std::string input_conf = "--input-conf=" + resource_root + "input.conf"; + std::string input_conf = "--input-conf=" + startup_args.resource_root + "input.conf"; std::string cache_dir = "--cache-dir=" + std::move(get_cache_dir().join("media").data); std::string wid_arg = "--wid="; - wid_arg += std::to_string(parent_window); + wid_arg += std::to_string(startup_args.parent_window); - std::string video_player_filepath = resource_root + "/video_player/sibs-build/linux_x86_64/" + std::string video_player_filepath = startup_args.resource_root + "/video_player/sibs-build/linux_x86_64/" #ifdef NDEBUG "release/" #else @@ -203,27 +194,29 @@ namespace QuickMedia { "--osc=yes", //"--force_all_formats=no", cache_dir.c_str(), - input_conf.c_str(), wid_arg.c_str(), "--ipc-fd", ipc_fd.c_str() }); + + if(!startup_args.use_system_input_config) + args.push_back(input_conf.c_str()); if(is_running_wayland()) { args.push_back("--gpu-context=x11egl"); fprintf(stderr, "Wayland detected. Launching mpv in x11egl mode\n"); } - if(keep_open) + if(startup_args.keep_open) args.push_back("--keep-open=yes"); std::string ytdl_format; - if(no_video) + if(startup_args.no_video) ytdl_format = "--ytdl-format=bestaudio/best"; else - ytdl_format = "--ytdl-format=bestvideo[height<=?" + std::to_string(monitor_height) + "]+bestaudio/best"; + ytdl_format = "--ytdl-format=bestvideo[height<=?" + std::to_string(startup_args.monitor_height) + "]+bestaudio/best"; - if(!use_youtube_dl) + if(!startup_args.use_youtube_dl) args.push_back("--ytdl=no"); else args.push_back(ytdl_format.c_str()); @@ -233,7 +226,7 @@ namespace QuickMedia { if(get_file_type(mpris_path) == FileType::REGULAR) mpris_arg = "--scripts=" + mpris_path.data; - if(!use_system_mpv_config) { + if(!startup_args.use_system_mpv_config) { args.insert(args.end(), { "--config=no", "--profile=gpu-hq", @@ -246,12 +239,12 @@ namespace QuickMedia { } std::string force_media_title_arg; - if(!title.empty()) { - force_media_title_arg = "--force-media-title=" + title; + if(!startup_args.title.empty()) { + force_media_title_arg = "--force-media-title=" + startup_args.title; args.push_back(force_media_title_arg.c_str()); } - if(no_video) + if(startup_args.no_video) args.push_back("--video=no"); std::string chapters_file_arg; @@ -260,18 +253,18 @@ namespace QuickMedia { args.push_back(chapters_file_arg.c_str()); } - if(audio_path && audio_path[0] != '\0') { + if(!startup_args.audio_path.empty()) { args.push_back("--audio-file"); - args.push_back(audio_path); + args.push_back(startup_args.audio_path.c_str()); } std::string start_time_arg; - if(!start_time.empty()) { - start_time_arg = "--start=" + start_time; + if(!startup_args.start_time.empty()) { + start_time_arg = "--start=" + startup_args.start_time; args.push_back(start_time_arg.c_str()); } - args.insert(args.end(), { "--", path, nullptr }); + args.insert(args.end(), { "--", startup_args.path.c_str(), nullptr }); if(exec_program_async(args.data(), &video_process_id) != 0) { close(fd[1]); @@ -284,18 +277,16 @@ namespace QuickMedia { return Error::OK; } - VideoPlayer::Error VideoPlayer::load_video(const char *path, const char *audio_path, mgl::WindowHandle _parent_window, bool use_youtube_dl, const std::string &title, const std::string &start_time, const std::vector &chapters) { + VideoPlayer::Error VideoPlayer::load_video() { // 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); - this->use_youtube_dl = use_youtube_dl; - if(!create_tmp_file_with_chapters_data(tmp_chapters_filepath, chapters)) + assert(!startup_args.path.empty()); + if(!create_tmp_file_with_chapters_data(tmp_chapters_filepath, startup_args.chapters)) fprintf(stderr, "Warning: failed to create chapters file. Chapters will not be displayed\n"); - fprintf(stderr, "Playing video: %s, audio: %s\n", path ? path : "", audio_path ? audio_path : ""); + fprintf(stderr, "Playing video: %s, audio: %s\n", startup_args.path.c_str(), startup_args.audio_path.c_str()); if(video_process_id == -1) - return launch_video_process(path, audio_path, _parent_window, title, start_time); + return launch_video_process(); fprintf(stderr, "TODO: Implement VideoPlayer::load_video without restarting the video player\n"); abort(); @@ -341,7 +332,7 @@ namespace QuickMedia { if(window_handle == 0 && retry_timer.get_elapsed_time_seconds() >= RETRY_TIME_SEC) { retry_timer.restart(); - std::vector child_windows = get_child_window(display, parent_window); + std::vector child_windows = get_child_window(display, startup_args.parent_window); size_t num_children = child_windows.size(); if(num_children == 0) { ++find_window_tries; diff --git a/src/plugins/LocalAnime.cpp b/src/plugins/LocalAnime.cpp new file mode 100644 index 0000000..4bc296a --- /dev/null +++ b/src/plugins/LocalAnime.cpp @@ -0,0 +1,42 @@ +#include "../../plugins/LocalAnime.hpp" +#include "../../include/Config.hpp" +#include "../../include/Storage.hpp" +#include "../../include/Notification.hpp" + +namespace QuickMedia { + static bool validate_local_anime_dir_config_is_set() { + if(get_config().local_anime.directory.empty()) { + show_notification("QuickMedia", "local_anime.directory config is not set", Urgency::CRITICAL); + return false; + } + + if(get_file_type(get_config().local_anime.directory) != FileType::DIRECTORY) { + show_notification("QuickMedia", "local_anime.directory config is not set to a valid directory", Urgency::CRITICAL); + return false; + } + + return true; + } + + PluginResult LocalAnimeSearchPage::submit(const SubmitArgs &args, std::vector &result_tabs) { + if(!validate_local_anime_dir_config_is_set()) + return PluginResult::OK; + + return PluginResult::ERR; + } + + PluginResult LocalAnimeSearchPage::lazy_fetch(BodyItems &result_items) { + if(!validate_local_anime_dir_config_is_set()) + return PluginResult::OK; + + return PluginResult::ERR; + } + + std::shared_ptr LocalAnimeSearchPage::get_bookmark_body_item(BodyItem *selected_item) { + return nullptr; + } + + void LocalAnimeSearchPage::toggle_read(BodyItem *selected_item) { + // TODO: + } +} \ No newline at end of file diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index 6b61aed..367c777 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -3690,7 +3690,7 @@ namespace QuickMedia { PluginResult Matrix::upload_file(RoomData *room, const std::string &filepath, UploadInfo &file_info, UploadInfo &thumbnail_info, std::string &err_msg, bool upload_thumbnail) { FileAnalyzer file_analyzer; - if(!file_analyzer.load_file(filepath.c_str())) { + if(!file_analyzer.load_file(filepath.c_str(), true)) { err_msg = "Failed to load " + filepath; return PluginResult::ERR; } @@ -3722,7 +3722,7 @@ namespace QuickMedia { char tmp_filename[] = "/tmp/quickmedia_video_frame_XXXXXX"; int tmp_file = mkstemp(tmp_filename); if(tmp_file != -1) { - if(video_get_first_frame(filepath.c_str(), tmp_filename, thumbnail_max_size.x, thumbnail_max_size.y)) { + if(video_get_first_frame(file_analyzer, tmp_filename, thumbnail_max_size.x, thumbnail_max_size.y)) { UploadInfo upload_info_ignored; // Ignore because it wont be set anyways. Thumbnails dont have thumbnails. PluginResult upload_thumbnail_result = upload_file(room, tmp_filename, thumbnail_info, upload_info_ignored, err_msg, false); if(upload_thumbnail_result != PluginResult::OK) { -- cgit v1.2.3