New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Album artwork via Ueberzug #437
base: master
Are you sure you want to change the base?
Conversation
I think this PR is clean and feature-complete enough for review now.... |
Damn that's cool |
a0bc7be updates the configure script to check for e31a33e updates the configure script to check for
Not to mention json has its own problems. Linux doesn't require that filenames be valid unicode. json doesn't support non-unicode strings. We can construct a path that cannot be passed successfully to either ueberzug's simple or json parsers by creating a filename with both tabs and non-unicode characters (please don't do this). $ cp img.jpg "$(printf "\t\xbd\xb2.jpg")" |
There is also the possibility of using Boost.PropertyTree for reading/writing JSON. I would also prefer to work with Boost.JSON, but Boost.PropertyTree has the advantage to be around since Boost 1.41. Edit: I need to try out this PR. :) |
@evanc577 Thanks! Would it be a lot of work to use PropertyTree instead? Considering that boost::json is very new. |
Easy enough, switched to property_tree and lowered min boost version to 1.64 |
virtual void removeArtwork() = 0; | ||
}; | ||
|
||
class UeberzugBackend : public ArtworkBackend |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What other backends could be there? I'm wondering whether this abstraction is needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure? There are a lot of ways to draw images onto a terminal, but I haven't found any other that plays nicely with ncurses. But is the extra layer of abstraction that big of a deal?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would love libsixel is that's possible so i can use it in OSX as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ueberzug depends on X11, so Wayland and MacOS (probably? I don't own a mac) won't work. I think I will probably end up implementing sixel and kitty backends.
I did a quick and dirty kitty backend proof of concept with the kitty icat program. It works, but the experience isn't great (images flickering when switching screens). The kitty graphics protocol exposes more options that icat doesn't, and should let me address that issue, but requires me to use ImageMagick.
I've only briefly looked at sixel, but I think it should be possible too. The main problem is that I haven't found a good reference for how to actually write the escape sequences. Do I need to use libsixel? Or can I write the escape sequences myself? I think this also requires ImageMagick.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Libsixel would be awesome. I know Iterm2 on Mac supports it and there's a PR waiting for Alacritty to support it as well.
Thanks for your awesome work!
Do you think it's worth adding support for fetching covers from the Internet (and storing them in I don't know where it fetches them from though. |
Fetching art from the internet might be for another PR, this one's scope is already getting pretty big. I've been playing around with libmpdclient, and even though it doesn't have direct support for the Pros:
Cons:
I've created a new branch https://github.com/evanc577/ncmpcpp/tree/artwork-mpd with a working implementation. It's only been tested with a local MPD server and no password. |
Thanks for the clarification! It's unfortunate that mopidy doesn't have the support (yet, at least). I suppose as an alternative, it could be possible to extract albumart URLs through mpris (which is exposed through mopidy-mpris), but I have no idea how feasible that would be to include here. |
#include <map> | ||
#include <boost/regex.hpp> | ||
|
||
#include "charset.h" | ||
#include "mpdpp.h" | ||
|
||
MPD::Connection Mpd; | ||
MPD::Connection Mpd_artwork; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since I am using a worker thread to retrieve artwork and MPD connections aren't thread safe, I need another MPD connection. The other option is to put synchronization everywhere. I think using a separate connection is better.
Alignment of album artwork within window. One of "N", "NE", "E", "SE", "S", "SW", "W", "NW", "center". | ||
.TP | ||
.B font_width = pixels | ||
Manually set font width in pixels. Set to 0 to try auto detection, support depends on terminal emulator. Check error log if no image appears. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the font width/height?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the size of an individual character as displayed by the terminal. Basically take a screenshot of the cursor and count how many pixels large it is.
Some terminals don't have a way to detect how large the window actually is in pixels, so this is a manual workaround.
src/screens/artwork.cpp
Outdated
|
||
Artwork* myArtwork; | ||
|
||
Magick::Blob Artwork::art_buffer; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why so many global variables?
Also, can you briefly describe how that works? I see there's a worker thread, but I'd like to have a high level description of what's going on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are static
member initializations for the Artwork
class.
The basic idea of the architecture is:
- (main thread) When the artwork needs to be updated, send a command to the worker thread. The commands include updating the artwork or removing it. The main thread is not blocked, continues execution.
- (worker thread) Wakes up, processes the command it just received.
- If an update command is received, fetch the correct image and resize if needed, then call one of the backends (Ueberzug or Kitty) to display it.
- If remove command, call backend to remove the image from screen.
- (worker thread) Goes to sleep until a new command is available.
Artwork fetching is performed by the worker thread, which will either check the local filesystem, or query MPD. Once the raw image data is available, ImageMagick is used to resize and align the image according to the user's configuration.
The Ueberzug backend is implemented fairly easily. On startup, Ueberzug is spawned as a child process with a pipe to its stdin. When the image needs to be displayed or updated, the worker thread writes JSON to Ueberzug's stdin, and it handles the rest. On shutdown, the pipe is closed, which stops Ueberzug, and the process is reaped.
The Kitty backed is a bit more complex. For reference, the Kitty graphics protocol is described here. Since this protocol relies on writing to stdout, there can easily be race conditions if both the main and worker threads both try to write output at the same time. The solution is to only write to stdout from the main thread. On startup, a self pipe is created, with the write end given to the worker thread and the read end polled by the main select()
loop. When the worker thread is finished processing a command, it will write some data to the pipe, which will wake up the main thread. The main thread can now write the needed data to stdout without a race.
} | ||
|
||
// save current cursor position | ||
std::cout << "\0337"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't using ncurses for moving the cursor work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I did try that before and it was extremely buggy / didn't work. Same with writing terminal control sequences via ncurses.
winsize Artwork::getWinSize() | ||
{ | ||
struct winsize sz; | ||
ioctl(STDOUT_FILENO, TIOCGWINSZ, &sz); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is for getting the current window size in pixels. The main issue here is that the terminal operates on characters, but images are measured in pixels, with no direct correlation between them. By knowing the width/height of the terminal window in pixels, and the number of character rows/columns, I can determine the size of each character in pixels and resize the output image correctly.
Is there a branch for this I could try out? |
The source branch for this PR is https://github.com/evanc577/ncmpcpp/tree/artwork but review progress seems to have stalled |
Just checked out your fork (amazing by the way) and was wondering: Does |
@evanc577 Is it possible to use an alternative backend other than ueberzug or kitty? For example, |
There aren't any alternatives implemented, but they shouldn't be difficult to implement. Assuming they play well with ncurses. @arybczak thoughts about merging this PR? |
@evanc577 Can't build this PR on Ubuntu 20.04.
|
@spvkgn The imagemagick error was fixed sometime during imagemagick 7. In IM 6, I'll try to fix those syscall warnings, though if they ever return with an error something's probably pretty messed up and I'm not sure that there's much that can be done other than giving up. |
Also throw an exception on syscall errors
@spvkgn Try building again. I worked around the IM6 issue making a copy of the variable before calling |
This is such an awesome PR! I just compiled it at home and am in love with it. I've used a number of other album art scripts but this seems like the best solution. Since most of my album art files are named
It would be also awesome if this variable allowed unknown values like In the meantime, if I wanted to change where ncmpcpp looks for artwork, would all I have to change is lines 435-438 in src/screens/artwork.cpp ? |
@evanc577 This is looking pretty good. EDIT: Found it buried in the documentation :) I don't know how feasible something like this would be, or even if its possible within the limitations of ueberzug, curses, etc. but I've always wished for a preview artwork feature to go alongside the library view. Such as, if you highlight an artist then it would automatically recursively search for any cover.jpg/png/tiff/etc on the first track it finds and update the image displayed. Perhaps a more reasonable feature could be to implement dynamic color switching which utilizes something like Python's |
Added |
Hell yes thank you! Hope this badboy gets merged some day |
Ueberzug is not maintained anymore, so it would be useful to add support for Überzug++ replacement. |
ueberzug is being maintained again btw |
Display album art in terminal with Ueberzug.
Added a new "artwork" screen that can be resized and positioned using existing ncmpcpp functions.
mpd_music_dir
config value.cover.png
,cover.jpg
,cover.tiff
,cover.bmp
albumart_default_path
if it can't find a suitable image.ueberzug
executable must be visible in$PATH
, ncmpcpp spawns it as a background process.albumart_scaler
option. See Ueberzug documentation.Use
./configure --enable-artwork
flag to compile artwork support.PR draft thoughts
Some current limitations:
albumart
, libmpdclient has not implemented this functionality yet anyways.Only searches for "cover.jpg" in directory.cover.png
,cover.jpg
,cover.tiff
, andcover.bmp
.albumart_default_path
config option.albumart_scaler
option to customize look, values corresponding to Ueberzug's scaler options.If using split screens and a popup appears (such as when adding a song to a playlist), the artwork disappears and need to switch/reload screens to make it appear again.Configure script doesn't check whether Ueberzug is installed or not.Dirty hacks that probably need to go away:
Usesposix_spawn()
to start ueberzug, kills it on exit withstd::atexit()
andkill()
. If ncmpcpp crashes and terminates abnormally, the spawned process will not be terminated.Solved by fork-exec ueberzug directly and manually creating a pipe for ueberzug stdin.boost::process
which abstracts away a lot of the hard stuff...Ueberzug is hardcoded, should refactor so other viable backends can be easily added.Screenshots