|
#!/usr/bin/env python3 |
|
|
|
from argparse import ArgumentParser, Namespace |
|
import socket |
|
from time import sleep |
|
from typing import List, Optional, Type, TypeVar |
|
from urllib.parse import urljoin |
|
|
|
import pychromecast |
|
from pychromecast.controllers.dashcast import DashCastController |
|
|
|
T = TypeVar("T") |
|
|
|
def parse_args() -> Namespace: |
|
parser = ArgumentParser(description="stream the screen of your Android device to a Chromecast") |
|
parser.add_argument("--url", type=str, help="the URL ScreenStream is displaying (only needed when the app is running on a different device than this script)") # TODO: type=url? |
|
parser.add_argument("--pin", type=int, help="the PIN ScreenStream is displaying (if you do not specify this, you will be asked for it)") |
|
parser.add_argument("--cast_name", type=str, help="the name of your Chromecast (if you do not specify this, you will be presented a list of available devices to select from)") |
|
# TODO: --cast_ip? |
|
args = parser.parse_args() |
|
return args |
|
|
|
|
|
def ask(what: str, choose_from: Optional[List] = None, type: Type[T] = int) -> T: |
|
if choose_from is None: |
|
while True: |
|
inp = input(f"Please enter {what}: ") |
|
try: |
|
return type(inp) |
|
except ValueError: |
|
print(f"'{inp}' is not a valid {type.__name__}.") |
|
else: # TODO: selection via arrow keys |
|
assert type == int |
|
print(f"Please select a {what} from the following options: (0-{len(choose_from)})") |
|
for idx, name in enumerate(choose_from): |
|
print(f"{idx}. {name}") |
|
while True: |
|
inp = input("> ") |
|
try: |
|
idx = int(inp) |
|
except ValueError: |
|
print(f"'{inp}' is not a valid int.") |
|
else: |
|
if 0 <= idx < len(choose_from): |
|
return idx # correct, but mypy didn't notice |
|
else: |
|
print(f"'{inp}' is not in the range [0, {len(choose_from)-1}].") |
|
|
|
|
|
def determine_url() -> str: |
|
# Try to find our primary IP address. |
|
# That is of course the one with which we would connect to Google. |
|
# Also, just do IPv4 here. |
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) |
|
sock.connect(("8.8.8.8", 1)) # This doesn't have to work. |
|
ip = sock.getsockname()[0] |
|
sock.close() |
|
# Just assume that ScreenStream listens on port 8080. |
|
return f"http://{ip}:8080/" |
|
|
|
|
|
def find_chromecast(name: Optional[str] = None) -> pychromecast.Chromecast: |
|
casts = pychromecast.get_chromecasts() |
|
if name: |
|
for cc in casts: |
|
if name == cc.device.friendly_name: |
|
return cc |
|
else: |
|
print(f"Device {name} not found.") |
|
return casts[ask("Chromecast", [cc.device.friendly_name for cc in casts])] |
|
|
|
|
|
if __name__ == "__main__": |
|
args = parse_args() |
|
|
|
pin = args.pin or ask("PIN", type=int) |
|
url_to_device = args.url or determine_url() |
|
url = urljoin(url_to_device, f"/?pin={pin}") |
|
cast = find_chromecast(args.cast_name) |
|
print(f"Going to open {url} on {cast.device.friendly_name}.") |
|
# TODO: Check whether this URL actually works. |
|
|
|
cast.wait() # wait for connection |
|
dc = DashCastController() |
|
cast.register_handler(dc) |
|
dc.load_url(url) |
|
try: |
|
while True: |
|
sleep(1000) |
|
except KeyboardInterrupt: |
|
cast.quit_app() |