Skip to content

Commit

Permalink
Merge pull request #4 from LokiLuciferase/feature/device-swap
Browse files Browse the repository at this point in the history
Feature/device swap
  • Loading branch information
LokiLuciferase authored May 12, 2024
2 parents 2a58284 + 359e7a4 commit f4a7455
Show file tree
Hide file tree
Showing 10 changed files with 160 additions and 28 deletions.
4 changes: 2 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ Quckstart
----------

At first we need to determine the name of the chromecast device we want to cast to. To do so, run ``lolcatt --scan``.
A default device and device aliases can be set in the ``catt`` configuration file ``~/.config/catt/config.cfg``. See catt_'s documentation for more information.
To start the UI, run ``lolcatt -d '<device name or alias>'`` (or simply ``lolcatt`` if a default device is set).
A default device and device aliases can be set in the ``catt`` configuration file (per default under ``~/.config/catt/config.cfg``), see catt_'s documentation for more information.

To cast, paste either a URL or a path to a local file into the input field and press enter. To add a URL or path to the playback queue instead of playing immediately, hit Ctrl+s instead of enter. To view and navigate in the queue, tap the name of the currently playing item. To seek, tap the progress bar.
To cast, paste either a URL or a path to a local file into the input field and press enter. To add a URL or path to the playback queue instead of playing immediately, hit Ctrl+s instead of enter. To view and navigate in the queue, tap the name of the currently playing item. To seek, tap the progress bar. To change chromecast device, tap the name of the currently active device (currently only devices with set aliases can be selected in this way).

For URLs, all websites supported by yt-dlp_ (which handles media download under the hood) are supported. Find a list of supported websites here_. For local media, most common video and image formats are supported.

Expand Down
2 changes: 1 addition & 1 deletion lolcatt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

__author__ = """Lukas Lüftinger"""
__email__ = '[email protected]'
__version__ = '0.4.1'
__version__ = '0.5.0'
11 changes: 11 additions & 0 deletions lolcatt/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from lolcatt.casting.caster import Caster
from lolcatt.ui.lolcatt_controls import LolCattControls
from lolcatt.ui.lolcatt_device_info import LolCattDeviceInfo
from lolcatt.ui.lolcatt_devicelist import LolCattDevicelist
from lolcatt.ui.lolcatt_playback_info import LolCattPlaybackInfo
from lolcatt.ui.lolcatt_playlist import LolCattPlaylist
from lolcatt.ui.lolcatt_progress import LolCattProgress
Expand Down Expand Up @@ -34,6 +35,13 @@ def compose(self):
yield LolCattPlaylist()


class DeviceScreen(Screen):
"""A screen for the device info UI."""

def compose(self):
yield LolCattDevicelist()


class LolCatt(App):
"""The main application class for lolcatt."""

Expand All @@ -44,13 +52,16 @@ def __init__(self, device_name: str = None, config: Dict = None, *args, **kwargs
self.caster = Caster(device_name, config=config)
self.remote_screen = None
self.playlist_screen = None
self.device_screen = None
super().__init__(*args, **kwargs)

def on_mount(self):
self.remote_screen = RemoteScreen()
self.playlist_screen = PlaylistScreen()
self.device_screen = DeviceScreen()
self.install_screen(self.remote_screen, name='remote')
self.install_screen(self.playlist_screen, name='playlist')
self.install_screen(self.device_screen, name='device')
self.push_screen('remote')


Expand Down
71 changes: 49 additions & 22 deletions lolcatt/casting/caster.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ def __init__(
self._queue = []
self._current_item = None
self._played_queue = []
self._well_known_devices = {}
self._available_devices = None
self._catt_call = None
self._catt_config = get_config_as_dict()
Expand All @@ -61,6 +62,7 @@ def __init__(
self._update_interval = update_interval
self._autoplay = autoplay
self._state_last_updated = time.time()
self._init_well_known_devices()
self.change_device(name_or_alias)

def _build_cast_args(self) -> List[str]:
Expand All @@ -78,6 +80,13 @@ def _build_cast_args(self) -> List[str]:

return catt_cast_args

def _init_well_known_devices(self):
for _, device_name in self._catt_config['aliases'].items():
try:
self._well_known_devices[device_name] = CattDevice(device_name)
except Exception:
pass

def cast(self, url_or_path: str):
"""
Casts the given url or path to the currently active device.
Expand Down Expand Up @@ -210,7 +219,15 @@ def get_played_queue(self) -> List[str]:
"""
return self._played_queue

def get_available_devices(self) -> List[str]:
def get_well_known_devices(self) -> Dict[str, CattDevice]:
"""
Returns well known devices which have aliases set.
:return: Well known devices.
"""
return self._well_known_devices

def get_available_devices(self) -> List[CattDevice]:
"""
Runs Chromecast discovery and returns a list of available CattDevices.
Expand All @@ -219,16 +236,13 @@ def get_available_devices(self) -> List[str]:
self._available_devices = discover()
return self._available_devices

def change_device(self, name_or_alias: str = None):
def _get_device_name(self, name_or_alias: Optional[str]) -> Optional[str]:
"""
Changes the currently active device to the given name or alias. If the device is not
available, a ValueError is raised.
:param name_or_alias: The name or alias of the device to change to.
Returns the device name for the given device name or alias.
"""
if name_or_alias == 'default':
self._device_name = self._catt_config['options'].get('device')
if self._device_name is None:
device_name = self._catt_config['options'].get('device')
if device_name is None:
print(
'No default device set. '
'Scanning for all available devices and picking first...'
Expand All @@ -239,25 +253,38 @@ def change_device(self, name_or_alias: str = None):
)
possible_devices = self.get_available_devices()
if len(possible_devices) > 0:
self._device_name = possible_devices[0].name
device_name = possible_devices[0].name
elif name_or_alias == 'None':
self._device_name = None
device_name = None
elif name_or_alias is not None:
self._device_name = self._catt_config['aliases'].get(name_or_alias, name_or_alias)
device_name = self._catt_config['aliases'].get(name_or_alias, name_or_alias)
else:
self._device_name = None
device_name = None
return device_name

if self._device_name is not None:
try:
self._device = CattDevice(self._device_name)
self._loading_started = None
self._is_loading_cast = False
except CastError:
print(f'Selected device "{self._device_name}" was not found on this network.')
print('Scan for available devices using "lolcatt --scan".')
raise ValueError(f'No device with name or alias "{name_or_alias}" found.')
def change_device(self, name_or_alias: Optional[str] = None):
"""
Changes the currently active device to the given name or alias. If the device is not
available, a ValueError is raised.
def get_device(self) -> CattDevice:
:param name_or_alias: The name or alias of the device to change to.
"""
self._device_name = self._get_device_name(name_or_alias)

if self._device_name is not None:
if self._device_name in self._well_known_devices:
self._device = self._well_known_devices[self._device_name]
else:
try:
self._device = CattDevice(self._device_name)
except CastError as e:
print(f'Selected device "{self._device_name}" was not found on this network.')
print('Scan for available devices using "lolcatt --scan".')
raise ValueError(f'No device with name or alias "{name_or_alias}" found: {e}')
self._loading_started = None
self._is_loading_cast = False

def get_device(self) -> Optional[CattDevice]:
"""
Returns the currently active CattDevice.
Expand Down
24 changes: 24 additions & 0 deletions lolcatt/ui/lolcatt.tcss
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,30 @@ LolCatUrlInput {
text-style: bold;
}

#devicelist_box {
layout: vertical;
align: center middle;
dock: top;
max-height: 18;
max-width: 41;
}

#devicelist_close_btn {
max-width: 5;
max-height: 3;
}

#devicelist > ListItem {
padding-top: 1;
padding-bottom: 1;
padding-left: 2;
padding-right: 2;
}

#active_device_listitem {
text-style: bold;
}

#app {
layout: vertical;
align: center middle;
Expand Down
3 changes: 3 additions & 0 deletions lolcatt/ui/lolcatt_device_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,8 @@ def on_mount(self):
interval=self.app.caster.get_update_interval(), callback=self._update_label
)

def on_click(self, event):
self.app.push_screen('device')

def compose(self):
yield Container(self.label, id='device_info')
63 changes: 63 additions & 0 deletions lolcatt/ui/lolcatt_devicelist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env python3
from catt.cli import get_config_as_dict
from textual import on
from textual.containers import Vertical
from textual.reactive import reactive
from textual.widgets import Button
from textual.widgets import Label
from textual.widgets import ListItem
from textual.widgets import ListView
from textual.widgets import Static


class LolCattDeviceListView(ListView):
def on_list_view_selected(self, selected):
try:
self.app.caster.change_device(selected.item.devicename)
self.app.pop_screen()
except AttributeError:
pass


class LolCattDevicelist(Static):

devices = reactive([])

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.listview = LolCattDeviceListView(
ListItem(Label('Scanning...')),
id="devicelist",
initial_index=None,
)
self.available_devices = list(get_config_as_dict().get("aliases", {}).values())
self.active_device = None
self.close_btn = Button("X", id="devicelist_close_btn")

def compose(self):
yield Vertical(self.listview, self.close_btn, id="devicelist_box")

def update_list(self):
new_device_name = self.app.caster.get_device_name()
if self.active_device == new_device_name:
return
self.active_device = new_device_name
self.listview.clear()
active_idx = None
for i, device in enumerate(self.available_devices):
if device == new_device_name:
id = "active_device_listitem"
active_idx = i
else:
id = f"device_listitem_{i}"
li_obj = ListItem(Label(device), id=id)
li_obj.devicename = device
self.listview.append(li_obj)
self.listview.index = active_idx

async def on_mount(self):
self.set_interval(1, self.update_list)

@on(Button.Pressed, "#devicelist_close_btn")
def on_close_btn(self):
self.app.pop_screen()
6 changes: 5 additions & 1 deletion lolcatt/ui/lolcatt_playlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@

class LolCattPlaybackListView(ListView):
def on_list_view_selected(self, selected):
self.app.caster.cast_at_idx(selected.item.idx)
try:
self.app.caster.cast_at_idx(selected.item.idx)
except AttributeError:
pass


class LolCattPlaylist(Static):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.listview = LolCattPlaybackListView(
ListItem(Label('Loading...')),
id="playlist",
initial_index=None,
)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ exclude = '''
'''

[tool.bumpver]
current_version = "0.4.1"
current_version = "0.5.0"
version_pattern = "MAJOR.MINOR.PATCH"
commit_message = "bump version {old_version} -> {new_version}"
commit = true
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,6 @@
test_suite='tests',
tests_require=test_requirements,
url='https://github.com/LokiLuciferase/lolcatt',
version='0.4.1',
version='0.5.0',
zip_safe=False,
)

0 comments on commit f4a7455

Please sign in to comment.