Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Casts a YouTube video to multiple Google Cast devices within a multicast domain.
#!/usr/bin/env python3
####################################################################################
# allcast_cli.py #
# Casts a specified YouTube video to Google Cast devices within a subdomain. [CLI] #
####################################################################################
from zeroconf import ServiceBrowser, Zeroconf
from time import sleep
from argparse import ArgumentParser
import re
import requests
from ipaddress import IPv4Address
parser = ArgumentParser(description='Cast a YouTube video to all specified Chromecast devices.')
parser.add_argument('video', help='YouTube video to be played. ex: E8y3eDUMb4Q')
parser.add_argument('--filter', '-f',
help='Regular expression (regex), to filter names to cast to (based upon mDNS txt fn)')
parser.add_argument('--duration', '-d', type=int,
help='Duration to continue scanning for castable devices and pushing the video. 0=Forever')
parser.add_argument('--sync', '-s', action='store_true',
help='Cast to all discovered devices simultaneously '
'when duration expires, instead of upon-discovery.')
args = parser.parse_args()
# Make sure duration is greater than 0, with a default value of 10
if not args.duration:
args.duration = 10
if args.duration < 0:
parser.error('Duration must be 0 or greater.')
# Make sure that if syncing is enabled, there is a duration set greater than 0
if args.sync:
if args.duration == 0:
parser.error('Cannot use sync option when duration is 0.')
# If we're using regex filters, make sure they're valid and compile them
if args.filter:
try:
filter_regex = re.compile(args.filter)
except re.error:
parser.error('Filter regex is invalid.')
class ServiceListener(object):
# Called when a service is discovered
def add_service(self, zeroconf, type, name):
info = zeroconf.get_service_info(type, name)
# fn: Device's human name
fn = str(info.properties[b'fn'])
# rs: Content currently casted by device
# rs = str(info.properties[b'rs'])
if not args.filter or (args.filter and filter_regex.search(fn)):
if not args.sync:
cast_to_device(name, info)
else:
save_to_cast_list(name, info)
else:
print(u"Ignoring {0} ({1}) -- {2}".format(name, IPv4Address(info.address), fn[2:-1]))
# Cast to a specified device
def cast_to_device(name, info):
fn = str(info.properties[b'fn'])
print(u"Casting to {0} ({1}) -- {2}".format(name, IPv4Address(info.address), fn[2:-1]))
data = {'v': args.video}
r = requests.post("http://{0}:8008/apps/YouTube".format(IPv4Address(info.address)), data=data)
# If response code falls in the 200 block, it's successful. Otherwise, print an error code.
if 200 > r.status_code >= 300:
print("{0} - {1}".format(r.status_code, r.reason))
# Save a device to a list for syncronized casting
def save_to_cast_list(name, info):
fn = str(info.properties[b'fn'])
print(u"Saving {0} ({1}) -- {2}".format(name, IPv4Address(info.address), fn[2:-1]))
cast_list.append((name, info))
# Start of main processing
cast_list = list()
zeroconf = Zeroconf()
ServiceBrowser(zeroconf, "_googlecast._tcp.local.", ServiceListener())
if args.duration > 0:
sleep(args.duration)
else:
input("Press Enter to exit.\n")
# If synchronizing, cast to all saved devices before closing
if args.sync:
for name, info in cast_list:
cast_to_device(name, info)
print('Closing...')
zeroconf.close()
@cetaSYN

This comment has been minimized.

Copy link
Owner Author

@cetaSYN cetaSYN commented Dec 2, 2019

python3 allcast_cli.py <Video Tag>
		YouTube video to be played (ex: dQw4w9WgXcQ)
	[--filter | -f <string>]
		mDNS txt fn regex(i.e. "Home-TV.*")
	[--duration | -d <int>]
		Duration in seconds to continue discovering & casting (0=Forever)
	[--sync | -s]
		When used, casts to all discovered devices simultaneously when duration expires.

Written to help pseudo-sync a video to all TVs in an office in early 2018.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment