|
|
@@ -0,0 +1,140 @@
|
|
|
+#!/usr/bin/perl
|
|
|
+use strict;
|
|
|
+use warnings;
|
|
|
+use LWP::UserAgent;
|
|
|
+
|
|
|
+# App config
|
|
|
+my $HTTP_TIMEOUT = 10;
|
|
|
+my $HTTP_UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A';
|
|
|
+my $HTTP_VERIFY = 0;
|
|
|
+
|
|
|
+# Globals
|
|
|
+my $LWP;
|
|
|
+my $QUALITY_URL;
|
|
|
+my $STREAM_URL;
|
|
|
+
|
|
|
+# Command-line parameters
|
|
|
+if (scalar(@ARGV) < 1) {
|
|
|
+ die('Usage: ' . $0 . "M3U8_URL\n");
|
|
|
+}
|
|
|
+$QUALITY_URL = $ARGV[0];
|
|
|
+
|
|
|
+# Environmental parameters
|
|
|
+my $DEBUG = 0;
|
|
|
+if (defined($ENV{'DEBUG'}) && $ENV{'DEBUG'} =~ /(\d+)/) {
|
|
|
+ $DEBUG = $1;
|
|
|
+}
|
|
|
+
|
|
|
+# Prototypes
|
|
|
+sub fetch($$);
|
|
|
+
|
|
|
+# Fetch and parse the quality playlist
|
|
|
+{
|
|
|
+ my $playlist;
|
|
|
+ if (fetch($QUALITY_URL, \$playlist) == 200) {
|
|
|
+ if ($DEBUG > 1) {
|
|
|
+ print STDERR "Quality playist:\n" . $playlist . "\n";
|
|
|
+ }
|
|
|
+
|
|
|
+ my $baseURL = $QUALITY_URL;
|
|
|
+ $baseURL =~ s/\?[^\?]*$//;
|
|
|
+ $baseURL =~ s/\/[^\/]*$//;
|
|
|
+ if ($DEBUG > 2) {
|
|
|
+ print STDERR 'Quality playlist base URL: ' . $baseURL . "\n";
|
|
|
+ }
|
|
|
+
|
|
|
+ my $lastBandwidth = 0;
|
|
|
+ my $savedBandwidth = 0;
|
|
|
+ foreach my $segment (split(/^/, $playlist)) {
|
|
|
+ if ($segment =~ /^\s*$/) {
|
|
|
+ next;
|
|
|
+ }
|
|
|
+ $segment =~ s/^\s+//;
|
|
|
+ $segment =~ s/\s+$//;
|
|
|
+
|
|
|
+ # Extract bandwidth data
|
|
|
+ if ($segment =~ /^#/) {
|
|
|
+ if ($segment =~ /^#EXT-X-STREAM-INF:.*\bBANDWIDTH=(\d+)\b/) {
|
|
|
+ $lastBandwidth = $1;
|
|
|
+ }
|
|
|
+ next;
|
|
|
+ }
|
|
|
+
|
|
|
+ # Keep the best stream
|
|
|
+ if ($DEBUG > 1) {
|
|
|
+ print STDERR 'Considering stream URL (' . $lastBandwidth . '): ' . $segment . "\n";
|
|
|
+ }
|
|
|
+ if ($lastBandwidth > $savedBandwidth) {
|
|
|
+ $savedBandwidth = $lastBandwidth;
|
|
|
+ $STREAM_URL = $segment;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+if (!defined($STREAM_URL)) {
|
|
|
+ die("Unable to find stream URL\n");
|
|
|
+}
|
|
|
+if ($DEBUG) {
|
|
|
+ print STDERR 'Keeping stream URL: ' . $STREAM_URL . "\n";
|
|
|
+}
|
|
|
+
|
|
|
+# Fetch and parse the stream playlist
|
|
|
+my @stream_segments = ();
|
|
|
+{
|
|
|
+ my $playlist;
|
|
|
+ if (fetch($STREAM_URL, \$playlist) == 200) {
|
|
|
+ if ($DEBUG > 1) {
|
|
|
+ print STDERR "Stream playlist:\n" . $playlist . "\n";
|
|
|
+ }
|
|
|
+
|
|
|
+ my $baseURL = $STREAM_URL;
|
|
|
+ $baseURL =~ s/\?[^\?]*$//;
|
|
|
+ $baseURL =~ s/\/[^\/]*$//;
|
|
|
+ if ($DEBUG > 2) {
|
|
|
+ print STDERR 'Playlist base URL: ' . $baseURL . "\n";
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach my $segment (split(/^/, $playlist)) {
|
|
|
+ if ($segment =~ /^#/ || $segment =~ /^\s*$/) {
|
|
|
+ next;
|
|
|
+ }
|
|
|
+ $segment =~ s/^\s+//;
|
|
|
+ $segment =~ s/\s+$//;
|
|
|
+ push(@stream_segments, $baseURL . '/' . $segment);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+# DEV-ONLY DEBUG
|
|
|
+foreach my $segment (@stream_segments) {
|
|
|
+ print STDERR $segment . "\n";
|
|
|
+}
|
|
|
+
|
|
|
+# Cleanup
|
|
|
+exit(0);
|
|
|
+
|
|
|
+# GET the requested URL, accepting encoded data and extracting the HTTP status code
|
|
|
+sub fetch($$) {
|
|
|
+ my ($url, $data) = @_;
|
|
|
+
|
|
|
+ # Init the global LWP as needed
|
|
|
+ if (!defined($LWP)) {
|
|
|
+ $LWP = new LWP::UserAgent;
|
|
|
+ $LWP->agent($HTTP_UA);
|
|
|
+ $LWP->timeout($HTTP_TIMEOUT);
|
|
|
+ $LWP->ssl_opts({ 'verify_hostname' => $HTTP_VERIFY });
|
|
|
+ }
|
|
|
+
|
|
|
+ # Make a request
|
|
|
+ my $request = HTTP::Request->new('GET' => $url);
|
|
|
+ $request->header('Accept-Encoding' => HTTP::Message::decodable);
|
|
|
+ my $response = $LWP->request($request);
|
|
|
+
|
|
|
+ # Do something useful with the response
|
|
|
+ my $code = 400;
|
|
|
+ if ($response->status_line =~ /^(\d+)\s/) {
|
|
|
+ $code = $1;
|
|
|
+ }
|
|
|
+ ${$data} = $response->decoded_content();
|
|
|
+ return $code;
|
|
|
+}
|