Fix E_NOTICES on php 8.2
[playVideoOnDreamboxProxy.git] / www / functions.php
1 <?php
2 function getPageUrl()
3 {
4     global $argv, $argc;
5
6     $dryRun = false;
7     if (php_sapi_name() == 'cli') {
8         if ($argc < 2) {
9             errorInput(
10                 "No URL given as command line parameter\n"
11                 . "Usage:\n"
12                 . " play.php [--dry-run|-n] <url>"
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 ?>