diff --git a/Makefile b/Makefile index 3546650..89c4792 100644 --- a/Makefile +++ b/Makefile @@ -27,4 +27,7 @@ firefox: clean: @rm -f open-in-mpv Firefox.zip Chrome.crx -.PHONY: all release debug install uninstall firefox clean \ No newline at end of file +fmt: + clang-format -i src/*.{hpp,cpp} + +.PHONY: all release debug install uninstall firefox clean fmt \ No newline at end of file diff --git a/README.md b/README.md index 5384e99..c6a34ed 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,14 @@ -# open-in-mpv +

open-in-mpv

+ This is a simple web extension (for Chrome and Firefox) which helps open any video in the currently open tab in the [mpv player](https://mpv.io). The extension itself shares a lot of code with the one from the awesome [iina](https://github.com/iina/iina), while the (bare) backend is written in C++20 (this is a rewrite from Rust). +- [Installation](#installation) + - [The `mpv://` protocol](#the-mpv-protocol) + - [Playlist and `enqueue` functionality](#playlist-and-enqueue-functionality) + - [Player support](#player-support) + ## Installation > Compiled binaries and packed extensions can be found in the [releases page](https://github.com/Baldomo/open-in-mpv/releases). @@ -15,7 +21,13 @@ sudo make install ### The `mpv://` protocol `open-in-mpv install-protocol` will create a custom `xdg-open` desktop file with a scheme handler for the `mpv://` protocol. This lets `xdg-open` call `open-in-mpv` with an encoded URI, so that it can be parsed and the information can be relayed to `mpv` - this logic follows how `iina` parses and opens custom URIs with the `iina://` protocol on Mac. `install-protocol.sh` has the same functionality. -The table below is a simple documentation of the URL query keys and values used to let the `open-in-mpv` executable what to do: +Please note that this specification is enforced quite strictly, as the program will error out when: + +- The protocol is not `mpv://` +- The method/path is not `/open` +- The query is empty + +The table below is a simple documentation of the URL query keys and values used to let the `open-in-mpv` executable what to do. | Key | Example value | Description | |---------------|----------------------------------------|---------------| @@ -24,7 +36,7 @@ The table below is a simple documentation of the URL query keys and values used | `pip` | `1` | Simulates a picture-in-picture mode (only works with mpv for now) | | `enqueue` | `1` | Adds a video to the queue (see below) | | `new_window` | `1` | Forcibly starts a video in a new window even if one is already open | -| `player` | `celluloid` | Starts any arbitrary video player (only mpv-based ones are recommended, such as [Celluloid](https://celluloid-player.github.io/)) | +| `player` | `celluloid` | Starts any supported video player (see [Player support](#player-support)) | | `flags` | `--vo%3Dgpu` | Custom command options and flags to be passed to the video player, URL-encoded | ### Playlist and `enqueue` functionality @@ -32,4 +44,7 @@ For `enqueue` to work properly with any mpv-based player (provided it supports m ```conf input-ipc-server=/tmp/mpvsocket -``` \ No newline at end of file +``` + +### Player support +Supported players are defined in `src/players.hpp`, where the struct `player` defines supported functionality and command line flag overrides. To request support for a player you're welcome to open a new issue or a pull request. \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index ecfe8be..955796c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -61,7 +61,7 @@ int main(int argc, char const *argv[]) { return 0; } - std::cout << "Error writing to socket, opening new instance" + std::cerr << "Error writing to socket, opening new instance" << std::endl; } std::system(mo->build_cmd().c_str()); diff --git a/src/options.hpp b/src/options.hpp index 7c6e506..d71093a 100644 --- a/src/options.hpp +++ b/src/options.hpp @@ -3,12 +3,35 @@ #include "players.hpp" #include "url.hpp" +#include +#include #include #include #include +#include using std::string; +namespace { +// Replace all occurrences of `from` with `to` in `str` +string replace_all(string str, const string &from, const string &to) { + size_t start_pos = 0; + while ((start_pos = str.find(from, start_pos)) != string::npos) { + str.replace(start_pos, from.length(), to); + // Handles case where 'to' is a substring of 'from' + start_pos += to.length(); + } + return str; +} + +// Remove all characters equals to `c` at the beginning of the string +string lstrip(string str, char c) { + while (str.at(0) == c) + str.replace(0, 1, ""); + return str; +} +} // namespace + namespace oim { /* @@ -27,6 +50,11 @@ class options { bool enqueue_; bool new_window_; + /* + * Parses flag overrides and returns the final flags + */ + string override_flags(); + public: /* * Constructor for oim::options @@ -73,12 +101,16 @@ string options::build_cmd() { } ret << player_info_->executable << " "; - if (fullscreen_) + if (fullscreen_ && !player_info_->fullscreen.empty()) ret << player_info_->fullscreen << " "; - if (pip_) + if (pip_ && !player_info_->pip.empty()) ret << player_info_->pip << " "; - if (!flags_.empty()) - ret << flags_ << " "; + if (!flags_.empty()) { + if (!player_info_->flag_overrides.empty()) + ret << override_flags() << " "; + else + ret << flags_ << " "; + } ret << url_; return ret.str(); @@ -99,6 +131,41 @@ string options::build_ipc() { return ret.str(); } +string options::override_flags() { + // Return immediatly in case there are no overrides + if (player_info_->flag_overrides.empty()) + return flags_; + + bool star = false; + std::ostringstream ret; + + // Check whether there's a global override + auto star_pair = player_info_->flag_overrides.find("*"); + if (star_pair != player_info_->flag_overrides.end()) + star = true; + + // Turn flags_ into a stream to tokenize somewhat idiomatically + auto flagstream = std::istringstream{ flags_ }; + string tmp; + + while (flagstream >> tmp) { + if (star) { + // Remove all dashes at the beginning of the flag + string stripped = ::lstrip(tmp, '-'); + ret << ::replace_all((*star_pair).second, "%s", stripped); + } else { + // Search for the flag currently being processed + auto fo = player_info_->flag_overrides.find(tmp); + if (fo == player_info_->flag_overrides.end()) + continue; + + ret << ::replace_all((*fo).second, "%s", tmp); + } + } + + return ret.str(); +} + void options::parse(const char *url_s) { oim::url u(url_s); @@ -119,15 +186,12 @@ void options::parse(const char *url_s) { if (player_info_ == nullptr) throw string("Unsupported player: ") + player_; - fullscreen_ = u.query_value("fullscreen") == "1"; + fullscreen_ = u.query_value("full_screen") == "1"; pip_ = u.query_value("pip") == "1"; enqueue_ = u.query_value("enqueue") == "1"; new_window_ = u.query_value("new_window") == "1"; } -bool options::needs_ipc() { - // For now this is needed only when queuing videos - return enqueue_; -} +bool options::needs_ipc() { return player_info_->needs_ipc; } } // namespace oim \ No newline at end of file diff --git a/src/players.hpp b/src/players.hpp index dc37bd5..24409f7 100644 --- a/src/players.hpp +++ b/src/players.hpp @@ -27,17 +27,20 @@ struct player { bool needs_ipc; /* + * Overrides for any extra command line flag + * * Override syntax: * `"*"`: matches anything and will take precedence over any other - * override e.g. the pair `{"*", ""}` will void all flags + * override + * e.g. the pair `{"*", ""}` will void all flags * `"flag"`: matches the flag `--flag` - * e.g. the pair `{"foo", "bar"}` will replace `--foo` with - * `--bar` + * e.g. the pair `{"--foo", "--bar"}` will replace `--foo` with + * `--bar` * `"%s"`: is replaced with the original flag without the leading `--` - * e.g. the pair `{"foo", "--%s-bar"}` will replace `--foo` with - * `--foo-bar` + * e.g. the pair `{"--foo", "--%s-bar"}` will replace `--foo` with + * `--foo-bar` * - * Note: command line options with parameters such as --foo=bar are + * Note: command line options with parameters such as `--foo=bar` are * considered a flags as a whole */ unordered_map flag_overrides; @@ -61,7 +64,7 @@ unordered_map player_info = { .enqueue = "--enqueue", .new_window = "--new-window", .needs_ipc = false, - .flag_overrides = { { "*", "--mpv-options=%s" } } } } + .flag_overrides = { { "*", "--mpv-options=%s" } } } }, }; player *get_player_info(string name) {