Skip to content
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

Support album artwork ('albumart' command) and binary responses #57

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

leso-kn
Copy link

@leso-kn leso-kn commented Aug 12, 2022

Included in this PR:

  • Added support for Binary Responses
  • Added an implementation of the albumart command, making it possible to fetch album artwork for any media source that provides an artwork url
  • Support Binary Responses in network unit-tests

Example Screenshot

Example of album art displayed in home assistant

mopidy_mpd/dispatcher.py Outdated Show resolved Hide resolved
mopidy_mpd/network.py Outdated Show resolved Hide resolved
@leso-kn
Copy link
Author

leso-kn commented Aug 19, 2022

Updated Fixed linter warnings and added unit tests to cover protocol.album_art.

@leso-kn
Copy link
Author

leso-kn commented Sep 24, 2022

@djmattyg007 Test coverage is finalized, should now be ready to be merged :)

@gvc0
Copy link

gvc0 commented Dec 20, 2022

I tested this code an ran into a lot of issues. Problems are in the script album_art.py:

  1. Line 11: largest_image.uri returns a URI without a protocol prefix. Missing 'file://' I guess'. As such line 55 fails to open the URI (ValueError("unknown url type))
  2. Line 11: largest_image.uri returns a URI with a relative path instead of an absolute path. As such line 55 fails to open the URI (No such file or directory)
  3. Line 11: largest_image.uri returns a wrong URI. It returns /local/<art filename> while these files are stored under /local/images. As such line 55 fails to open the URI (No such file or directory). Note that /local/<art filename> is how the images are referred to in the SQLite database images field, but I guess you need to use a specific function to translates these database values into the appropriate absolute path (retrieved from the config)
  4. Line 49 context.core.playback.get_current_tl_track().get() will throw an error when the albumart function is called when the play queue is empty
  5. Line 49 context.core.playback.get_current_tl_track().get() always returns the image URI of the track that's currently playing, independent from the track requested by the client. When an MPD client tries to download all the album images in its library overview all albums in the library will get the image of the current track.

The good news is that when issues 1-3 are corrected the images are successfully downloaded via the MPD protocol. However, as explained in issue 5 the images are wrong when downloaded from a library list.

@gvc0
Copy link

gvc0 commented Dec 21, 2022

As a follow up on yesterdays comments I tried to improve the album_art.py code, see commit gvc0@59bb77c . It's working fine in Home Assistant (web GUI as well as mobile app). It now also works fine in MAFA, a solid and well maintained Android MPD client which offers more advanced MPD functionality than Home Assistant.

This is my first attempt at developing code for Mopidy so all feedback is welcome. I also would like to have some advice please on how to translate an image uri retrieved from the database to a uri type and absolute file path. For now I hard coded it, see line 69, as I couldn't figure out how to deal with this.

What I need is:
/local/e8fb302a1e79c5b6edf72d06ccbd1941-500x500.jpeg >>> file:///media/Data1/Mopidy/Data/local/images/e8fb302a1e79c5b6edf72d06ccbd1941-500x500.jpeg

Note: I also tested some other MPD clients and noticed most use uri's in their albumart requests in a different format than Mopidy uses. If we want to support all these variations we'll need to implement a request uri normalization function. I didn't get to it yet because I didn't find another MPD client yet worth the effort.

@gvc0
Copy link

gvc0 commented Dec 23, 2022

I finished my work on album_art.py and I think it's ready for a first release. Made a lot of improvements and tested with different clients, all looks fine. I created a pull requests (on branch @leso-kn). As I don't have any experience with Mopidy development and it's release process, please let me know if anything more is required from me.

@leso-kn
Copy link
Author

leso-kn commented Feb 15, 2023

Update: Added @gvc0's changes for file://-support and improved exception handling.

Note: ⚠️ There currently seems to be an issue with the Github Actions configuration of mopidy-mpd.
The code introduced by this PR (test_albumart.py respectively) is not affected by the issue and passes in all configured jobs (accessible when reviewing the pipeline logs). The remaining unit-tests succeed as well, when run in a clean (e.g. container) environment as described in !17.

@kingosticks
Copy link
Member

kingosticks commented Feb 16, 2023

Rebase on master for fixed tests. Sorry about that.

@leso-kn
Copy link
Author

leso-kn commented Feb 16, 2023

No worries, thanks for the quick fix :) Rebase done, all tests pass through Github Actions on my side now!
Should run through here now once triggered by a maintainer.

@kingosticks
Copy link
Member

I'll try and carve out some time to go through this in the next few days.

@kingosticks
Copy link
Member

I also would like to have some advice please on how to translate an image uri retrieved from the database to a uri type and absolute file path. For now I hard coded it, see line 69, as I couldn't figure out how to deal with this.

You want a reverse lookup? This isn't possible. This is/was the blocking problem IIRC. I'll have to look closer.

@gvc0
Copy link

gvc0 commented Feb 16, 2023

I also would like to have some advice please on how to translate an image uri retrieved from the database to a uri type and absolute file path. For now I hard coded it, see line 69, as I couldn't figure out how to deal with this.

You want a reverse lookup? This isn't possible. This is/was the blocking problem IIRC. I'll have to look closer.

Don't bother, I solved that already months ago (before I commented my code was finished). Nothing hard coded anymore, altough I think you might be able to improve my way of accessing Mopidy config.

@leso-kn
Copy link
Author

leso-kn commented Feb 16, 2023

Update: Coverage raised to 94% (patch coverage). Should pass codecov now.

@leso-kn leso-kn force-pushed the feature/album-art branch 3 times, most recently from c85fa74 to a0e10f9 Compare February 16, 2023 15:01
@leso-kn
Copy link
Author

leso-kn commented Feb 16, 2023

Update

  • Removed mopidy-local-integration for the moment as suggested in discussion 57#110856.
  • Coverage raised to 100% (patch coverage).

mopidy_mpd/protocol/album_art.py Outdated Show resolved Hide resolved
mopidy_mpd/protocol/album_art.py Outdated Show resolved Hide resolved
@leso-kn leso-kn force-pushed the feature/album-art branch 2 times, most recently from daad059 to 27e73d8 Compare February 16, 2023 20:36
@kingosticks
Copy link
Member

Do you want to open a new pull request for leso-kn:feature/album-art ?

@kingosticks
Copy link
Member

Oh, you force pushed it here, that's fine. ignore me

mopidy_mpd/protocol/album_art.py Outdated Show resolved Hide resolved
if art_url is None:
return b"binary: 0\n"

cover_cache[uri] = urlopen(art_url).read()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs error handling. And does it need to support a timeout, retries, proxy config. Lots to handle here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A core API makes more and more sense here. This is a whole world of pain to add to Mopidy-MPD and it's really the job of each backend to provide data. Would a get_images_data() API be useful to other backends, I don't know (but it's not a requirement, we can do it similarly to get_distinct). Can mpris provide image data (https://gitlab.freedesktop.org/mpris/mpris-spec/-/issues/17)? It might also be useful in the case where the backend can't provide publicly accessible images.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jodal @adamcik do you have any thoughts about what to here?

  • New core api get_image_data(uris) with an implementation for downloading over HTTP.
  • As above but as the default implementation for a matching backend extension API, allows backends to provide custom implementation I.e. Mopidy-Local would provide images directly
  • above functionality part of Mopidy-MPD, anything weird or more complicated is not supported.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One additional remark about my part of the code that was not accepted. I had foreseen a maximum (preferred) image size for images retrieved from -Local. The current code uses the largest images available (line 8), which is very inefficient (slow) for MPD clients that prefetch the images for all local albums. Ideally max preferred image size would be a config setting, in -MPD and/or -Local.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kingosticks I see. When I took recourse to urlopen in the initial implementation I indeed didn't quite consider that mopidy-mpd in general utilitizes calls to the core API for fetching data.

I believe your suggestion 2 sounds like the most clean way forward (new core api get_image_data(uris) / HTTP download per default with an option for custom implementations by backends which require so).

I am currently implementing your other feedback – thanks for the through code-review :) – I'll leave this part of the code as is for the moment, as it's likely that things will move to the core (I did however implement ACK_ERROR_NO_EXIST as suggested by your other above review).

Copy link
Author

@leso-kn leso-kn Feb 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A quick note aswell: I foreseeably won't be available during the upcoming week. I'll try to upload adjustments in response to your feedback before leaving as far as possible, leaving this very part out for now as described above.

mopidy_mpd/protocol/album_art.py Outdated Show resolved Hide resolved
mopidy_mpd/protocol/album_art.py Outdated Show resolved Hide resolved
mopidy_mpd/protocol/album_art.py Outdated Show resolved Hide resolved
Comment on lines +240 to +241
result = network.LineProtocol.join_lines(
self.mock, ["1".encode(), "2".encode()]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
result = network.LineProtocol.join_lines(
self.mock, ["1".encode(), "2".encode()]
assert b"1\n2\n" == network.LineProtocol.join_lines(
self.mock, [b"1", b"2"]

)
assert "1\n2\n".encode() == result
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
assert "1\n2\n".encode() == result

album=Album(uri="something:àlbum:12345"),
)
self.backend.library.dummy_library = [track]
self.core.tracklist.add(uris=[track.uri]).get()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to add to the tracklist and play the track in these tests?

return result


class AlbumArtTest(protocol.BaseTestCase):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs tests for the image width sorting, caching, and offset. And all the many ways urlopen can fail.

):
self.send_request("albumart file:///home/test/music.flac 0")

self.assertInResponse("binary: " + str(len(expected)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self.assertInResponse("binary: " + str(len(expected)))
self.assertInResponse("size: " + str(len(expected)))
self.assertInResponse("binary: " + str(len(expected)))
self.assertInResponse("OK")

Unfortunately, I think self.assertInResponse("result") is also going to be true. Which is wrong.... Maybe we need to fix the MockConnection so we work entirely in bytes when testing commands that provide binary responses?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants