mirror of
https://github.com/Burningstone91/smart-home-setup.git
synced 2022-05-05 21:16:50 +03:00
Replace MPD integration with Mopidy Custom Component and add configuration for bedside remote controls
This commit is contained in:
1
home-assistant/custom_components/mopidy/__init__.py
Executable file
1
home-assistant/custom_components/mopidy/__init__.py
Executable file
@@ -0,0 +1 @@
|
||||
"""The mopidy component."""
|
||||
4
home-assistant/custom_components/mopidy/const.py
Executable file
4
home-assistant/custom_components/mopidy/const.py
Executable file
@@ -0,0 +1,4 @@
|
||||
"""Constants for the Mopidy integration."""
|
||||
|
||||
DOMAIN = "mopidy"
|
||||
ICON = "mdi:music-note"
|
||||
8
home-assistant/custom_components/mopidy/manifest.json
Executable file
8
home-assistant/custom_components/mopidy/manifest.json
Executable file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"domain": "mopidy",
|
||||
"name": "Mopidy",
|
||||
"documentation": "https://www.home-assistant.io/integrations/mopidy",
|
||||
"requirements": ["mopidyapi==1.0.0"],
|
||||
"codeowners": ["@bushvin"]
|
||||
}
|
||||
|
||||
824
home-assistant/custom_components/mopidy/media_player.py
Executable file
824
home-assistant/custom_components/mopidy/media_player.py
Executable file
@@ -0,0 +1,824 @@
|
||||
"""Support to interact with a MopidyMusic Server."""
|
||||
import logging
|
||||
from mopidyapi import MopidyAPI
|
||||
from requests.exceptions import ConnectionError as reConnectionError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.components.media_player import (
|
||||
BrowseMedia,
|
||||
MediaPlayerEntity,
|
||||
PLATFORM_SCHEMA,
|
||||
)
|
||||
from homeassistant.components.media_player.errors import BrowseError
|
||||
import homeassistant.util.dt as dt_util
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.media_player.const import (
|
||||
MEDIA_CLASS_ALBUM,
|
||||
MEDIA_CLASS_ARTIST,
|
||||
MEDIA_CLASS_COMPOSER,
|
||||
MEDIA_CLASS_DIRECTORY,
|
||||
MEDIA_CLASS_GENRE,
|
||||
MEDIA_CLASS_MUSIC,
|
||||
MEDIA_CLASS_PLAYLIST,
|
||||
MEDIA_CLASS_PODCAST,
|
||||
MEDIA_CLASS_TRACK,
|
||||
MEDIA_CLASS_URL,
|
||||
MEDIA_TYPE_ALBUM,
|
||||
MEDIA_TYPE_ARTIST,
|
||||
MEDIA_TYPE_EPISODE,
|
||||
MEDIA_TYPE_MUSIC,
|
||||
MEDIA_TYPE_PLAYLIST,
|
||||
MEDIA_TYPE_TRACK,
|
||||
REPEAT_MODE_ALL,
|
||||
REPEAT_MODE_OFF,
|
||||
REPEAT_MODE_ONE,
|
||||
SUPPORT_BROWSE_MEDIA,
|
||||
SUPPORT_CLEAR_PLAYLIST,
|
||||
SUPPORT_NEXT_TRACK,
|
||||
SUPPORT_PAUSE,
|
||||
SUPPORT_PLAY,
|
||||
SUPPORT_PLAY_MEDIA,
|
||||
SUPPORT_PREVIOUS_TRACK,
|
||||
SUPPORT_REPEAT_SET,
|
||||
SUPPORT_SEEK,
|
||||
SUPPORT_SELECT_SOURCE,
|
||||
SUPPORT_SHUFFLE_SET,
|
||||
SUPPORT_STOP,
|
||||
SUPPORT_TURN_OFF,
|
||||
SUPPORT_TURN_ON,
|
||||
SUPPORT_VOLUME_MUTE,
|
||||
SUPPORT_VOLUME_SET,
|
||||
SUPPORT_VOLUME_STEP,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_PORT,
|
||||
CONF_NAME,
|
||||
STATE_IDLE,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_PLAYING,
|
||||
STATE_PAUSED,
|
||||
STATE_OFF,
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
ICON,
|
||||
)
|
||||
|
||||
SUPPORT_MOPIDY = (
|
||||
SUPPORT_BROWSE_MEDIA
|
||||
| SUPPORT_CLEAR_PLAYLIST
|
||||
| SUPPORT_NEXT_TRACK
|
||||
| SUPPORT_PAUSE
|
||||
| SUPPORT_PLAY
|
||||
| SUPPORT_PLAY_MEDIA
|
||||
| SUPPORT_PREVIOUS_TRACK
|
||||
| SUPPORT_REPEAT_SET
|
||||
| SUPPORT_SHUFFLE_SET
|
||||
| SUPPORT_SEEK
|
||||
| SUPPORT_STOP
|
||||
| SUPPORT_TURN_OFF
|
||||
| SUPPORT_TURN_ON
|
||||
| SUPPORT_SELECT_SOURCE
|
||||
| SUPPORT_VOLUME_MUTE
|
||||
| SUPPORT_VOLUME_SET
|
||||
| SUPPORT_VOLUME_STEP
|
||||
)
|
||||
|
||||
MEDIA_TYPE_SHOW = "show"
|
||||
|
||||
PLAYABLE_MEDIA_TYPES = [
|
||||
MEDIA_TYPE_PLAYLIST,
|
||||
MEDIA_TYPE_ALBUM,
|
||||
MEDIA_TYPE_ARTIST,
|
||||
MEDIA_TYPE_EPISODE,
|
||||
MEDIA_TYPE_SHOW,
|
||||
MEDIA_TYPE_TRACK,
|
||||
]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
DEFAULT_NAME = "Mopidy"
|
||||
DEFAULT_PORT = 6680
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
||||
}
|
||||
)
|
||||
|
||||
CACHE_ROSETTA = {}
|
||||
|
||||
|
||||
class MissingMediaInformation(BrowseError):
|
||||
"""Missing media required information."""
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass: HomeAssistant, config: ConfigEntry, async_add_entities, discover_info=None
|
||||
):
|
||||
"""Set up the Mopidy platform."""
|
||||
host = config.get(CONF_HOST)
|
||||
port = config.get(CONF_PORT)
|
||||
name = config.get(CONF_NAME)
|
||||
|
||||
entity = MopidyMediaPlayerEntity(host, port, name)
|
||||
async_add_entities([entity], True)
|
||||
|
||||
|
||||
class MopidyMediaPlayerEntity(MediaPlayerEntity):
|
||||
"""Representation of the Mopidy server."""
|
||||
|
||||
def __init__(self, hostname, port, name):
|
||||
"""Initialize the Mopidy device."""
|
||||
self.hostname = hostname
|
||||
self.port = port
|
||||
self.device_name = name
|
||||
|
||||
self.server_version = None
|
||||
self.player_currenttrack = None
|
||||
self.player_currenttrach_source = None
|
||||
|
||||
self._media_position = None
|
||||
self._media_position_updated_at = None
|
||||
self._state = STATE_UNKNOWN
|
||||
self._volume = None
|
||||
self._muted = None
|
||||
self._media_image_url = None
|
||||
self._shuffled = None
|
||||
self._repeat_mode = None
|
||||
self._playlists = []
|
||||
self._currentplaylist = None
|
||||
|
||||
self.client = None
|
||||
self._available = None
|
||||
|
||||
def _fetch_status(self):
|
||||
"""Fetch status from Mopidy."""
|
||||
_LOGGER.debug("Fetching Mopidy Server status for %s" % self.device_name)
|
||||
self.player_currenttrack = self.client.playback.get_current_track()
|
||||
if hasattr(self.player_currenttrack, "uri"):
|
||||
self.player_currenttrach_source = self.player_currenttrack.uri.partition(
|
||||
":"
|
||||
)[0]
|
||||
else:
|
||||
self.player_currenttrach_source = None
|
||||
|
||||
media_position = int(self.client.playback.get_time_position() / 1000)
|
||||
if media_position != self._media_position:
|
||||
self._media_position = media_position
|
||||
self._media_position_updated_at = dt_util.utcnow()
|
||||
|
||||
state = self.client.playback.get_state()
|
||||
if state is None:
|
||||
self._state = STATE_UNAVAILABLE
|
||||
elif state == "playing":
|
||||
self._state = STATE_PLAYING
|
||||
elif state == "paused":
|
||||
self._state = STATE_PAUSED
|
||||
elif state == "stopped":
|
||||
self._state = STATE_OFF
|
||||
else:
|
||||
self._state = STATE_UNKNOWN
|
||||
|
||||
self._volume = float(self.client.mixer.get_volume() / 100)
|
||||
self._muted = self.client.mixer.get_mute()
|
||||
|
||||
if hasattr(self.player_currenttrack, "uri"):
|
||||
res = self.client.library.get_images([self.player_currenttrack.uri])
|
||||
if (
|
||||
self.player_currenttrack.uri in res
|
||||
and len(res[self.player_currenttrack.uri]) > 0
|
||||
and hasattr(res[self.player_currenttrack.uri][0], "uri")
|
||||
):
|
||||
self._media_image_url = res[self.player_currenttrack.uri][0].uri
|
||||
if self.player_currenttrach_source == "local":
|
||||
self._media_image_url = "http://%s:%s%s" % (
|
||||
self.hostname,
|
||||
self.port,
|
||||
self._media_image_url,
|
||||
)
|
||||
else:
|
||||
self._media_image_url = None
|
||||
|
||||
self._shuffled = self.client.tracklist.get_random()
|
||||
self._playlists = self.client.playlists.as_list()
|
||||
|
||||
repeat = self.client.tracklist.get_repeat()
|
||||
single = self.client.tracklist.get_single()
|
||||
if repeat and single:
|
||||
self._repeat_mode = REPEAT_MODE_ONE
|
||||
elif repeat:
|
||||
self._repeat_mode = REPEAT_MODE_ALL
|
||||
else:
|
||||
self._repeat_mode = REPEAT_MODE_OFF
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the name of the entity."""
|
||||
return self.device_name
|
||||
|
||||
@property
|
||||
def icon(self) -> str:
|
||||
"""Return the icon."""
|
||||
return ICON
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return True if entity is available."""
|
||||
return self._available
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the media state."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def volume_level(self):
|
||||
"""Volume level of the media player (0..1)."""
|
||||
return self._volume
|
||||
|
||||
@property
|
||||
def is_volume_muted(self):
|
||||
"""Boolean if volume is currently muted."""
|
||||
return self._muted
|
||||
|
||||
@property
|
||||
def media_content_id(self):
|
||||
"""Return the content ID of current playing media."""
|
||||
if hasattr(self.player_currenttrack, "uri"):
|
||||
return self.player_currenttrack.uri
|
||||
return None
|
||||
|
||||
@property
|
||||
def media_content_type(self):
|
||||
"""Content type of current playing media."""
|
||||
return MEDIA_TYPE_MUSIC
|
||||
|
||||
@property
|
||||
def media_duration(self):
|
||||
"""Duration of current playing media in seconds."""
|
||||
if hasattr(self.player_currenttrack, "length"):
|
||||
return int(self.player_currenttrack.length / 1000)
|
||||
return None
|
||||
|
||||
@property
|
||||
def media_position(self):
|
||||
"""Position of current playing media in seconds."""
|
||||
return self._media_position
|
||||
|
||||
@property
|
||||
def media_position_updated_at(self):
|
||||
"""Last valid time of media position."""
|
||||
return self._media_position_updated_at
|
||||
|
||||
@property
|
||||
def media_image_url(self):
|
||||
"""Image url of current playing media."""
|
||||
return self._media_image_url
|
||||
|
||||
@property
|
||||
def media_image_remotely_accessible(self):
|
||||
"""If the image url is remotely accessible."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def media_title(self):
|
||||
"""Return the title of current playing media."""
|
||||
if hasattr(self.player_currenttrack, "name"):
|
||||
return self.player_currenttrack.name
|
||||
return None
|
||||
|
||||
@property
|
||||
def media_artist(self):
|
||||
"""Artist of current playing media, music track only."""
|
||||
if hasattr(self.player_currenttrack, "artists"):
|
||||
return ", ".join([a.name for a in self.player_currenttrack.artists])
|
||||
return None
|
||||
|
||||
@property
|
||||
def media_album_name(self):
|
||||
"""Album name of current playing media, music track only."""
|
||||
if hasattr(self.player_currenttrack, "album") and hasattr(
|
||||
self.player_currenttrack.album, "name"
|
||||
):
|
||||
return self.player_currenttrack.album.name
|
||||
return None
|
||||
|
||||
@property
|
||||
def media_album_artist(self):
|
||||
"""Album artist of current playing media, music track only."""
|
||||
if hasattr(self.player_currenttrack, "artists"):
|
||||
return ", ".join([a.name for a in self.player_currenttrack.artists])
|
||||
return None
|
||||
|
||||
@property
|
||||
def media_track(self):
|
||||
"""Track number of current playing media, music track only."""
|
||||
if hasattr(self.player_currenttrack, "track_no"):
|
||||
return self.player_currenttrack.track_no
|
||||
return None
|
||||
|
||||
@property
|
||||
def media_playlist(self):
|
||||
"""Title of Playlist currently playing."""
|
||||
if self._currentplaylist is not None:
|
||||
return self._currentplaylist
|
||||
|
||||
if hasattr(self.player_currenttrack, "album") and hasattr(
|
||||
self.player_currenttrack.album, "name"
|
||||
):
|
||||
return self.player_currenttrack.album.name
|
||||
|
||||
return None
|
||||
|
||||
@property
|
||||
def shuffle(self):
|
||||
"""Boolean if shuffle is enabled."""
|
||||
return self._shuffled
|
||||
|
||||
@property
|
||||
def repeat(self):
|
||||
"""Return current repeat mode."""
|
||||
return self._repeat_mode
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Flag media player features that are supported."""
|
||||
return SUPPORT_MOPIDY
|
||||
|
||||
@property
|
||||
def source(self):
|
||||
"""Name of the current input source."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def source_list(self):
|
||||
"""Return the list of available input sources."""
|
||||
return [el.name for el in self._playlists]
|
||||
|
||||
def mute_volume(self, mute):
|
||||
"""Mute the volume."""
|
||||
self.client.mixer.set_mute(mute)
|
||||
|
||||
def set_volume_level(self, volume):
|
||||
"""Set volume level, range 0..1."""
|
||||
self.client.mixer.set_volume(int(volume * 100))
|
||||
|
||||
def turn_off(self):
|
||||
"""Turn the media player off."""
|
||||
self.client.playback.stop()
|
||||
|
||||
def turn_on(self):
|
||||
"""Turn the media player on."""
|
||||
self.client.playback.play()
|
||||
|
||||
def media_play(self):
|
||||
"""Send play command."""
|
||||
self.client.playback.play()
|
||||
|
||||
def media_pause(self):
|
||||
"""Send pause command."""
|
||||
self.client.playback.pause()
|
||||
|
||||
def media_stop(self):
|
||||
"""Send stop command."""
|
||||
self.client.playback.stop()
|
||||
|
||||
def media_previous_track(self):
|
||||
"""Send previous track command."""
|
||||
self.client.playback.previous()
|
||||
|
||||
def media_next_track(self):
|
||||
"""Send next track command."""
|
||||
self.client.playback.next()
|
||||
|
||||
def media_seek(self, position):
|
||||
"""Send seek command."""
|
||||
self.client.playback.seek(int(position * 1000))
|
||||
|
||||
def play_media(self, media_type, media_id, **kwargs):
|
||||
"""Play a piece of media."""
|
||||
self._currentplaylist = None
|
||||
if media_type == MEDIA_TYPE_PLAYLIST:
|
||||
p = self.client.playlists.lookup(media_id)
|
||||
self._currentplaylist = p.name
|
||||
if media_id.partition(":")[0] == "m3u":
|
||||
media_uris = [t.uri for t in p.tracks]
|
||||
else:
|
||||
media_uris = [media_id]
|
||||
else:
|
||||
media_uris = [media_id]
|
||||
|
||||
if len(media_uris) > 0:
|
||||
self.client.tracklist.clear()
|
||||
self.client.tracklist.add(uris=media_uris)
|
||||
self.client.playback.play()
|
||||
else:
|
||||
_LOGGER.error(
|
||||
"No media for %s (%s) could be found." % (media_id, media_type)
|
||||
)
|
||||
raise MissingMediaInformation
|
||||
|
||||
def select_source(self, source):
|
||||
"""Select input source."""
|
||||
for el in self._playlists:
|
||||
if el.name == source:
|
||||
self.play_media(MEDIA_TYPE_PLAYLIST, el.uri)
|
||||
return el.uri
|
||||
raise ValueError("Could not find %s" % source)
|
||||
|
||||
def clear_playlist(self):
|
||||
"""Clear players playlist."""
|
||||
self.client.tracklist.clear()
|
||||
|
||||
def set_shuffle(self, shuffle):
|
||||
"""Enable/disable shuffle mode."""
|
||||
self.client.tracklist.set_random(shuffle)
|
||||
|
||||
def set_repeat(self, repeat):
|
||||
"""Set repeat mode."""
|
||||
if repeat == REPEAT_MODE_ALL:
|
||||
self.client.tracklist.set_repeat(True)
|
||||
self.client.tracklist.set_single(False)
|
||||
elif repeat == REPEAT_MODE_ONE:
|
||||
self.client.tracklist.set_repeat(True)
|
||||
self.client.tracklist.set_single(True)
|
||||
else:
|
||||
self.client.tracklist.set_repeat(False)
|
||||
self.client.tracklist.set_single(False)
|
||||
|
||||
def volume_up(self):
|
||||
"""Turn volume up for media player."""
|
||||
new_volume = self.client.mixer.get_volume() + 5
|
||||
if new_volume > 100:
|
||||
new_volume = 100
|
||||
|
||||
self.client.mixer.set_volume(new_volume)
|
||||
|
||||
def volume_down(self):
|
||||
"""Turn volume down for media player."""
|
||||
new_volume = self.client.mixer.get_volume() - 5
|
||||
if new_volume < 0:
|
||||
new_volume = 0
|
||||
|
||||
self.client.mixer.set_volume(new_volume)
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the device class"""
|
||||
return "speaker"
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return device information about this entity."""
|
||||
return {
|
||||
"indentifiers": {(DOMAIN, self.device_name)},
|
||||
"manufacturer": "Mopidy",
|
||||
"model": "Mopidy server %s" % self.server_version,
|
||||
"name": self.device_name,
|
||||
"sw_version": self.server_version,
|
||||
}
|
||||
|
||||
def _connect(self):
|
||||
try:
|
||||
self.client = MopidyAPI(
|
||||
host=self.hostname, port=self.port, use_websocket=False
|
||||
)
|
||||
self.server_version = self.client.rpc_call("core.get_version")
|
||||
_LOGGER.debug(
|
||||
"Connection to Mopidy server %s (%s:%s) established"
|
||||
% (self.device_name, self.hostname, self.port)
|
||||
)
|
||||
except reConnectionError as error:
|
||||
_LOGGER.error(
|
||||
"Cannot connect to %s @ %s:%s"
|
||||
% (self.device_name, self.hostname, self.port)
|
||||
)
|
||||
_LOGGER.error(error)
|
||||
self._available = False
|
||||
return
|
||||
self._available = True
|
||||
|
||||
def update(self):
|
||||
"""Get the latest data and update the state."""
|
||||
if self._available is None:
|
||||
self._connect()
|
||||
|
||||
if self._available:
|
||||
self._fetch_status()
|
||||
|
||||
async def async_browse_media(self, media_content_type=None, media_content_id=None):
|
||||
"""Implement the websocket media browsing helper."""
|
||||
|
||||
return await self.hass.async_add_executor_job(
|
||||
self._media_library_payload,
|
||||
dict(
|
||||
media_content_type=(
|
||||
"library" if media_content_type is None else media_content_type
|
||||
),
|
||||
media_content_id=(
|
||||
"library" if media_content_id is None else media_content_id
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
def _media_item_image_url(self, source, url):
|
||||
"""Return the correct url to the item's thumbnail."""
|
||||
|
||||
if source == "local":
|
||||
url = "http://%s:%d%s" % (
|
||||
self.hostname,
|
||||
self.port,
|
||||
url,
|
||||
)
|
||||
|
||||
url = "%s?t=x" % url
|
||||
return url
|
||||
|
||||
def _media_library_payload(self, payload):
|
||||
"""Create response payload to describe contents of a specific library."""
|
||||
source = None
|
||||
|
||||
try:
|
||||
payload["media_content_type"]
|
||||
payload["media_content_id"]
|
||||
except KeyError as err:
|
||||
_LOGGER.error("Missing type or uri for media item payload: %s", payload)
|
||||
raise MissingMediaInformation from err
|
||||
|
||||
library_info = dict(
|
||||
media_class=MEDIA_CLASS_DIRECTORY,
|
||||
media_content_id="library",
|
||||
media_content_type="library",
|
||||
title="Media Library",
|
||||
can_play=False,
|
||||
can_expand=True,
|
||||
children=[],
|
||||
)
|
||||
library_info.update(payload)
|
||||
|
||||
if payload["media_content_id"] == "library":
|
||||
library_items = self.client.library.browse(None)
|
||||
library_info["title"] = "Media Library"
|
||||
else:
|
||||
source = payload["media_content_id"].partition(":")[0]
|
||||
library_items = self.client.library.browse(payload["media_content_id"])
|
||||
extra_info = fetch_media_info(
|
||||
payload["media_content_type"], payload["media_content_id"]
|
||||
)
|
||||
library_info.update(extra_info)
|
||||
image = self.client.library.get_images([payload["media_content_id"]])
|
||||
if (
|
||||
payload["media_content_id"] in image
|
||||
and len(image[payload["media_content_id"]]) > 0
|
||||
):
|
||||
library_info["thumbnail"] = self._media_item_image_url(
|
||||
source, image[payload["media_content_id"]][0].uri
|
||||
)
|
||||
|
||||
children_uri = []
|
||||
for item in library_items:
|
||||
library_info["children"].append(
|
||||
self._media_item_payload(
|
||||
{"name": item.name, "type": item.type, "uri": item.uri}
|
||||
)
|
||||
)
|
||||
children_uri.append(item.uri)
|
||||
|
||||
if source == "spotify":
|
||||
# Spotify thumbnail lookup is throttled
|
||||
s = 10
|
||||
else:
|
||||
s = 1000
|
||||
uri_set = [
|
||||
children_uri[r * s : (r + 1) * s]
|
||||
for r in range((len(children_uri) + s - 1) // s)
|
||||
]
|
||||
|
||||
images = dict()
|
||||
for s in uri_set:
|
||||
images.update(self.client.library.get_images(s))
|
||||
|
||||
if len(images.keys()) > 0:
|
||||
for item in library_info["children"]:
|
||||
if (
|
||||
item.media_content_id in images
|
||||
and len(images[item.media_content_id]) > 0
|
||||
):
|
||||
item.thumbnail = self._media_item_image_url(
|
||||
source, images[item.media_content_id][0].uri
|
||||
)
|
||||
|
||||
library_info["can_play"] = (
|
||||
library_info["media_content_type"] in PLAYABLE_MEDIA_TYPES
|
||||
)
|
||||
|
||||
if payload["media_content_id"] in CACHE_ROSETTA:
|
||||
library_info["title"] = CACHE_ROSETTA[payload["media_content_id"]]
|
||||
|
||||
r = BrowseMedia(**library_info)
|
||||
try:
|
||||
r.children_media_class = extra_info["children_media_class"]
|
||||
except:
|
||||
r.children_media_class = MEDIA_CLASS_DIRECTORY
|
||||
return r
|
||||
|
||||
def _media_item_payload(self, item):
|
||||
try:
|
||||
media_type = item["type"]
|
||||
media_id = item["uri"]
|
||||
except KeyError as err:
|
||||
_LOGGER.error("Missing type or uri for media item: %s", item)
|
||||
raise MissingMediaInformation from err
|
||||
|
||||
media_class = MEDIA_CLASS_DIRECTORY
|
||||
can_expand = media_type not in [MEDIA_TYPE_TRACK]
|
||||
can_play = media_type in PLAYABLE_MEDIA_TYPES
|
||||
|
||||
payload = dict(
|
||||
media_class=media_class,
|
||||
media_content_id=media_id,
|
||||
media_content_type=media_type,
|
||||
can_play=can_play,
|
||||
can_expand=can_expand,
|
||||
title=item.get("name"),
|
||||
)
|
||||
CACHE_ROSETTA[media_id] = payload["title"]
|
||||
extra_info = fetch_media_info(media_type, media_id)
|
||||
payload.update(extra_info)
|
||||
return BrowseMedia(**payload)
|
||||
|
||||
|
||||
def fetch_media_info(
|
||||
media_type=None,
|
||||
media_uri=None,
|
||||
defaults={},
|
||||
):
|
||||
"""Fetch addional media information which is not available through the mopidy API"""
|
||||
|
||||
res = dict()
|
||||
res["media_class"] = defaults.get("media_class")
|
||||
res["children_media_class"] = defaults.get("children_media_class")
|
||||
|
||||
if media_type is None or media_uri is None:
|
||||
return res
|
||||
|
||||
source = media_uri.partition(":")[0]
|
||||
uri = media_uri.partition(":")[2]
|
||||
|
||||
if False:
|
||||
_LOGGER.debug("media_type: %s" % media_type)
|
||||
_LOGGER.debug("media_uri: %s" % media_uri)
|
||||
_LOGGER.debug("source: %s" % source)
|
||||
_LOGGER.debug("uri: %s" % uri)
|
||||
|
||||
if media_type == "directory":
|
||||
res["media_class"] = MEDIA_CLASS_DIRECTORY
|
||||
res["children_media_class"] = MEDIA_CLASS_DIRECTORY
|
||||
|
||||
elif media_type == "album":
|
||||
res["media_class"] = MEDIA_CLASS_ALBUM
|
||||
res["children_media_class"] = MEDIA_CLASS_TRACK
|
||||
|
||||
elif media_type == "artist":
|
||||
res["media_class"] = MEDIA_CLASS_ARTIST
|
||||
res["children_media_class"] = MEDIA_CLASS_TRACK
|
||||
|
||||
elif media_type == "track":
|
||||
res["media_class"] = MEDIA_CLASS_TRACK
|
||||
res["children_media_class"] = MEDIA_CLASS_TRACK
|
||||
|
||||
elif media_type == "playlist":
|
||||
res["media_class"] = MEDIA_CLASS_PLAYLIST
|
||||
res["children_media_class"] = MEDIA_CLASS_TRACK
|
||||
|
||||
if source == "local":
|
||||
media_info = {}
|
||||
for el in uri.partition("?")[2].split("&"):
|
||||
if el != "":
|
||||
media_info[el.partition("=")[0]] = el.partition("=")[2]
|
||||
|
||||
if media_type == "directory" and media_info.get("type") == "album":
|
||||
res["title"] = "Albums"
|
||||
res["media_class"] = MEDIA_CLASS_ALBUM
|
||||
res["children_media_class"] = MEDIA_CLASS_ALBUM
|
||||
|
||||
elif media_type == "directory" and media_info.get("type") == "artist":
|
||||
res["media_class"] = MEDIA_CLASS_ARTIST
|
||||
res["children_media_class"] = MEDIA_CLASS_ARTIST
|
||||
if media_info.get("role") == "composer":
|
||||
res["title"] = "Composer"
|
||||
res["media_class"] = MEDIA_CLASS_COMPOSER
|
||||
res["children_media_class"] = MEDIA_CLASS_COMPOSER
|
||||
elif media_info.get("role") == "performer":
|
||||
res["title"] = "Performer"
|
||||
else:
|
||||
res["title"] = "Artists"
|
||||
|
||||
elif (
|
||||
media_type == "directory"
|
||||
and media_info.get("composer") is not None
|
||||
and media_info.get("album") is not None
|
||||
):
|
||||
res["media_class"] = MEDIA_CLASS_ALBUM
|
||||
res["children_media_class"] = MEDIA_CLASS_TRACK
|
||||
|
||||
elif media_type == "directory" and media_info.get("composer") is not None:
|
||||
res["media_class"] = MEDIA_CLASS_COMPOSER
|
||||
res["children_media_class"] = MEDIA_CLASS_ALBUM
|
||||
|
||||
elif (
|
||||
media_type == "directory"
|
||||
and media_info.get("genre") is not None
|
||||
and media_info.get("album") is not None
|
||||
):
|
||||
res["media_class"] = MEDIA_CLASS_ALBUM
|
||||
res["children_media_class"] = MEDIA_CLASS_TRACK
|
||||
|
||||
elif media_type == "directory" and media_info.get("genre") is not None:
|
||||
res["media_class"] = MEDIA_CLASS_GENRE
|
||||
res["children_media_class"] = MEDIA_CLASS_ALBUM
|
||||
|
||||
elif media_type == "directory" and media_info.get("type") == "genre":
|
||||
res["title"] = "Genres"
|
||||
res["media_class"] = MEDIA_CLASS_GENRE
|
||||
res["children_media_class"] = MEDIA_CLASS_GENRE
|
||||
|
||||
elif (
|
||||
media_type == "directory"
|
||||
and media_info.get("type") == "track"
|
||||
and media_info.get("album") is not None
|
||||
):
|
||||
res["media_class"] = MEDIA_CLASS_ALBUM
|
||||
res["children_media_class"] = MEDIA_CLASS_TRACK
|
||||
|
||||
elif media_type == "directory" and media_info.get("type") == "track":
|
||||
res["media_class"] = MEDIA_CLASS_TRACK
|
||||
res["children_media_class"] = MEDIA_CLASS_TRACK
|
||||
|
||||
elif media_type == "artist":
|
||||
res["media_class"] = MEDIA_CLASS_ARTIST
|
||||
res["children_media_class"] = MEDIA_CLASS_ALBUM
|
||||
|
||||
elif source == "spotify":
|
||||
if media_type == "directory" and uri == "top:tracks:countries":
|
||||
res["media_class"] = MEDIA_CLASS_DIRECTORY
|
||||
res["children_media_class"] = MEDIA_CLASS_DIRECTORY
|
||||
|
||||
elif media_type == "directory" and "top:tracks:" in uri:
|
||||
res["media_class"] = MEDIA_CLASS_DIRECTORY
|
||||
res["children_media_class"] = MEDIA_CLASS_TRACK
|
||||
|
||||
elif media_type == "directory" and uri in [
|
||||
"top:albums",
|
||||
"top:albums:countries",
|
||||
]:
|
||||
res["media_class"] = MEDIA_CLASS_DIRECTORY
|
||||
res["children_media_class"] = MEDIA_CLASS_ALBUM
|
||||
|
||||
elif media_type == "directory" and "top:albums:" in uri:
|
||||
res["media_class"] = MEDIA_CLASS_DIRECTORY
|
||||
res["children_media_class"] = MEDIA_CLASS_ALBUM
|
||||
|
||||
elif media_type == "directory" and uri in [
|
||||
"top:artists",
|
||||
"top:artists:countries",
|
||||
]:
|
||||
res["media_class"] = MEDIA_CLASS_ARTIST
|
||||
res["children_media_class"] = MEDIA_CLASS_ARTIST
|
||||
|
||||
elif media_type == "directory" and "top:artists:" in uri:
|
||||
res["media_class"] = MEDIA_CLASS_ARTIST
|
||||
res["children_media_class"] = MEDIA_CLASS_ARTIST
|
||||
|
||||
elif media_type == "directory" and uri == "your:tracks":
|
||||
res["media_class"] = MEDIA_CLASS_DIRECTORY
|
||||
res["children_media_class"] = MEDIA_CLASS_TRACK
|
||||
|
||||
elif media_type == "directory" and uri == "your:albums":
|
||||
res["media_class"] = MEDIA_CLASS_DIRECTORY
|
||||
res["children_media_class"] = MEDIA_CLASS_ALBUM
|
||||
|
||||
elif media_type == "directory" and uri == "top":
|
||||
res["title"] = "Top Lists"
|
||||
|
||||
elif media_type == "directory" and uri == "directory":
|
||||
res["title"] = "Spotify"
|
||||
|
||||
elif source[:7] == "podcast":
|
||||
if media_type == "album":
|
||||
res["media_class"] = MEDIA_CLASS_DIRECTORY
|
||||
res["children_media_class"] = MEDIA_CLASS_PODCAST
|
||||
|
||||
elif media_type == "track":
|
||||
res["media_class"] = MEDIA_CLASS_PODCAST
|
||||
res["children_media_class"] = MEDIA_CLASS_PODCAST
|
||||
|
||||
# _LOGGER.debug("res: %s" % res)
|
||||
return res
|
||||
@@ -1,8 +1,14 @@
|
||||
# media_player:
|
||||
# - platform: mpd
|
||||
# name: MPD Bedroom
|
||||
# host: 192.168.0.30
|
||||
# port: 6601
|
||||
|
||||
media_player:
|
||||
- platform: mpd
|
||||
- platform: mopidy
|
||||
name: MPD Bedroom
|
||||
host: 192.168.0.30
|
||||
port: 6601
|
||||
port: 6681
|
||||
|
||||
binary_sensor:
|
||||
- platform: template
|
||||
@@ -77,6 +83,40 @@ automation:
|
||||
remote: remote_light_bedroom
|
||||
light: light.bedroom
|
||||
|
||||
# Remote Sabrina
|
||||
- id: 'remote_sabrina'
|
||||
alias: "Fernbedieung Sabrina"
|
||||
use_blueprint:
|
||||
path: custom/hue_dimmer_general.yaml
|
||||
input:
|
||||
remote: remote_sabrina
|
||||
short_press_turn_on:
|
||||
- service: light.turn_on
|
||||
entity_id: light.bedroom_bed
|
||||
long_press_turn_on:
|
||||
- service: light.turn_on
|
||||
entity_id: light.bedroom_ceiling
|
||||
short_press_turn_off:
|
||||
- service: script.turn_on
|
||||
entity_id: script.turn_all_off
|
||||
|
||||
# Remote Dimitri
|
||||
- id: 'remote_dimitri'
|
||||
alias: "Fernbedienung Dimitri"
|
||||
use_blueprint:
|
||||
path: custom/hue_dimmer_general.yaml
|
||||
input:
|
||||
remote: remote_dimitri
|
||||
short_press_turn_on:
|
||||
- service: light.turn_on
|
||||
entity_id: light.bedroom_bed
|
||||
long_press_turn_on:
|
||||
- service: light.turn_on
|
||||
entity_id: light.bedroom_ceiling
|
||||
short_press_turn_off:
|
||||
- service: script.turn_on
|
||||
entity_id: script.turn_all_off
|
||||
|
||||
# Entity Customization
|
||||
homeassistant:
|
||||
customize:
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
# media_player:
|
||||
# - platform: mpd
|
||||
# name: MPD Livingroom
|
||||
# host: 192.168.0.30
|
||||
# port: 6602
|
||||
|
||||
media_player:
|
||||
- platform: mpd
|
||||
- platform: mopidy
|
||||
name: MPD Livingroom
|
||||
host: 192.168.0.30
|
||||
port: 6602
|
||||
port: 6682
|
||||
|
||||
emulated_roku:
|
||||
servers:
|
||||
@@ -21,7 +27,7 @@ binary_sensor:
|
||||
device_class: occupancy
|
||||
value_template: >-
|
||||
{% set motion = is_state('binary_sensor.motion_livingroom', 'on') %}
|
||||
{% set tv = is_state('media_player.receiver_livingroom', 'on') %}
|
||||
{% set tv = is_state('remote.wohnzimmer', 'on') %}
|
||||
{{ motion or tv }}
|
||||
delay_off:
|
||||
minutes: 10
|
||||
@@ -118,4 +124,4 @@ homeassistant:
|
||||
sensor.battery_level_temperature_livingroom:
|
||||
friendly_name: Temperatur Sensor Wohnzimmer
|
||||
sensor.battery_level_remote_light_livingroom:
|
||||
friendly_name: Remote Licht Wohnzimmer
|
||||
friendly_name: Remote Licht Wohnzimmer
|
||||
|
||||
@@ -2,11 +2,17 @@
|
||||
# media_player:
|
||||
# platform: yamaha_musiccast
|
||||
# host: 10.10.80.7
|
||||
# media_player:
|
||||
# - platform: mpd
|
||||
# name: MPD Office
|
||||
# host: 192.168.0.30
|
||||
# port: 6603
|
||||
|
||||
media_player:
|
||||
- platform: mpd
|
||||
- platform: mopidy
|
||||
name: MPD Office
|
||||
host: 192.168.0.30
|
||||
port: 6603
|
||||
port: 6683
|
||||
|
||||
binary_sensor:
|
||||
- platform: template
|
||||
|
||||
Reference in New Issue
Block a user