Пример #1
0
class MrpMetadata(Metadata):
    """Implementation of API for retrieving metadata."""
    def __init__(self, protocol, psm, identifier):
        """Initialize a new MrpPlaying."""
        super().__init__(identifier)
        self.protocol = protocol
        self.psm = psm
        self.artwork_cache = Cache(limit=4)

    async def artwork(self):
        """Return artwork for what is currently playing (or None)."""
        identifier = self.artwork_id
        if not identifier:
            _LOGGER.debug("No artwork available")
            return None

        if identifier in self.artwork_cache:
            _LOGGER.debug("Retrieved artwork %s from cache", identifier)
            return self.artwork_cache.get(identifier)

        artwork = await self._fetch_artwork()
        if artwork:
            self.artwork_cache.put(identifier, artwork)
            return artwork

        return None

    async def _fetch_artwork(self):
        playing = self.psm.playing
        resp = await self.psm.protocol.send_and_receive(
            messages.playback_queue_request(playing.location))
        if not resp.HasField("type"):
            return None

        item = resp.inner().playbackQueue.contentItems[playing.location]
        return ArtworkInfo(item.artworkData, playing.metadata.artworkMIMEType)

    @property
    def artwork_id(self):
        """Return a unique identifier for current artwork."""
        metadata = self.psm.playing.metadata
        if metadata and metadata.artworkAvailable:
            if metadata.HasField("artworkIdentifier"):
                return metadata.artworkIdentifier
            if metadata.HasField("contentIdentifier"):
                return metadata.contentIdentifier
        return None

    async def playing(self):
        """Return what is currently playing."""
        return MrpPlaying(self.psm.playing)

    @property
    def app(self) -> Optional[App]:
        """Return information about running app."""
        state = self.psm.playing
        if state.client:
            name = state.client.displayName or None
            return App(name, state.client.bundleIdentifier)
        return None
Пример #2
0
class DmapMetadata(Metadata):
    """Implementation of API for retrieving metadata from an Apple TV."""

    def __init__(self, identifier, apple_tv):
        """Initialize metadata instance."""
        self.identifier = identifier
        self.apple_tv = apple_tv
        self.artwork_cache = Cache(limit=4)

    @property
    def device_id(self) -> Optional[str]:
        """Return a unique identifier for current device."""
        return self.identifier

    async def artwork(
        self, width: Optional[int] = 512, height: Optional[int] = None
    ) -> Optional[ArtworkInfo]:
        """Return artwork for what is currently playing (or None).

        The parameters "width" and "height" makes it possible to request artwork of a
        specific size. This is just a request, the device might impose restrictions and
        return artwork of a different size. Set both parameters to None to request
        default size. Set one of them and let the other one be None to keep original
        aspect ratio.
        """
        # Having to fetch "playing" here is not ideal, but an identifier is
        # needed and we cannot trust any previous identifiers. So we have to do
        # this until a better solution comes along.
        playing = await self.playing()
        identifier = playing.hash
        if identifier in self.artwork_cache:
            _LOGGER.debug("Retrieved artwork %s from cache", identifier)
            return self.artwork_cache.get(identifier)

        _LOGGER.debug("Fetching artwork")
        artwork = await self.apple_tv.artwork(width, height)
        if artwork:
            info = ArtworkInfo(bytes=artwork, mimetype="image/png", width=-1, height=-1)
            self.artwork_cache.put(identifier, info)
            return info

        return None

    @property
    def artwork_id(self):
        """Return a unique identifier for current artwork."""
        return self.apple_tv.latest_hash

    async def playing(self):
        """Return current device state."""
        return await self.apple_tv.playstatus()

    @property
    def app(self) -> Optional[App]:
        """Return information about running app."""
        raise exceptions.NotSupportedError()
Пример #3
0
class DmapMetadata(Metadata):
    """Implementation of API for retrieving metadata from an Apple TV."""
    def __init__(self, identifier, apple_tv):
        """Initialize metadata instance."""
        super().__init__(identifier)
        self.apple_tv = apple_tv
        self.artwork_cache = Cache(limit=4)

    async def artwork(self):
        """Return artwork for what is currently playing (or None)."""
        # Having to fetch "playing" here is not ideal, but an identifier is
        # needed and we cannot trust any previous identifiers. So we have to do
        # this until a better solution comes along.
        playing = await self.playing()
        identifier = playing.hash
        if identifier in self.artwork_cache:
            _LOGGER.debug("Retrieved artwork %s from cache", identifier)
            return self.artwork_cache.get(identifier)

        artwork = await self._fetch_artwork()
        if artwork:
            self.artwork_cache.put(identifier, artwork)
            return artwork

        return None

    async def _fetch_artwork(self):
        _LOGGER.debug("Fetching artwork")
        data = await self.apple_tv.artwork()
        if data:
            return ArtworkInfo(data, "image/png")
        return None

    @property
    def artwork_id(self):
        """Return a unique identifier for current artwork."""
        return self.apple_tv.latest_hash

    async def playing(self):
        """Return current device state."""
        return await self.apple_tv.playstatus()

    @property
    def app(self) -> Optional[App]:
        """Return information about running app."""
        raise exceptions.NotSupportedError()
Пример #4
0
 def __init__(self, protocol, psm, identifier):
     """Initialize a new MrpPlaying."""
     super().__init__(identifier)
     self.protocol = protocol
     self.psm = psm
     self.artwork_cache = Cache(limit=4)
Пример #5
0
class MrpMetadata(Metadata):
    """Implementation of API for retrieving metadata."""
    def __init__(self, protocol, psm, identifier):
        """Initialize a new MrpPlaying."""
        super().__init__(identifier)
        self.protocol = protocol
        self.psm = psm
        self.artwork_cache = Cache(limit=4)

    async def artwork(self, width=512, height=None) -> Optional[ArtworkInfo]:
        """Return artwork for what is currently playing (or None).

        The parameters "width" and "height" makes it possible to request artwork of a
        specific size. This is just a request, the device might impose restrictions and
        return artwork of a different size. Set both parameters to None to request
        default size. Set one of them and let the other one be None to keep original
        aspect ratio.
        """
        identifier = self.artwork_id
        if not identifier:
            _LOGGER.debug("No artwork available")
            return None

        if identifier in self.artwork_cache:
            _LOGGER.debug("Retrieved artwork %s from cache", identifier)
            return self.artwork_cache.get(identifier)

        artwork = await self._fetch_artwork(width or 0, height or -1)
        if artwork:
            self.artwork_cache.put(identifier, artwork)
            return artwork

        return None

    async def _fetch_artwork(self, width, height):
        playing = self.psm.playing
        resp = await self.psm.protocol.send_and_receive(
            messages.playback_queue_request(playing.location, width, height))
        if not resp.HasField("type"):
            return None

        item = resp.inner().playbackQueue.contentItems[playing.location]
        return ArtworkInfo(
            bytes=item.artworkData,
            mimetype=playing.metadata.artworkMIMEType,
            width=item.artworkDataWidth,
            height=item.artworkDataHeight,
        )

    @property
    def artwork_id(self):
        """Return a unique identifier for current artwork."""
        metadata = self.psm.playing.metadata
        if metadata and metadata.artworkAvailable:
            if metadata.HasField("artworkIdentifier"):
                return metadata.artworkIdentifier
            if metadata.HasField("contentIdentifier"):
                return metadata.contentIdentifier
            return self.psm.playing.item_identifier
        return None

    async def playing(self):
        """Return what is currently playing."""
        return MrpPlaying(copy(self.psm.playing))

    @property
    def app(self) -> Optional[App]:
        """Return information about running app."""
        player_path = self.psm.playing.player_path
        if player_path and player_path.client:
            return App(player_path.client.displayName,
                       player_path.client.bundleIdentifier)
        return None
Пример #6
0
 def __init__(self, identifier, apple_tv):
     """Initialize metadata instance."""
     super().__init__(identifier)
     self.apple_tv = apple_tv
     self.artwork_cache = Cache(limit=4)
Пример #7
0
 def setUp(self):
     self.cache = Cache(limit=2)
Пример #8
0
class CacheTest(unittest.TestCase):
    def setUp(self):
        self.cache = Cache(limit=2)

    def test_cache_is_empty(self):
        self.assertTrue(self.cache.empty())

    def test_put_get_item(self):
        self.cache.put(ID1, DATA1)
        self.assertEqual(self.cache.get(ID1), DATA1)

    def test_put_get_multiple(self):
        self.cache.put(ID1, DATA1)
        self.cache.put(ID2, DATA2)

        self.assertEqual(self.cache.get(ID1), DATA1)
        self.assertEqual(self.cache.get(ID2), DATA2)

    def test_cache_not_empty(self):
        self.cache.put(ID1, DATA1)
        self.assertFalse(self.cache.empty())

    def test_cache_has_item(self):
        self.cache.put(ID1, DATA1)

        self.assertTrue(ID1 in self.cache)
        self.assertFalse(ID2 in self.cache)

    def test_cache_size(self):
        self.assertEqual(len(self.cache), 0)
        self.cache.put(ID1, DATA1)
        self.assertEqual(len(self.cache), 1)

    def test_put_same_identifier_replaces_data(self):
        self.cache.put(ID1, DATA1)
        self.cache.put(ID1, DATA2)
        self.assertEqual(self.cache.get(ID1), DATA2)
        self.assertEqual(len(self.cache), 1)

    def test_put_removes_oldest(self):
        self.cache.put(ID1, DATA1)
        self.cache.put(ID2, DATA2)
        self.cache.put(ID3, DATA3)

        self.assertEqual(len(self.cache), 2)
        self.assertNotIn(ID1, self.cache)
        self.assertIn(ID2, self.cache)
        self.assertIn(ID3, self.cache)

    def test_get_makes_data_newer(self):
        self.cache.put(ID1, DATA1)
        self.cache.put(ID2, DATA2)
        self.cache.get(ID1)
        self.cache.put(ID3, DATA3)

        self.assertEqual(len(self.cache), 2)
        self.assertIn(ID1, self.cache)
        self.assertNotIn(ID2, self.cache)
        self.assertIn(ID3, self.cache)

    def test_get_latest_identifier(self):
        self.assertEqual(self.cache.latest(), None)

        self.cache.put(ID1, DATA1)
        self.assertEqual(self.cache.latest(), ID1)

        self.cache.put(ID2, DATA2)
        self.assertEqual(self.cache.latest(), ID2)

        self.cache.get(ID1)
        self.assertEqual(self.cache.latest(), ID1)
Пример #9
0
def cache():
    yield Cache(limit=2)