Skip to content

Commit

Permalink
Use basic class instead of dict for app_info (skorokithakis#158)
Browse files Browse the repository at this point in the history
* Use namedtuples instead of dicts for app_info

* Clean up get_app_info a bit

* Use basic class instead of namedtuple

* Renaming spree (app_info to app)

* Types for CastController

* Remove redundant logic in get_app
  • Loading branch information
theychx authored Jan 22, 2019
1 parent d30f0db commit 536e7c2
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 61 deletions.
11 changes: 2 additions & 9 deletions catt/api.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
from .controllers import (
get_app_info,
get_chromecast,
get_chromecast_with_ip,
get_chromecasts,
get_controller,
get_stream,
)
from .controllers import get_app, get_chromecast, get_chromecast_with_ip, get_chromecasts, get_controller, get_stream


def discover() -> list:
Expand Down Expand Up @@ -57,7 +50,7 @@ def _create_cast(self) -> None:
self.uuid = self._cast.uuid

def _create_controller(self) -> None:
self._cast_controller = get_controller(self._cast, get_app_info("default"))
self._cast_controller = get_controller(self._cast, get_app("default"))

@property
def controller(self):
Expand Down
106 changes: 54 additions & 52 deletions catt/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,27 @@
from .stream_info import StreamInfo
from .util import warning

APP_INFO = [
{"app_name": "youtube", "app_id": "233637DE", "supported_device_types": ["cast"]},
{"app_name": "dashcast", "app_id": DASHCAST_APP_ID, "supported_device_types": ["cast", "audio"]},
]
DEFAULT_APP = {"app_name": "default", "app_id": "CC1AD845"}
BACKDROP_APP_ID = "E8C28D3C"
NO_PLAYER_STATE_IDS = ["84912283"]
DEVICES_WITH_TWO_MODEL_NAMES = {"Eureka Dongle": "Chromecast"}
DEFAULT_PORT = 8009
VALID_STATE_EVENTS = ["UNKNOWN", "IDLE", "BUFFERING", "PLAYING", "PAUSED"]


class App:
def __init__(self, app_name, app_id, supported_device_types):
self.name = app_name
self.id = app_id
self.supported_device_types = supported_device_types


APPS = [
App(app_name="youtube", app_id="233637DE", supported_device_types=["cast"]),
App(app_name="dashcast", app_id=DASHCAST_APP_ID, supported_device_types=["cast", "audio"]),
]
DEFAULT_APP = App(app_name="default", app_id="CC1AD845", supported_device_types=["cast", "audio", "group"])


def get_chromecasts():
devices = pychromecast.get_chromecasts()
devices.sort(key=lambda cc: cc.name)
Expand Down Expand Up @@ -85,40 +94,35 @@ def get_stream(url, device_info=None, host=None, ytdl_options=None):
return StreamInfo(url, host=host, model=cc_info, device_type=cast_type, ytdl_options=ytdl_options)


def get_app_info(id_or_name, cast_type=None, strict=False, show_warning=False):
if id_or_name == "default":
return DEFAULT_APP

field = "app_id" if re.match("[0-9A-F]{8}$", id_or_name) else "app_name"
def get_app(id_or_name, cast_type=None, strict=False, show_warning=False):
arg_is_id = True if re.match("[0-9A-F]{8}$", id_or_name) else False
try:
app_info = next(a for a in APP_INFO if a[field] == id_or_name)
app = next(a for a in APPS if (a.id if arg_is_id else a.name) == id_or_name)
except StopIteration:
if strict:
raise AppSelectionError("app not found (strict is set)")
else:
app_info = DEFAULT_APP

if app_info["app_name"] != "default":
if not cast_type:
raise AppSelectionError("cast_type is needed for app selection")
elif cast_type not in app_info["supported_device_types"]:
msg = "The %s app is not available for this device." % app_info["app_name"].capitalize()
if strict:
raise CattCastError(msg)
elif show_warning:
warning(msg)
app_info = DEFAULT_APP
return app_info


# I'm not sure it serves any purpose to have get_app_info and get_controller
return DEFAULT_APP

if not cast_type:
raise AppSelectionError("cast_type is needed for app selection")
elif cast_type not in app.supported_device_types:
msg = "The %s app is not available for this device." % app.name.capitalize()
if strict:
raise CattCastError(msg)
elif show_warning:
warning(msg)
return DEFAULT_APP
return app


# I'm not sure it serves any purpose to have get_app and get_controller
# as two separate functions. I'll probably merge them at some point.
def get_controller(cast, app_info, action=None, prep=None):
app_name = app_info["app_name"]
controller = {"youtube": YoutubeCastController, "dashcast": DashCastController}.get(app_name, DefaultCastController)
def get_controller(cast, app, action=None, prep=None):
controller = {"youtube": YoutubeCastController, "dashcast": DashCastController}.get(app.name, DefaultCastController)
if action and action not in dir(controller):
raise CattCastError("This action is not supported by the %s controller." % app_name)
return controller(cast, app_name, app_info["app_id"], prep=prep)
raise CattCastError("This action is not supported by the %s controller." % app.name)
return controller(cast, app, prep=prep)


def setup_cast(device_name, video_url=None, controller=None, ytdl_options=None, action=None, prep=None):
Expand All @@ -129,23 +133,23 @@ def setup_cast(device_name, video_url=None, controller=None, ytdl_options=None,
)

if controller:
app_info = get_app_info(controller, cast_type, strict=True)
app = get_app(controller, cast_type, strict=True)
elif prep == "app":
if stream:
if stream.is_local_file:
app_info = get_app_info("default")
app = get_app("default")
else:
app_info = get_app_info(stream.extractor, cast_type, show_warning=True)
app = get_app(stream.extractor, cast_type, show_warning=True)
else:
app_info = get_app_info("default")
app = get_app("default")
else:
# cast.app_id can be None, in the case of an inactive audio device.
if cast.app_id:
app_info = get_app_info(cast.app_id, cast_type)
app = get_app(cast.app_id, cast_type)
else:
app_info = get_app_info("default")
app = get_app("default")

cast_controller = get_controller(cast, app_info, action=action, prep=prep)
cast_controller = get_controller(cast, app, action=action, prep=prep)
return (cast_controller, stream) if stream else cast_controller


Expand Down Expand Up @@ -329,18 +333,18 @@ def wait_for_states(self, timeout=None):


class CastController:
def __init__(self, cast, name, app_id, prep=None):
def __init__(self, cast: pychromecast.Chromecast, app: App, prep: Optional[str] = None) -> None:
self._cast = cast
self.name = name
self.name = app.name
self.info_type = None
self.save_capability = None
self.playlist_capability = None

self._cast_listener = CastStatusListener(app_id, self._cast.app_id)
self._cast_listener = CastStatusListener(app.id, self._cast.app_id)
self._cast.register_status_listener(self._cast_listener)

try:
self._cast.register_handler(self._controller)
self._cast.register_handler(self._controller) # type: ignore
except AttributeError:
self._controller = self._cast.media_controller

Expand Down Expand Up @@ -525,12 +529,10 @@ def restore(self, data):


class DefaultCastController(CastController, MediaControllerMixin, PlaybackBaseMixin):
def __init__(self, cast, name, app_id, prep=None):
super(DefaultCastController, self).__init__(cast, name, app_id, prep=prep)
def __init__(self, cast, app, prep=None):
super(DefaultCastController, self).__init__(cast, app, prep=prep)
self.info_type = "url"
self.save_capability = (
"complete" if (self._is_seekable and self._cast.app_id == DEFAULT_APP["app_id"]) else None
)
self.save_capability = "complete" if (self._is_seekable and self._cast.app_id == DEFAULT_APP.id) else None

def play_media_url(self, video_url, **kwargs):
content_type = kwargs.get("content_type") or "video/mp4"
Expand All @@ -551,9 +553,9 @@ def restore(self, data):


class DashCastController(CastController):
def __init__(self, cast, name, app_id, prep=None):
def __init__(self, cast, app, prep=None):
self._controller = PyChromecastDashCastController()
super(DashCastController, self).__init__(cast, name, app_id, prep=prep)
super(DashCastController, self).__init__(cast, app, prep=prep)

def load_url(self, url, **kwargs):
self._controller.load_url(url, force=True)
Expand All @@ -568,9 +570,9 @@ def prep_app(self):


class YoutubeCastController(CastController, MediaControllerMixin, PlaybackBaseMixin):
def __init__(self, cast, name, app_id, prep=None):
def __init__(self, cast, app, prep=None):
self._controller = YouTubeController()
super(YoutubeCastController, self).__init__(cast, name, app_id, prep=prep)
super(YoutubeCastController, self).__init__(cast, app, prep=prep)
self.info_type = "id"
self.save_capability = "partial"
self.playlist_capability = "complete"
Expand Down

0 comments on commit 536e7c2

Please sign in to comment.