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

[WIP] Album Art and Binary Responses #49

Closed
wants to merge 13 commits into from
12 changes: 11 additions & 1 deletion mopidy_mpd/dispatcher.py
Expand Up @@ -155,7 +155,11 @@ def _add_ok_filter(self, request, response, filter_chain):
return response

def _has_error(self, response):
return response and response[-1].startswith("ACK")
return (
response
and isinstance(response[-1], str)
and response[-1].startswith("ACK")
)

# Filter: call handler

Expand Down Expand Up @@ -239,6 +243,12 @@ class MpdContext:
#: The subsytems that we want to be notified about in idle mode.
subscriptions = None

#: The maximum binary chunk size
binary_limit = 1024 * 1024

#: A cache of the most recently sent artwork
art_cache = ("", bytes())

_uri_map = None

def __init__(
Expand Down
13 changes: 9 additions & 4 deletions mopidy_mpd/network.py
Expand Up @@ -494,8 +494,7 @@ def decode(self, line):
def join_lines(self, lines):
if not lines:
return ""
line_terminator = self.decode(self.terminator)
return line_terminator.join(lines) + line_terminator
return self.terminator.join(lines) + self.terminator

def send_lines(self, lines):
"""
Expand All @@ -507,5 +506,11 @@ def send_lines(self, lines):
if not lines:
return

data = self.join_lines(lines)
self.connection.queue_send(self.encode(data))
encoded_lines = []
for line in lines:
if not isinstance(line, bytes):
encoded_lines.append(self.encode(line))
else:
encoded_lines.append(line)

self.connection.queue_send(self.join_lines(encoded_lines))
9 changes: 9 additions & 0 deletions mopidy_mpd/protocol/connection.py
Expand Up @@ -51,3 +51,12 @@ def ping(context):
Does nothing but return ``OK``.
"""
pass


@protocol.commands.add("binarylimit")
def binarylimit(context, size):
"""
Set the maximum binary response size for the current connection to the
specified number of bytes.
"""
context.binary_limit = size
73 changes: 73 additions & 0 deletions mopidy_mpd/protocol/music_db.py
@@ -1,5 +1,8 @@
import functools
import itertools
import os.path
from urllib.request import Request, urlopen
import urllib.error

from mopidy.models import Track
from mopidy_mpd import exceptions, protocol, translator
Expand Down Expand Up @@ -84,6 +87,65 @@ def _artist_as_track(artist):
)


def _get_art(context, uri=None, offset=0):
# TODO work out how validators work and move these there
if uri is None:
raise exceptions.MpdArgError("Need to specify uri")
offset = protocol.INT(offset)

images = context.core.library.get_images([uri]).get()[uri]

if len(images) == 0:
raise exceptions.MpdNoExistError("No file exists")

image_uri = images[0].uri

if image_uri == context.art_cache[0]:
bytes = context.art_cache[1]
CMurtagh-LGTM marked this conversation as resolved.
Show resolved Hide resolved
else:
if image_uri.startswith("/"):
data_path = context.config["core"]["config_dir"]
_, extension, file = image_uri.split("/")
with open(
os.path.join(data_path, extension, "images", file), "rb"
) as image_file:
Comment on lines +109 to +111
Copy link
Member

Choose a reason for hiding this comment

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

what happens if the file doesn't exist or we don't have read permissions?

Copy link
Author

Choose a reason for hiding this comment

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

That is an excellent point, I'll worry about this once we decide on what we're doing.

bytes = image_file.read()
elif image_uri.startswith("https://") or image_uri.startswith("http://"):
try:
with urlopen(Request(image_uri)) as r:
bytes = r.read()
CMurtagh-LGTM marked this conversation as resolved.
Show resolved Hide resolved
except urllib.error.URLError as e:
raise exceptions.MpdArgError(f"There was an error with getting the uri, reason: {e.reason}")
else:
raise exceptions.MpdNotImplemented(
f"Cannot make sense of the uri {image_uri}"
)

context.art_cache = (image_uri, bytes)

if offset > len(bytes):
raise exceptions.MpdArgError("Offset too large")

return [
("size", len(bytes)),
("binary", len(bytes[offset : offset + context.binary_limit])),
bytes[offset : offset + context.binary_limit],
]
pass
CMurtagh-LGTM marked this conversation as resolved.
Show resolved Hide resolved


@protocol.commands.add("albumart")
def albumart(context, uri=None, offset=0):
"""
`albumart {URI} {OFFSET}`

Locate album art for the given song and return a chunk of an album art
image file at offset OFFSET.
"""
track = context.core.library.lookup([uri]).get()[uri]
return _get_art(context, track[0].album.uri, offset)


@protocol.commands.add("count")
def count(context, *args):
"""
Expand Down Expand Up @@ -548,3 +610,14 @@ def readcomments(context, uri):
support it. For example, on Ogg files, this lists the Vorbis comments.
"""
pass


@protocol.commands.add("readpicture")
def readpicture(context, uri=None, offset=0):
"""
`readpicture {URI} {OFFSET}`

Locate a picture for the given song and return a chunk of the image file at
offset OFFSET.
"""
return _get_art(context, uri, offset)
6 changes: 5 additions & 1 deletion mopidy_mpd/session.py
Expand Up @@ -36,7 +36,11 @@ def on_line_received(self, line):
logger.debug(
"Response to %s: %s",
self.connection,
formatting.indent(self.decode(self.terminator).join(response)),
formatting.indent(
self.decode(self.terminator).join(
[r for r in response if not isinstance(r, bytes)]
CMurtagh-LGTM marked this conversation as resolved.
Show resolved Hide resolved
)
),
)

self.send_lines(response)
Expand Down