├── .gitignore ├── data └── config.php.dist ├── www ├── play.php ├── form.php └── functions.php └── README.rst /.gitignore: -------------------------------------------------------------------------------- 1 | /data/config.php 2 | /README.html 3 | -------------------------------------------------------------------------------- /data/config.php.dist: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /www/play.php: -------------------------------------------------------------------------------- 1 | 55 | -------------------------------------------------------------------------------- /www/form.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | playVideoOnDreamboxProxy 8 | 9 | 10 | 11 | 12 |

playVideoOnDreamboxProxy

13 |

14 | This proxy application allows you to play videos on your Dreambox; 15 | either direct video URLs or videos linked in HTML pages. 16 |

17 |

18 | You may use the 19 | Android app 20 | or the 21 | Firefox browser extension 22 | to control it directly. 23 |

24 | 25 | 26 |

Video URL input

27 |

28 | Enter the URL of a video or a web site that contains a video: 29 |

30 |
31 | 34 | 35 |
36 |

37 | Video will be played on 38 | . 39 |

40 | 41 | 42 |
43 |

44 | Licensed under the 45 | AGPL v3+. 46 | Homepage. 47 |

48 | 49 | 50 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | *********************************** 2 | Play video on Dreambox proxy server 3 | *********************************** 4 | Server for the `"Play video on Dreambox" android app`__. 5 | 6 | Accepts an URL, runs `youtube-dl`__ on it to extract the video 7 | URL and lets the Dreambox__ satellite receiver play this file. 8 | 9 | 10 | __ http://cweiske.de/playVideoOnDreambox.htm#android 11 | __ http://rg3.github.io/youtube-dl/ 12 | __ http://dream-multimedia-tv.de/ 13 | 14 | 15 | ===== 16 | Setup 17 | ===== 18 | Point your web server's document root to the ``www/`` directory. 19 | 20 | Altenatively symlink the ``www/play.php`` file into your document root 21 | directory. 22 | 23 | 24 | Configuration 25 | ============= 26 | You can adjust the path to ``youtube-dl`` and the Dreambox host name 27 | or IP address by creating a config file in ``data/config.php``. 28 | 29 | Simply copy ``data/config.php.dist`` onto ``data/config.php`` and adjust it. 30 | 31 | If your dreambox web interface is password protected, add the username 32 | and password to the ``$dreamboxUrl`` configuration variable:: 33 | 34 | $dreamboxUrl = 'http://user:pass@dreambox'; 35 | 36 | 37 | ===== 38 | Usage 39 | ===== 40 | Send the web site URL via POST to ``play.php``:: 41 | 42 | $ curl -XPOST --data http://example.org/page.htm\ 43 | -H 'Content-type: text/plain'\ 44 | http://proxy.example.org/play.php 45 | 46 | You can test it on command line, too:: 47 | 48 | $ php www/play.php http://example.org/page.htm 49 | 50 | Testing the URL selection without playing on the dreambox is possible:: 51 | 52 | $ php www/play.php http://example.org/page.htm --dry-run 53 | 54 | 55 | ======= 56 | License 57 | ======= 58 | This application is available under the `AGPL v3`__ or later. 59 | 60 | __ http://www.gnu.org/licenses/agpl.html 61 | 62 | 63 | ====== 64 | Author 65 | ====== 66 | Written by `Christian Weiske`__, cweiske@cweiske.de 67 | 68 | __ http://cweiske.de/ 69 | -------------------------------------------------------------------------------- /www/functions.php: -------------------------------------------------------------------------------- 1 | " 13 | ); 14 | } 15 | $options = []; 16 | array_shift($argv);//remove script itself 17 | foreach ($argv as $val) { 18 | if ($val[0] == '-') { 19 | $options[$val] = true; 20 | } else { 21 | $pageUrl = $val; 22 | } 23 | } 24 | if (isset($options['--dry-run']) || isset($options['-n'])) { 25 | $dryRun = true; 26 | } 27 | } else if (!isset($_SERVER['CONTENT_TYPE'])) { 28 | errorInput('Content type header missing'); 29 | } else if ($_SERVER['CONTENT_TYPE'] == 'text/plain') { 30 | //Android app 31 | $pageUrl = file_get_contents('php://input'); 32 | } else if ($_SERVER['CONTENT_TYPE'] == 'application/x-www-form-urlencoded') { 33 | //Web form 34 | if (!isset($_POST['url'])) { 35 | errorInput('"url" POST parameter missing'); 36 | } 37 | $pageUrl = $_POST['url']; 38 | } else { 39 | errorInput('Content type is not text/plain but ' . $_SERVER['CONTENT_TYPE']); 40 | } 41 | 42 | $parts = parse_url($pageUrl ?? null); 43 | if ($parts === false) { 44 | errorInput('Invalid URL in POST data'); 45 | } else if (!isset($parts['scheme'])) { 46 | errorInput('Invalid URL in POST data: No scheme'); 47 | } else if ($parts['scheme'] !== 'http' && $parts['scheme'] !== 'https') { 48 | errorInput('Invalid URL in POST data: Non-HTTP scheme'); 49 | } 50 | return [$pageUrl, $dryRun]; 51 | } 52 | 53 | function getYoutubeDlJson($pageUrl, $youtubedlPath) 54 | { 55 | $cmd = $youtubedlPath 56 | . ' --no-playlist'//would otherwise cause multiple json blocks 57 | . ' --quiet' 58 | . ' --dump-json' 59 | . ' ' . escapeshellarg($pageUrl); 60 | 61 | $descriptors = [ 62 | 1 => ['pipe', 'w'],//stdout 63 | 2 => ['pipe', 'w'],//stderr 64 | ]; 65 | $proc = proc_open($cmd, $descriptors, $pipes); 66 | if ($proc === false) { 67 | errorOut('Error running youtube-dl'); 68 | } 69 | $stdout = stream_get_contents($pipes[1]); 70 | $stderr = stream_get_contents($pipes[2]); 71 | 72 | $exitCode = proc_close($proc); 73 | 74 | if ($exitCode === 0) { 75 | //stdout contains the JSON data 76 | return $stdout; 77 | } 78 | 79 | if (strlen($stderr)) { 80 | $lines = explode("\n", trim($stderr)); 81 | $lastLine = end($lines); 82 | } else { 83 | $lines = explode("\n", trim($stdout)); 84 | $lastLine = end($lines); 85 | } 86 | 87 | if ($exitCode === 127) { 88 | errorOut( 89 | 'youtube-dl not found at ' . $youtubedlPath, 90 | '500 youtube-dl not found' 91 | ); 92 | } else if (strpos($lastLine, 'Unsupported URL') !== false) { 93 | errorOut( 94 | 'Unsupported URL at ' . $pageUrl, 95 | '406 Unsupported URL (No video found)' 96 | ); 97 | } 98 | 99 | errorOut('youtube-dl error: ' . $lastLine); 100 | } 101 | 102 | function extractVideoUrlFromJson($json) 103 | { 104 | $data = json_decode($json); 105 | if ($data === null) { 106 | errorOut('Cannot decode JSON: ' . json_last_error_msg()); 107 | } 108 | 109 | $safeFormats = []; 110 | foreach ($data->formats as $format) { 111 | if (strpos($format->format, 'hls') !== false) { 112 | //dreambox 7080hd does not play hls files 113 | continue; 114 | } 115 | if (strpos($format->format, 'vp9') !== false 116 | || $format->vcodec == 'vp9' 117 | ) { 118 | //dreambox 7080hd does not play VP9 video streams 119 | continue; 120 | } 121 | if (strtolower(substr($format->vcodec, 0, 6)) == 'avc1.6') { 122 | //dreambox DM7080 does not play H.264 High Profile 123 | continue; 124 | } 125 | if ($format->protocol == 'http_dash_segments') { 126 | //split up into multiple small files 127 | continue; 128 | } 129 | if ($format->ext == 'flv') { 130 | //Internal data flow error 131 | continue; 132 | } 133 | $safeFormats[] = $format; 134 | } 135 | 136 | $url = null; 137 | 138 | //filter: best quality 139 | usort($safeFormats, function ($a, $b) { 140 | $a->acodec = $a->acodec ?? null; 141 | $b->acodec = $b->acodec ?? null; 142 | if ((($a->acodec != 'none') + ($b->acodec != 'none')) == 1) { 143 | return ($b->acodec != 'none') - ($a->acodec != 'none'); 144 | } 145 | return ($b->quality ?? 0) - ($a->quality ?? 0); 146 | }); 147 | foreach ($safeFormats as $format) { 148 | //echo $format->format . ' | ' . $format->vcodec . ' | ' . $format->acodec . "\n"; 149 | $url = $format->url; 150 | break; 151 | } 152 | 153 | if ($url === null) { 154 | //use URL chosen by youtube-dl 155 | $url = $data->url; 156 | } 157 | 158 | if ($url == '') { 159 | errorOut( 160 | 'No video URL found', 161 | '406 No video URL found' 162 | ); 163 | } 164 | return $url; 165 | } 166 | 167 | function playVideoOnDreambox($videoUrl, $dreamboxUrl) 168 | { 169 | ini_set('track_errors', 1); 170 | $xml = @file_get_contents($dreamboxUrl . '/web/session'); 171 | if ($xml === false) { 172 | if (!isset($http_response_header)) { 173 | errorOut( 174 | 'Error fetching dreambox web interface token: ' 175 | . $GLOBALS['lastError'] 176 | ); 177 | } 178 | 179 | list($http, $code, $message) = explode( 180 | ' ', $http_response_header[0], 3 181 | ); 182 | if ($code == 401) { 183 | //dreambox web interface authentication has been enabled 184 | errorOut( 185 | 'Error: Web interface authentication is required', 186 | '401 Dreambox web authentication required' 187 | ); 188 | } else { 189 | errorOut( 190 | 'Failed to fetch dreambox session token: ' . $php_errormsg, 191 | $code . ' ' . $message 192 | ); 193 | } 194 | } 195 | $sx = simplexml_load_string($xml); 196 | $token = (string) $sx; 197 | 198 | $playUrl = $dreamboxUrl 199 | . '/web/mediaplayerplay' 200 | . '?file=4097:0:1:0:0:0:0:0:0:0:' 201 | . str_replace('%3A', '%253A', rawurlencode($videoUrl)); 202 | 203 | $ctx = stream_context_create( 204 | array( 205 | 'http' => array( 206 | 'method' => 'POST', 207 | 'header' => 'Content-type: application/x-www-form-urlencoded', 208 | 'content' => 'sessionid=' . $token, 209 | //'ignore_errors' => true 210 | ) 211 | ) 212 | ); 213 | $ret = file_get_contents($playUrl, false, $ctx); 214 | if ($ret !== false) { 215 | if (php_sapi_name() != 'cli') { 216 | header('HTTP/1.0 200 OK'); 217 | } 218 | echo "Video play request sent to dreambox\n"; 219 | exit(0); 220 | } else { 221 | errorOut( 222 | 'Failed to send video play request to dreambox: ' . $php_errormsg 223 | ); 224 | } 225 | } 226 | 227 | function errorHandlerStore($number, $message, $file, $line) 228 | { 229 | $GLOBALS['lastError'] = $message; 230 | return false; 231 | } 232 | $GLOBALS['lastError'] = null; 233 | ?> 234 | --------------------------------------------------------------------------------