#!/usr/bin/perl # # Simple script to allow your Apple TV to play: # * Broadcasts from uitzendinggemist.nl # * Live streams Journaal24/Politiek24 # * Any other stream URL # # Usage: # atv.pl http://nos.nl/nieuws/live/journaal24/ # atv.pl http://www.uitzendinggemist.nl/afleveringen/1248403 # atv.pl http://download.omroep.nl/vara/tv/dewerelddraaitdoor/20120413-dwdd_tvdd.mp4 # # -- Eric Jansen # ### Set this to the hostname/IP of your Apple TV my $APPLE_TV = 'apple-tv'; use strict; use warnings; use Digest::MD5 'md5_hex'; use LWP::UserAgent; use MIME::Base64; use Net::HTTP; $|=1; my $url = $ARGV[0] or die "Argument to this script must be a webpage/stream"; if ($url =~ /\.(?:gif|jpg|png)$/i) { # Send the picture to the Apple TV aTVphoto($APPLE_TV, $url); } else { if ($url !~ /\.(mp4|m3u8)$/i) { # Parse URL, replace known pages by the stream URL if ($url =~ 'http://www.uitzendinggemist.nl/afleveringen/(\d+)') { $url = getUzgStream($1); } elsif ($url =~ 'http://(?:www.)?nos.nl/.*journaal24') { $url = 'http://livestreams.omroep.nl/live/npo/thematv/journaal24/journaal24.isml/journaal24.m3u8'; } elsif ($url =~ 'http://(?:www.)?nos.nl/.*politiek24') { $url = 'http://livestreams.omroep.nl/live/npo/thematv/politiek24/politiek24.isml/politiek24.m3u8'; } else { $url = getGenericStream($url); } } # Send the stream URL to the Apple TV aTVplay($APPLE_TV, $url); } # # Display a picture on the Apple TV # sub aTVphoto { my ($atv, $url) = @_; my $ua = new LWP::UserAgent; $ua->agent('Mozilla/5.0'); my $sock = Net::HTTP->new( Host => $atv, PeerPort => 7000, KeepAlive => 1) or die("Error: Could not connect to Apple TV on $atv"); print "Displaying picture on Apple TV, press CTRL+C to stop...\n"; while (1) { my $picture = $ua->get($url); if ($picture->is_success) { $sock->write_request(PUT => '/photo', 'User-Agent' => 'MediaControl/1.0', $picture->decoded_content); sleep 30; } else { die 'Problem retrieving requested URL'; } } close($sock); } # # Send a stream URL to the Apple TV # sub aTVplay { my ($atv, $url) = @_; my $sock = Net::HTTP->new( Host => $atv, PeerPort => 7000, KeepAlive => 1) or die("Error: Could not connect to Apple TV on $atv"); $sock->write_request(POST => '/play', 'User-Agent' => 'MediaControl/1.0', "Content-Location: $url\nStart-Position: 0\n"); my ($status, $message, %headers) = $sock->read_response_headers; die "Error: Apple TV responded: $status, $message" if $status != 200; print "Streaming video to Apple TV, press CTRL+C to stop...\n"; aTVwait($sock); close($sock); } # # Show progress while playing # sub aTVwait { my $sock = shift; while (1) { $sock->write_request(GET => '/scrub', 'User-Agent' => 'MediaControl/1.0'); my ($status, $message, %headers) = $sock->read_response_headers; die "Error: Apple TV responded: $status, $message" if $status != 200; my $data; $sock->read_entity_body($data, 1024); if ($data =~ /duration: ([\d\.]+)\r?\nposition: ([\d\.]+)/m) { progressBar($2, $1, 50); } sleep 1; } } # # Format progress bar # sub progressBar { my ($pos, $tot, $width) = @_; $width -= 15; my ($n1, $n2) = (0, $width); if ($tot > 0) { # use int (x + 0.5) for round(x) $n1 = int($pos/$tot * $width + 0.5); $n2 = int((1 - $pos/$tot) * $width + 0.5); } # 0:38 [======> ] 2:14 printf("\r%2d:%02d [%s>%s] %2d:%02d", int($pos/60), int($pos%60), "=" x $n1, " " x $n2, int($tot/60), int($tot%60)); } # # Uitzendinggemist.nl is slighly complicated, get the correct stream URL # sub getUzgStream { my $ua = new LWP::UserAgent; $ua->agent('Mozilla/5.0'); my $id = shift; my $page = $ua->get("http://www.uitzendinggemist.nl/afleveringen/$id"); my $episode; if ($page->is_success && $page->decoded_content =~ /episodeID=(\w+)/) { $episode = $1; } else { die 'Problem retrieving episode page from uitzendinggemist.nl'; } my @secret; my $security = $ua->get('http://pi.omroep.nl/info/security/'); if ($security->is_success && $security->decoded_content =~ /(.+)<\/key>/) { my $key = decode_base64($1); @secret = split('\|', $key); } else { die 'Problem retrieving security info from uitzendinggemist.nl'; } my $hash = md5_hex(join('|', $episode, $secret[1])); $hash =~ tr/a-z/A-Z/; my $stream; my $streamdata = $ua->get("http://pi.omroep.nl/info/stream/aflevering/$episode/$hash"); if ($streamdata->is_success && $streamdata->decoded_content =~ '(http://odi.omroep.nl/video/embedplayer/h264_std/.+http)') { return $1; } else { die 'Problem retrieving stream URL from uitzendinggemist.nl'; } return $stream; } # # Generic function to get a stream from a page URL by searching for the