├── .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 |
36 |
37 | Video will be played on
38 | = $dreamboxUrlNoAuth ?>.
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 |
--------------------------------------------------------------------------------