X-Git-Url: https://git.cweiske.de/playVideoOnDreamboxProxy.git/blobdiff_plain/34daea5a682bd5b5a95e22964d3fa1e1a3e7837b..HEAD:/www/functions.php diff --git a/www/functions.php b/www/functions.php index 36aacec..95694e7 100644 --- a/www/functions.php +++ b/www/functions.php @@ -2,20 +2,44 @@ function getPageUrl() { global $argv, $argc; + + $dryRun = false; if (php_sapi_name() == 'cli') { if ($argc < 2) { - errorInput('No URL given as command line parameter'); + errorInput( + "No URL given as command line parameter\n" + . "Usage:\n" + . " play.php [--dry-run|-n] " + ); } - $pageUrl = $argv[1]; - } else { - if (!isset($_SERVER['CONTENT_TYPE'])) { - errorInput('Content type header missing'); - } else if ($_SERVER['CONTENT_TYPE'] != 'text/plain') { - errorInput('Content type is not text/plain but ' . $_SERVER['CONTENT_TYPE']); + $options = []; + array_shift($argv);//remove script itself + foreach ($argv as $val) { + if ($val[0] == '-') { + $options[$val] = true; + } else { + $pageUrl = $val; + } } + if (isset($options['--dry-run']) || isset($options['-n'])) { + $dryRun = true; + } + } else if (!isset($_SERVER['CONTENT_TYPE'])) { + errorInput('Content type header missing'); + } else if ($_SERVER['CONTENT_TYPE'] == 'text/plain') { + //Android app $pageUrl = file_get_contents('php://input'); + } else if ($_SERVER['CONTENT_TYPE'] == 'application/x-www-form-urlencoded') { + //Web form + if (!isset($_POST['url'])) { + errorInput('"url" POST parameter missing'); + } + $pageUrl = $_POST['url']; + } else { + errorInput('Content type is not text/plain but ' . $_SERVER['CONTENT_TYPE']); } - $parts = parse_url($pageUrl); + + $parts = parse_url($pageUrl ?? null); if ($parts === false) { errorInput('Invalid URL in POST data'); } else if (!isset($parts['scheme'])) { @@ -23,7 +47,7 @@ function getPageUrl() } else if ($parts['scheme'] !== 'http' && $parts['scheme'] !== 'https') { errorInput('Invalid URL in POST data: Non-HTTP scheme'); } - return $pageUrl; + return [$pageUrl, $dryRun]; } function getYoutubeDlJson($pageUrl, $youtubedlPath) @@ -32,28 +56,47 @@ function getYoutubeDlJson($pageUrl, $youtubedlPath) . ' --no-playlist'//would otherwise cause multiple json blocks . ' --quiet' . ' --dump-json' - . ' ' . escapeshellarg($pageUrl) - . ' 2> /dev/null'; + . ' ' . escapeshellarg($pageUrl); - $lastLine = exec($cmd, $output, $exitCode); - if ($exitCode !== 0) { - if ($exitCode === 127) { - errorOut( - 'youtube-dl not found at ' . $youtubedlPath, - '500 youtube-dl not found' - ); - } else if (strpos($lastLine, 'Unsupported URL') !== false) { - errorOut( - 'Unsupported URL at ' . $pageUrl, - '406 Unsupported URL (No video found)' - ); - } else { - errorOut('youtube-dl error: ' . $lastLine); - } + $descriptors = [ + 1 => ['pipe', 'w'],//stdout + 2 => ['pipe', 'w'],//stderr + ]; + $proc = proc_open($cmd, $descriptors, $pipes); + if ($proc === false) { + errorOut('Error running youtube-dl'); + } + $stdout = stream_get_contents($pipes[1]); + $stderr = stream_get_contents($pipes[2]); + + $exitCode = proc_close($proc); + + if ($exitCode === 0) { + //stdout contains the JSON data + return $stdout; + } + + if (strlen($stderr)) { + $lines = explode("\n", trim($stderr)); + $lastLine = end($lines); + } else { + $lines = explode("\n", trim($stdout)); + $lastLine = end($lines); + } + + if ($exitCode === 127) { + errorOut( + 'youtube-dl not found at ' . $youtubedlPath, + '500 youtube-dl not found' + ); + } else if (strpos($lastLine, 'Unsupported URL') !== false) { + errorOut( + 'Unsupported URL at ' . $pageUrl, + '406 Unsupported URL (No video found)' + ); } - $json = implode("\n", $output); - return $json; + errorOut('youtube-dl error: ' . $lastLine); } function extractVideoUrlFromJson($json) @@ -63,17 +106,48 @@ function extractVideoUrlFromJson($json) errorOut('Cannot decode JSON: ' . json_last_error_msg()); } - $url = null; + $safeFormats = []; foreach ($data->formats as $format) { if (strpos($format->format, 'hls') !== false) { //dreambox 7080hd does not play hls files continue; } + if (strpos($format->format, 'vp9') !== false + || $format->vcodec == 'vp9' + ) { + //dreambox 7080hd does not play VP9 video streams + continue; + } + if (strtolower(substr($format->vcodec, 0, 6)) == 'avc1.6') { + //dreambox DM7080 does not play H.264 High Profile + continue; + } if ($format->protocol == 'http_dash_segments') { //split up into multiple small files continue; } + if ($format->ext == 'flv') { + //Internal data flow error + continue; + } + $safeFormats[] = $format; + } + + $url = null; + + //filter: best quality + usort($safeFormats, function ($a, $b) { + $a->acodec = $a->acodec ?? null; + $b->acodec = $b->acodec ?? null; + if ((($a->acodec != 'none') + ($b->acodec != 'none')) == 1) { + return ($b->acodec != 'none') - ($a->acodec != 'none'); + } + return ($b->quality ?? 0) - ($a->quality ?? 0); + }); + foreach ($safeFormats as $format) { + //echo $format->format . ' | ' . $format->vcodec . ' | ' . $format->acodec . "\n"; $url = $format->url; + break; } if ($url === null) { @@ -90,11 +164,18 @@ function extractVideoUrlFromJson($json) return $url; } -function playVideoOnDreambox($videoUrl, $dreamboxHost) +function playVideoOnDreambox($videoUrl, $dreamboxUrl) { ini_set('track_errors', 1); - $xml = @file_get_contents('http://' . $dreamboxHost . '/web/session'); + $xml = @file_get_contents($dreamboxUrl . '/web/session'); if ($xml === false) { + if (!isset($http_response_header)) { + errorOut( + 'Error fetching dreambox web interface token: ' + . $GLOBALS['lastError'] + ); + } + list($http, $code, $message) = explode( ' ', $http_response_header[0], 3 ); @@ -114,7 +195,7 @@ function playVideoOnDreambox($videoUrl, $dreamboxHost) $sx = simplexml_load_string($xml); $token = (string) $sx; - $playUrl = 'http://' . $dreamboxHost + $playUrl = $dreamboxUrl . '/web/mediaplayerplay' . '?file=4097:0:1:0:0:0:0:0:0:0:' . str_replace('%3A', '%253A', rawurlencode($videoUrl)); @@ -142,4 +223,11 @@ function playVideoOnDreambox($videoUrl, $dreamboxHost) ); } } + +function errorHandlerStore($number, $message, $file, $line) +{ + $GLOBALS['lastError'] = $message; + return false; +} +$GLOBALS['lastError'] = null; ?>