Subversion Repositories yt_downloader

Rev

Rev 20 | Rev 22 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 daniel-mar 1
#!/usr/bin/php
2
<?php
3
 
21 daniel-mar 4
// ViaThinkSoft YouTube Downloader Util 2.4.1
5
// Revision: 2023-06-10
2 daniel-mar 6
// Author: Daniel Marschall <www.daniel-marschall.de>
7 daniel-mar 7
// Licensed under the terms of the Apache 2.0 License
2 daniel-mar 8
//
6 daniel-mar 9
// For syntax and other documentation, please read the file README.
2 daniel-mar 10
 
11
// ------------------------------------------------------------------------------------------------
12
 
13
error_reporting(E_ALL | E_NOTICE | E_STRICT | E_DEPRECATED);
14
 
15
define('AUTO_API_KEY', '~/.yt_api_key');
6 daniel-mar 16
define('AUTO_COOKIE_FILE', '~/.yt_cookies');
2 daniel-mar 17
define('DOWNLOAD_SIMULATION_MODE', false);
18
define('DEFAULT_SEARCH_ORDER', 'relevance');
19
define('DEFAULT_SEARCH_MAXRESULTS', 10);
20
 
21
putenv("LANG=de_DE.UTF-8"); // required if video titles contain non-ASCII symbols
22
 
23
require_once __DIR__ . '/youtube_functions.inc.phps';
14 daniel-mar 24
require_once __DIR__ . '/checksum_functions.inc.phps';
2 daniel-mar 25
 
26
// Check if we are running in command line
27
 
28
if (PHP_SAPI !== 'cli') {
29
	fwrite(STDERR, "Error: Can only run in CLI mode\n");
30
	exit(2);
31
}
32
 
33
// Global vars
34
 
35
$listFilenameStack = array();
36
 
37
// Default values
38
 
39
$allow_creation_outputdir = false;
40
$type = 'v:';
41
$outputDir = '';
42
$alreadyDownloaded = '';
14 daniel-mar 43
$checksumMode = 'none';
2 daniel-mar 44
$failList = '';
45
$failTreshold = 3;
46
$rest_args = array();
47
$verbose = false;
48
$mp3id_transfer = true;
49
$apikey = '';
50
$resultcache = '';
51
$extra_args =
52
//            '-k ' . // The additional "-k" option in the above makes youtube-dl keep downloaded videos.
53
              '-i ' . // continue upon download errors
54
              '-c ';  // resume partially downloaded video files
55
$default_template = '%(title)s-%(id)s.%(ext)s';
6 daniel-mar 56
$cookie_file = AUTO_COOKIE_FILE;
16 daniel-mar 57
$downloader = 'yt-dlp';
2 daniel-mar 58
 
20 daniel-mar 59
if (($downloader == 'yt-dlp') && file_exists(__DIR__.'/ffmpeg')) {
60
	// With yt-dlp, you might get the error "ERROR: Postprocessing: Conversion failed!" if
61
	// you have a tool old ffmpeg version.
62
	$extra_args .= ' --ffmpeg-location '.escapeshellarg(__DIR__.'/ffmpeg');
63
}
64
 
2 daniel-mar 65
// Parse arguments
66
// We do not use getopt() at the moment, because the important functionality "optind" is only available in PHP 7.1, which is not yet distributed with most of the stable Linux distros
67
 
68
$init_extra_args = false;
7 daniel-mar 69
$argv_bak = $_SERVER['argv'];
2 daniel-mar 70
array_shift($argv_bak);
71
while (count($argv_bak) > 0) {
72
	$arg = array_shift($argv_bak);
73
	$arg2 = $arg . ' ' . (isset($argv_bak[0]) ? $argv_bak[0] : '');
7 daniel-mar 74
	$m = null;
2 daniel-mar 75
	if (preg_match('@^(/t|\-t|\-\-type)(\s+|=)(.*)$@s', $arg2, $m)) {
76
		array_shift($argv_bak);
77
		if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
78
		$type = $m[3];
79
	} else if (preg_match('@^(/o|\-o|\-\-outputDir)(\s+|=)(.*)$@s', $arg2, $m)) {
80
		array_shift($argv_bak);
81
		if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
82
		$outputDir = $m[3];
83
	} else if (preg_match('@^(/a|\-a|\-\-alreadyDownloaded)(\s+|=)(.*)$@s', $arg2, $m)) {
84
		array_shift($argv_bak);
85
		if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
86
		$alreadyDownloaded = $m[3];
87
	} else if (preg_match('@^(/f|\-f|\-\-failList)(\s+|=)(.*)$@s', $arg2, $m)) {
88
		array_shift($argv_bak);
89
		if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
90
		$failList = $m[3];
91
	} else if (preg_match('@^(/F|\-F|\-\-failTreshold)(\s+|=)(.*)$@s', $arg2, $m)) {
92
		array_shift($argv_bak);
93
		if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
94
		$failTreshold = $m[3];
95
	} else if (preg_match('@^(/C|\-C|\-\-resultcache)(\s+|=)(.*)$@s', $arg2, $m)) {
96
		array_shift($argv_bak);
97
		if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
98
		$resultcache = $m[3];
14 daniel-mar 99
	} else if (preg_match('@^(/H|\-H|\-\-checksumMode)(\s+|=)(.*)$@s', $arg2, $m)) {
100
		array_shift($argv_bak);
101
		if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
102
		$checksumMode = $m[3];
103
		if ((strtolower($checksumMode) != 'none')
104
			&& (strtolower($checksumMode) != 'sfv')
105
			&& (strtolower($checksumMode) != 'md5')
106
			&& (strtolower($checksumMode) != 'sfv,md5')
107
			&& (strtolower($checksumMode) != 'md5,sfv')) syntax_error("Checksum mode needs to be either 'None', 'MD5', 'SFV', or 'MD5,SFV'.");
2 daniel-mar 108
	} else if (preg_match('@^(/T|\-T|\-\-default-template)(\s+|=)(.*)$@s', $arg2, $m)) {
109
		array_shift($argv_bak);
110
		if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
111
		$default_template = $m[3];
112
	} else if (preg_match('@^(/A|\-A|\-\-api-key)(\s+|=)(.*)$@s', $arg2, $m)) {
113
		array_shift($argv_bak);
114
		if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
115
		$apikey = file_exists($m[3]) ? trim(file_get_contents($m[3])) : $m[3];
6 daniel-mar 116
	} else if (preg_match('@^(\-\-cookies)(\s+|=)(.*)$@s', $arg2, $m)) {
117
		array_shift($argv_bak);
118
		if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
119
		$cookie_file = file_exists($m[3]) ? trim(file_get_contents($m[3])) : $m[3];
16 daniel-mar 120
	} else if (preg_match('@^(\-\-downloader)(\s+|=)(.*)$@s', $arg2, $m)) {
121
		array_shift($argv_bak);
122
		if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
123
		$downloader = $m[3];
2 daniel-mar 124
	} else if (preg_match('@^(/X|\-X|\-\-extra-args)(\s+|=)(.*)$@s', $arg2, $m)) {
125
		array_shift($argv_bak);
126
		if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
127
		if ($init_extra_args) {
128
			$extra_args .= ' ' . $m[3]; // user has multiple "-X" arguments. append.
129
		} else {
130
			$extra_args = $m[3]; // overwrite defaults
131
			$init_extra_args = true;
132
		}
133
	} else if (preg_match('@^(/\?|/h|\-\?|\-h|\-\-help)$@s', $arg, $m)) {
134
		if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
135
		help();
136
	} else if (preg_match('@^(/V|\-V|\-\-version)$@s', $arg, $m)) {
137
		if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
138
		version();
139
	} else if (preg_match('@^(/v|\-v|\-\-verbose)$@s', $arg, $m)) {
140
		if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
141
		$verbose = true;
142
	} else if (preg_match('@^(/N|\-N|\-\-no-mp3-tagtransfer)$@s', $arg, $m)) {
143
		if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
144
		$mp3id_transfer = false;
145
	} else if (preg_match('@^(/O|\-O|\-\-create-outputdir)$@s', $arg, $m)) {
146
		if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
147
		$allow_creation_outputdir = true;
148
	} else if ($arg == '--') {
149
		if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
150
		$rest_args = $argv_bak;
151
		break;
152
	} else {
153
		$rest_args[] = $arg;
154
	}
155
}
156
unset($arg);
157
unset($argv_bak);
158
unset($init_extra_args);
159
 
16 daniel-mar 160
define('DOWNLOAD_YT_FORK', $downloader);
161
 
2 daniel-mar 162
// Validity checks
163
 
164
if ((substr($type,0,2) != 'a:') && (substr($type,0,2) != 'v:')) syntax_error("Type must be either 'v:' or 'a:'. '$type' is not valid.");
165
 
166
if (count($rest_args) == 0) syntax_error("Please enter at least one desired video for downloading");
167
 
168
if ($failTreshold <= 0) syntax_error("Fail treshold has invalid value. Must be >0.");
169
 
6 daniel-mar 170
$cookie_file = expand_tilde($cookie_file);
171
if (!file_exists($cookie_file)) $cookie_file = '';
172
 
16 daniel-mar 173
// Try to download/update youtube-dl/yt-dlp into local directory
174
download_latest_downloader();
2 daniel-mar 175
 
16 daniel-mar 176
if (command_exists(__DIR__.'/'.DOWNLOAD_YT_FORK)) {
177
	echo "Will use '".DOWNLOAD_YT_FORK."' from local directory\n";
178
	define('YTDL_EXE', __DIR__.'/'.DOWNLOAD_YT_FORK);
5 daniel-mar 179
} else {
180
	// Download failed. Is at least a package installed?
16 daniel-mar 181
	if (command_exists(DOWNLOAD_YT_FORK)) {
182
		echo "Will use '".DOWNLOAD_YT_FORK."' from Linux package\n";
183
		define('YTDL_EXE', DOWNLOAD_YT_FORK);
2 daniel-mar 184
	} else {
16 daniel-mar 185
		fwrite(STDERR, "This script requires the tool/package '".DOWNLOAD_YT_FORK."'. Please install it first.\n");
2 daniel-mar 186
		exit(1);
187
	}
188
}
189
 
190
// Now process the videos
191
 
192
yt_set_apikey_callback('_getApikey');
193
 
194
foreach ($rest_args as $resource) {
195
	if ($verbose) echo "Handle: $resource\n";
196
	if (strpos($resource, ':') === false) {
197
		fwrite(STDERR, "Invalid resource '$resource' (you are missing the prefix, e.g. vurl: or vid:). Skipping.\n");
198
	} else {
199
		list($resourceType, $resourceValue) = explode(':', $resource, 2);
19 daniel-mar 200
		try {
201
			ytdwn_handle_resource($resourceType, $resourceValue);
202
		} catch(Exception $e) {
203
			fwrite(STDERR, "Error at '$resourceType:$resourceValue': ".$e->getMessage()."\n");
204
		}
2 daniel-mar 205
	}
206
}
207
 
208
// ------------------------------------------------------------------------------------------------
209
 
210
function ytdwn_handle_resource($resourceType, $resourceValue) {
211
	if ($resourceType == 'vid') {
212
		$video_id = parse_quoting($resourceValue);
213
		ytdwn_video_id($video_id);
214
	} else if ($resourceType == 'vurl') {
215
		$video_url = parse_quoting($resourceValue);
216
		$video_id  = getVideoIDFromURL($video_url);
217
		if (!$video_id) {
218
			fwrite(STDERR, "$video_url is not a valid YouTube video URL. Skipping.\n");
219
		} else {
220
			ytdwn_video_id($video_id);
221
		}
222
	} else if ($resourceType == 'pid') {
223
		$playlist_id = parse_quoting($resourceValue);
224
		ytdwn_playlist_id($playlist_id);
225
	} else if ($resourceType == 'purl') {
226
		$playlist_url = parse_quoting($resourceValue);
227
		$playlist_id  = getPlaylistIDFromURL($playlist_url);
228
		if (!$playlist_id) {
229
			fwrite(STDERR, "$playlist_url is not a valid YouTube playlist URL. Skipping\n");
230
		} else {
231
			ytdwn_playlist_id($playlist_id);
232
		}
233
	} else if ($resourceType == 'cid') {
234
		$channel_id = parse_quoting($resourceValue);
235
 
7 daniel-mar 236
		$m = null;
2 daniel-mar 237
		if (preg_match('@\[search=(.+)\]@ismU', $channel_id, $m)) {
238
			$search = $m[1];
239
			$channel_id = preg_replace('@\[search=(.+)\]@ismU', '', $channel_id);
240
		} else {
241
			$search = ''; // default
242
		}
243
		$search = parse_quoting($search);
244
 
245
		ytdwn_channel_id($channel_id, $search);
246
	} else if ($resourceType == 'cname') {
247
		$channel_name = parse_quoting($resourceValue);
248
 
7 daniel-mar 249
		$m = null;
2 daniel-mar 250
		if (preg_match('@\[search=(.+)\]@ismU', $channel_name, $m)) {
251
			$search = $m[1];
252
			$channel_name = preg_replace('@\[search=(.+)\]@ismU', '', $channel_name);
253
		} else {
254
			$search = ''; // default
255
		}
256
		$search = parse_quoting($search);
257
 
258
		$channel_name = parse_quoting($channel_name);
259
		$channel_id = yt_get_channel_id($channel_name);
260
		if (!$channel_id) {
13 daniel-mar 261
			fwrite(STDERR, "URL $channel_name is a valid YouTube username. Will now try to interprete it as channel ID instead....\n");
2 daniel-mar 262
		}
13 daniel-mar 263
		ytdwn_channel_id($channel_id, $search);
2 daniel-mar 264
	} else if ($resourceType == 'curl') {
265
		$channel_url = parse_quoting($resourceValue);
266
 
7 daniel-mar 267
		$m = null;
2 daniel-mar 268
		if (preg_match('@\[search=(.+)\]@ismU', $channel_url, $m)) {
269
			$search = $m[1];
270
			$channel_url = preg_replace('@\[search=(.+)\]@ismU', '', $channel_url);
271
		} else {
272
			$search = ''; // default
273
		}
274
		$search = parse_quoting($search);
275
 
276
		$channel_url = parse_quoting($channel_url);
277
		$channel_id = curl_to_cid($channel_url);
278
		if (!$channel_id) {
8 daniel-mar 279
			fwrite(STDERR, "URL $channel_url is a valid YouTube channel or username URL. Skipping\n");
2 daniel-mar 280
		} else {
281
			ytdwn_channel_id($channel_id, $search);
282
		}
283
	} else if ($resourceType == 'search') {
284
		$searchterm = parse_quoting($resourceValue);
285
 
286
		$order = '';
7 daniel-mar 287
		$m = null;
2 daniel-mar 288
		if (preg_match('@\[order=(.+)\]@ismU', $searchterm, $m)) {
289
			$order = $m[1];
290
			$searchterm = preg_replace('@\[order=(.+)\]@ismU', '', $searchterm);
291
		} else {
292
			$order = DEFAULT_SEARCH_ORDER; // default
293
		}
294
		$order = parse_quoting($order);
295
 
296
		$maxresults = '';
297
		if (preg_match('@\[maxresults=(.+)\]@ismU', $searchterm, $m)) {
298
			$maxresults = $m[1];
299
			$searchterm = preg_replace('@\[maxresults=(.+)\]@ismU', '', $searchterm);
300
		} else {
301
			$maxresults = DEFAULT_SEARCH_MAXRESULTS; // default
302
		}
303
		$maxresults = parse_quoting($maxresults);
304
 
305
		$searchterm = parse_quoting($searchterm);
306
 
307
		ytdwn_search($searchterm, $order, $maxresults);
308
	} else if ($resourceType == 'list') {
309
		$list_files = glob(parse_quoting($resourceValue)); // in case the user entered a wildcard, e.g. *.list
310
		foreach ($list_files as $list_file) {
311
			if (!file_exists($list_file)) {
312
				fwrite(STDERR, "List file $list_file does not exist. Skipping\n");
313
			} else {
314
				ytdwn_list_file($list_file);
315
			}
316
		}
317
	} else {
318
		fwrite(STDERR, "Resource type '$resourceType' is not valid. Skipping $resourceType:$resourceValue.\n");
319
	}
320
}
321
 
322
function ytdwn_list_file($list_file) {
323
	global $listFilenameStack, $verbose;
324
 
325
	if ($verbose) echo "Processing list file '$list_file' ...\n";
326
 
327
	$listFilenameStack[] = $list_file;
328
	$lines = file($list_file);
329
	foreach ($lines as $line) {
330
		$line = trim($line);
331
		if ($line == '') continue;
332
		if ($line[0] == '#') continue;
333
		if (strpos($line, ':') === false) {
334
			fwrite(STDERR, "Invalid resource '$line' (you are missing the prefix, e.g. vurl: or vid:). Skipping.\n");
335
		} else {
336
			list($resourceType, $resourceValue) = explode(':',$line,2);
19 daniel-mar 337
			try {
338
				ytdwn_handle_resource($resourceType, $resourceValue);
339
			} catch(Exception $e) {
340
				fwrite(STDERR, "Error at line '$line': ".$e->getMessage()."\n");
341
			}
2 daniel-mar 342
		}
343
	}
344
	array_pop($listFilenameStack);
345
}
346
 
347
function ytdwn_channel_id($channel_id, $search='') {
348
	global $type;
349
	global $verbose;
350
 
351
	if ($verbose) echo "Processing channel ID '$channel_id' ...\n";
352
 
353
	// List the videos of the channel
354
 
16 daniel-mar 355
	$use_cache = !empty(_getResultcache()) && file_exists(_getResultcache());
356
	$cont = $use_cache ? file_get_contents(_getResultcache()) : '';
2 daniel-mar 357
	$out = json_decode($cont, true);
358
	if ($out == NULL) $out = array();
359
 
16 daniel-mar 360
	if ($use_cache) {
2 daniel-mar 361
		$stats = yt_get_channel_stats($channel_id);
18 daniel-mar 362
		if (!$stats) {
11 daniel-mar 363
			fwrite(STDERR, "Cannot get stats for channel with ID '$channel_id'\n");
364
			return;
365
		}
2 daniel-mar 366
		$videocount = $stats['videoCount'];
367
 
368
		$key = (!empty($search)) ? 'cid:'.$channel_id.'/'.$search : 'cid:'.$channel_id;
369
 
370
		if (!isset($out[$key])) $out[$key] = array();
371
		$videocount_old = isset($out[$key]['count']) ? $out[$key]['count'] : -1;
372
	} else {
373
		$videocount = -1;
374
		$videocount_old = -2;
375
		$key = '';
376
	}
377
 
378
	if ($videocount_old != $videocount) { // Attention: This might not work if videos are deleted and added (= the count stays the same)
16 daniel-mar 379
		if ($verbose && $use_cache) echo "Video count changed from $videocount_old to $videocount\n";
2 daniel-mar 380
		$out[$key]['count'] = $videocount;
381
		if (!empty($search)) {
382
			$out[$key]['results'] = yt_channel_items($channel_id, $search);
383
		} else {
384
			$out[$key]['results'] = yt_channel_items($channel_id);
385
		}
18 daniel-mar 386
		if (!$out[$key]['results']) {
19 daniel-mar 387
			// TODO: If a channel has deleted all videos, then this message comes.
388
			//       However, technically this is not an error.
16 daniel-mar 389
			fwrite(STDERR, "Cannot get result for channel with ID '$channel_id'\n");
390
			return;
391
		}
2 daniel-mar 392
	} else {
393
		if ($verbose) echo "Video count for channel is still $videocount, keep ".count($out[$key]['results'])." results.\n";
394
	}
395
 
396
	// Save the cache
397
 
398
	try {
16 daniel-mar 399
		if ($use_cache) file_put_contents(_getResultcache(), json_encode($out));
2 daniel-mar 400
	} catch(Exception $e) {
401
		fwrite(STDERR, "Cannot write result cache\n");
402
	}
403
 
404
	// Now download
405
 
18 daniel-mar 406
	if (!$out[$key]['results']) {
19 daniel-mar 407
		// TODO: If a channel has deleted all videos, then this message comes.
408
		//       However, technically this is not an error.
6 daniel-mar 409
		fwrite(STDERR, "Cannot get result for channel with ID '$channel_id'\n");
410
		return;
411
	}
2 daniel-mar 412
	foreach ($out[$key]['results'] as list($id, $title)) {
413
		if ($verbose) echo "Downloading '$title' as ".hf_type($type)." ...\n";
414
		ytdwn_video_id($id);
415
	}
416
}
417
 
418
function ytdwn_playlist_id($playlist_id) {
419
	global $type;
420
	global $verbose;
421
 
422
	if ($verbose) echo "Processing playlist ID '$playlist_id' ...\n";
423
 
424
	// List the videos of the playlist
425
 
16 daniel-mar 426
	$use_cache = !empty(_getResultcache()) && file_exists(_getResultcache());
427
	$cont = $use_cache ? file_get_contents(_getResultcache()) : '';
2 daniel-mar 428
	$out = json_decode($cont, true);
429
	if ($out == NULL) $out = array();
430
 
16 daniel-mar 431
	if ($use_cache) {
2 daniel-mar 432
		$stats = yt_get_playlist_stats($playlist_id);
18 daniel-mar 433
		if (!$stats) {
12 daniel-mar 434
			fwrite(STDERR, "Cannot get stats for playlist with ID '$playlist_id'\n");
11 daniel-mar 435
			return;
436
		}
2 daniel-mar 437
		$videocount = $stats['itemCount'];
438
 
439
		$key = 'pid:'.$playlist_id;
440
 
441
		if (!isset($out[$key])) $out[$key] = array();
442
		$videocount_old = isset($out[$key]['count']) ? $out[$key]['count'] : -1;
443
	} else {
444
		$videocount = -1;
445
		$videocount_old = -2;
446
		$key = '';
447
	}
448
 
449
	if ($videocount_old != $videocount) { // Attention: This might not work if videos are deleted and added (= the count stays the same)
16 daniel-mar 450
		if ($verbose && $use_cache) echo "Video count changed from $videocount_old to $videocount\n";
2 daniel-mar 451
		$out[$key]['count'] = $videocount;
452
		$out[$key]['results'] = yt_playlist_items($playlist_id);
18 daniel-mar 453
		if (!$out[$key]['results']) {
19 daniel-mar 454
			// TODO: If a playlist has deleted all videos, then this message comes.
455
			//       However, technically this is not an error.
16 daniel-mar 456
			fwrite(STDERR, "Cannot get result for playlist with ID '$playlist_id'\n");
457
			return;
458
		}
2 daniel-mar 459
	} else {
460
		if ($verbose) echo "Video count for playlist is still $videocount, keep ".count($out[$key]['results'])." results.\n";
461
	}
462
 
463
	// Save the cache
464
 
465
	try {
16 daniel-mar 466
		if ($use_cache) file_put_contents(_getResultcache(), json_encode($out));
2 daniel-mar 467
	} catch(Exception $e) {
468
		fwrite(STDERR, "Cannot write result cache\n");
469
	}
470
 
471
	// Now download
472
 
18 daniel-mar 473
	if (!$out[$key]['results']) {
6 daniel-mar 474
		fwrite(STDERR, "Cannot get result for playlist with ID '$playlist_id'\n");
475
		return;
476
	}
2 daniel-mar 477
	foreach ($out[$key]['results'] as list($id, $title)) {
478
		if ($verbose) echo "Downloading '$title' as ".hf_type($type)." ...\n";
479
		ytdwn_video_id($id);
480
	}
481
}
482
 
483
function ytdwn_search($search, $order='', $maxresults=-1) {
484
	global $type;
485
	global $verbose;
486
 
487
	if ($verbose) echo "Searching for '$search' ...\n";
488
 
489
	// Perform the search and list the videos
490
 
491
	$results = yt_search_items($search, $order, $maxresults);
492
 
493
	// Now download
494
 
18 daniel-mar 495
	if (!$results) {
6 daniel-mar 496
		fwrite(STDERR, "Cannot get data for search '$search'\n");
497
		return;
498
	}
2 daniel-mar 499
	foreach ($results as list($id, $title)) {
500
		if ($verbose) echo "Downloading '$title' as ".hf_type($type)." ...\n";
501
		ytdwn_video_id($id);
502
	}
503
}
504
 
15 daniel-mar 505
function template_to_wildcard($template, $video_id) {
506
	$x = $template;
507
	$x = str_replace('%(id)s', $video_id, $x);
508
	$x = preg_replace('@%\\(.+\\)s@ismU', '*', $x);
509
	$x = preg_replace('@\\*+@', '*', $x);
510
	return $x;
511
}
512
 
513
function ytdwn_get_downloaded_filename($outputTemplate, $video_id) {
514
	if (strpos($outputTemplate, '%(id)s') === false) {
515
		// TODO: There needs to be a better way to find out the written file name !!!
516
		return false;
517
	} else {
518
		$wildcard = template_to_wildcard($outputTemplate, $video_id);
519
		$test = glob($wildcard);
520
		if (count($test) == 0) return false;
521
		return $test[0];
522
	}
523
}
524
 
2 daniel-mar 525
function ytdwn_video_id($video_id) {
526
	global $type;
527
	global $verbose;
528
	global $mp3id_transfer;
529
	global $extra_args;
530
	global $default_template;
531
	global $failTreshold;
6 daniel-mar 532
	global $cookie_file;
14 daniel-mar 533
	global $checksumMode;
2 daniel-mar 534
 
535
	if (DOWNLOAD_SIMULATION_MODE) {
536
		echo "SIMULATE download of video id $video_id as ".hf_type($type)." to "._getOutputDir()."\n";
537
		return;
538
	}
539
 
540
	if (!empty(_getAlreadyDownloaded()) && in_alreadydownloaded_file($type, $video_id)) {
541
		if ($verbose) echo "Video $video_id has already been downloaded. Skip.\n";
542
		return true;
543
	}
544
 
545
	if (!empty(_getFailList()) && (ytdwn_fail_counter($type, $video_id) >= $failTreshold)) {
546
		if ($verbose) echo "Video $video_id has failed too often. Skip.\n";
547
		return true;
548
	}
549
 
550
	$out = '';
551
	$code = -1;
552
 
553
	$outputTemplate = rtrim(_getOutputDir(), '/').'/'.$default_template;
554
 
555
	if (substr($type,0,2) == 'v:') {
556
		$format = substr($type,2);
557
		if (!empty($format)) {
20 daniel-mar 558
			$cmd = YTDL_EXE.' -o '.escapeshellarg($outputTemplate).' '.$extra_args.(empty($cookie_file) ? '' : ' --cookies '.$cookie_file).' '.escapeshellarg(vid_to_vurl($video_id)).' --format '.escapeshellarg($format);
559
			echo "$cmd\n";
560
			exec($cmd, $out, $code);
2 daniel-mar 561
		} else {
20 daniel-mar 562
			$cmd = YTDL_EXE.' -o '.escapeshellarg($outputTemplate).' '.$extra_args.(empty($cookie_file) ? '' : ' --cookies '.$cookie_file).' '.escapeshellarg(vid_to_vurl($video_id));
563
			echo "$cmd\n";
564
			exec($cmd, $out, $code);
2 daniel-mar 565
		}
15 daniel-mar 566
 
567
		$written_file = $code == 0 ? ytdwn_get_downloaded_filename($outputTemplate, $video_id) : false;
568
 
2 daniel-mar 569
	} else if (substr($type,0,2) == 'a:') {
570
		$format = substr($type,2);
571
		if (!empty($format)) {
20 daniel-mar 572
			$cmd = YTDL_EXE.' -o '.escapeshellarg($outputTemplate).' '.$extra_args.(empty($cookie_file) ? '' : ' --cookies '.$cookie_file).' '.escapeshellarg(vid_to_vurl($video_id)).' --extract-audio --audio-format '.escapeshellarg($format);
573
			echo "$cmd\n";
574
			exec($cmd, $out, $code);
2 daniel-mar 575
		} else {
20 daniel-mar 576
			$cmd = YTDL_EXE.' -o '.escapeshellarg($outputTemplate).' '.$extra_args.(empty($cookie_file) ? '' : ' --cookies '.$cookie_file).' '.escapeshellarg(vid_to_vurl($video_id)).' --extract-audio';
577
			echo "$cmd\n";
578
			exec($cmd, $out, $code);
2 daniel-mar 579
		}
580
 
15 daniel-mar 581
		$written_file = $code == 0 ? ytdwn_get_downloaded_filename($outputTemplate, $video_id) : false;
582
 
583
		if (($code == 0) && ($mp3id_transfer) && (strtolower($format) == 'mp3')) {
18 daniel-mar 584
			if (!$written_file) {
15 daniel-mar 585
				fwrite(STDERR, "Cannot include YouTube ID to MP3, because the default template does not contain '%(id)s', or the downloaded file could not be determined for another reason.\n");
586
			} else {
587
				mp3_transfer_vid_to_id($written_file, $video_id);
588
			}
589
		}
590
	} else {
591
		assert(false);
592
		return false;
593
	}
594
 
2 daniel-mar 595
	if ($code == 0) {
15 daniel-mar 596
		if ($verbose) {
597
			fwrite(STDOUT, "Successfully downloaded video ID $video_id as ".hf_type($type)."\n");
18 daniel-mar 598
			if ($written_file) fwrite(STDOUT, "Output file name: $written_file\n");
15 daniel-mar 599
		}
2 daniel-mar 600
		if (!empty(_getAlreadyDownloaded())) {
601
			try {
602
				addto_alreadydownloaded_file($type, $video_id);
603
			} catch(Exception $e) {
14 daniel-mar 604
				fwrite(STDERR, "Cannot add to 'already downloaded' file\n");
2 daniel-mar 605
			}
606
		}
14 daniel-mar 607
 
608
		// Now do the checksums
609
		foreach (explode(',',$checksumMode) as $mode) {
15 daniel-mar 610
			if (strtolower($mode) === 'none') continue;
18 daniel-mar 611
			if (!$written_file) {
15 daniel-mar 612
				fwrite(STDERR, "Cannot add to the '$mode' checksum file, because the default template does not contain '%(id)s', or the downloaded file could not be determined for another reason.\n");
613
			} else if (!cs_add_automatically($written_file, $mode)) {
14 daniel-mar 614
				fwrite(STDERR, "Could not write to '$mode' checksum file!\n");
615
			}
616
		}
2 daniel-mar 617
	} else {
618
		fwrite(STDERR, "Error downloading $video_id! (Code $code)\n");
619
		if (!empty(_getFailList())) {
620
			try {
621
				ytdwn_register_fail($type, $video_id, $code);
622
			} catch(Exception $e) {
623
				fwrite(STDERR, "Cannot register fail\n");
624
			}
625
		}
626
		return false;
627
	}
628
 
629
	return true;
630
}
631
 
632
function vid_to_vurl($video_id) {
7 daniel-mar 633
	return "https://www.youtube.com/watch?v=$video_id";
2 daniel-mar 634
}
635
 
636
function EndsWith($Haystack, $Needle){
637
	return strrpos($Haystack, $Needle) === strlen($Haystack)-strlen($Needle);
638
}
639
 
21 daniel-mar 640
/**
641
 * Tries to put the video ID into the MP3 meta tags, and then removes the ID
642
 * from the file name.
643
 * @param $written_file
644
 * @param $video_id
645
 * @return bool
646
 */
15 daniel-mar 647
function mp3_transfer_vid_to_id(&$written_file, $video_id) {
2 daniel-mar 648
	global $verbose;
649
	global $default_template;
650
 
651
	if (!command_exists('id3v2')) {
15 daniel-mar 652
		fwrite(STDERR, "Tool id3v2 is not installed. Will not transfer the YouTube ID into the MP3 ID Tag. Use paramter '-N' to stop trying the transfer.\n");
2 daniel-mar 653
		return false;
654
	}
655
 
15 daniel-mar 656
	$orig_ts = filemtime($written_file);
657
	$ec = -1;
658
	system('id3v2 -c '.escapeshellarg($video_id).' '.escapeshellarg($written_file), $ec);
659
	touch($written_file, $orig_ts);
660
	if ($ec != 0) {
661
		fwrite(STDERR, "Cannot set ID tag for file $written_file\n");
2 daniel-mar 662
		return false;
663
	}
664
 
15 daniel-mar 665
	$target_filename = $written_file;
2 daniel-mar 666
 
15 daniel-mar 667
	// Things like '<title>-<id>.mp3' become '<title>.mp3' (our default template)
668
	// But templates like '<title> (<id>).mp3' could become '<title> ().mp3', which is not nice
669
	// So, we try our best to find the most common template types...
670
	$target_filename = str_replace('-'.$video_id, '', $target_filename);
671
	$target_filename = str_replace('_'.$video_id, '', $target_filename);
672
	$target_filename = str_replace(' '.$video_id, '', $target_filename);
673
	$target_filename = str_replace('('.$video_id.')', '', $target_filename);
674
	$target_filename = str_replace('['.$video_id.']', '', $target_filename);
675
	$target_filename = str_replace($video_id, '', $target_filename); // must be the last!
676
	if ($target_filename === $written_file) {
677
		fwrite(STDERR, "Could not remove VideoID from filename '$written_file'\n"); // should not happen
678
		return false;
679
	}
2 daniel-mar 680
 
15 daniel-mar 681
	if (!intelligent_rename($written_file, $target_filename)) {
682
		fwrite(STDERR, "Could not rename '$written_file' to '$target_filename'\n");
683
		return false;
684
	}
2 daniel-mar 685
 
15 daniel-mar 686
	$written_file = $target_filename; // was modified by intelligent_rename()
687
	return true;
2 daniel-mar 688
}
689
 
690
function curl_to_cid($channel_url) {
13 daniel-mar 691
	return yt_get_channel_id_from_url($channel_url);
2 daniel-mar 692
}
693
 
694
function in_alreadydownloaded_file($type, $video_id) {
695
	$lines = file(_getAlreadyDownloaded());
696
	foreach ($lines as $line) {
697
		if (trim($line) == rtrim($type,':').':'.$video_id) {
698
			return true;
699
		}
700
	}
701
	return false;
702
}
703
 
704
function addto_alreadydownloaded_file($type, $video_id) {
705
	file_put_contents(_getAlreadyDownloaded(), rtrim($type,':').':'.$video_id."\n", FILE_APPEND);
706
}
707
 
708
function syntax_error($msg) {
709
	fwrite(STDERR, "Syntax error: ".trim($msg)."\n");
710
	fwrite(STDERR, "Please use argument '--help' to show the syntax rules.\n");
711
	exit(2);
712
}
713
 
714
function _help() {
715
	global $argv;
716
	$out = '';
717
	$own = file_get_contents($argv[0]);
718
	$help = explode('// ----', $own, 2)[0];
7 daniel-mar 719
	$m = null;
2 daniel-mar 720
	$help = preg_match_all('@^//(.*)$@mU', $help, $m);
721
	foreach ($m[1] as $line) {
722
		$out .= substr($line,1)."\n";
723
	}
724
	return $out;
725
}
726
 
727
function help() {
728
	echo _help();
729
	exit(0);
730
}
731
 
732
function version() {
733
	echo explode("\n\n", _help(), 2)[0]."\n";
734
	exit(0);
735
}
736
 
737
function command_exists($command) {
738
	// https://stackoverflow.com/questions/592620/check-if-a-program-exists-from-a-bash-script
739
 
740
	$ec = -1;
741
	system('command -v '.escapeshellarg($command).' > /dev/null', $ec);
742
	return ($ec == 0);
743
}
744
 
745
function hf_type($type) {
746
	if (strpos($type, ':') === false) return $type; // invalid type (missing ':')
747
	list($av, $format) = explode(':', $type);
748
 
749
	if ($av == 'a') $av = 'audio';
750
	else if ($av == 'v') $av = 'video';
751
	else return $type; // invalid type
752
 
753
	return (!empty($format)) ? $format.'-'.$av : $av;
754
}
755
 
756
function expand_tilde($path) {
757
	// Source: http://jonathonhill.net/2013-09-03/tilde-expansion-in-php/
758
 
759
	if (function_exists('posix_getuid') && strpos($path, '~') !== false) {
760
		$info = posix_getpwuid(posix_getuid());
761
		$path = str_replace('~', $info['dir'], $path);
762
	}
763
 
764
	return $path;
765
}
766
 
767
function _getLastListname() {
768
	global $listFilenameStack;
769
	$listname = ''; // default
770
	if (count($listFilenameStack) > 0) {
771
		$listname = $listFilenameStack[count($listFilenameStack)-1];
772
		$listname = pathinfo($listname, PATHINFO_FILENAME); // remove file extension, e.g. ".list"
773
	}
774
	return $listname;
775
}
776
 
777
function _getApiKey() {
778
	global $apikey;
779
 
780
	$out = $apikey;
781
	if (empty($out)) {
782
		$auto_api_key = AUTO_API_KEY;
783
		$auto_api_key = expand_tilde($auto_api_key);
784
		$auto_api_key = str_replace('[listname]', _getLastListname(), $auto_api_key);
785
 
786
		if (file_exists($auto_api_key)) {
787
			$out = trim(file_get_contents($auto_api_key));
788
		} else {
789
			syntax_error("Please specify a YouTube API key with argument '-A'.");
790
		}
791
	} else {
792
		$out = str_replace('[listname]', _getLastListname(), $out);
793
		$out = expand_tilde($out);
794
 
795
		if (file_exists($out)) {
796
			$out = trim(file_get_contents($out));
797
		} else {
798
			// Assume, $out is a key, not a file
799
		}
800
	}
801
 
802
	if (!yt_check_apikey_syntax($out)) syntax_error("'$out' is not a valid API key, not an existing file containing an API key.\n");
803
 
804
	return $out;
805
}
806
 
807
function _getResultCache() {
808
	global $resultcache;
809
	if (empty($resultcache)) return '';
810
 
811
	$out = expand_tilde($resultcache);
812
 
813
	$out = str_replace('[listname]', _getLastListname(), $out);
814
 
815
	if (!file_exists($out)) {
816
		@touch($out);
817
		if (!file_exists($out)) {
818
			fwrite(STDERR, "File '$out' cannot be created. Disable feature.\n");
819
			return '';
820
		}
821
	}
822
 
823
	return $out;
824
}
825
 
826
function _getAlreadyDownloaded() {
827
	global $alreadyDownloaded;
828
	if (empty($alreadyDownloaded)) return '';
829
 
830
	$out = expand_tilde($alreadyDownloaded);
831
 
832
	$out = str_replace('[listname]', _getLastListname(), $out);
833
 
834
	if (!file_exists($out)) {
835
		@touch($out);
836
		if (!file_exists($out)) {
837
			fwrite(STDERR, "File '$out' cannot be created. Disable feature.\n");
838
			return '';
839
		}
840
	}
841
 
842
	return $out;
843
}
844
 
845
function _getFailList() {
846
	global $failList;
847
	if (empty($failList)) return '';
848
 
849
	$out = expand_tilde($failList);
850
 
851
	$out = str_replace('[listname]', _getLastListname(), $out);
852
 
853
	if (!file_exists($out)) {
854
		@touch($out);
855
		if (!file_exists($out)) {
856
			fwrite(STDERR, "File '$out' cannot be created. Disable feature.\n");
857
			return '';
858
		}
859
	}
860
 
861
	return $out;
862
}
863
 
864
function _getOutputDir() {
865
	global $outputDir, $allow_creation_outputdir;
866
	if (empty($outputDir)) return '.';
867
 
868
	$out = expand_tilde($outputDir);
869
 
870
	$out = str_replace('[listname]', _getLastListname(), $out);
871
 
872
	if ($allow_creation_outputdir) {
873
		if (!is_dir($out)) {
12 daniel-mar 874
			mkdir($out, 0777, true);
2 daniel-mar 875
			if (!is_dir($out)) {
876
				fwrite(STDERR, "Output directory '$out' does not exist.\n");
877
				exit(1);
878
			}
879
		}
880
	} else {
881
		if (!is_dir($out)) {
882
			fwrite(STDERR, "Output directory '$out' does not exist.\n");
883
			exit(1);
884
		}
885
	}
886
 
887
	return $out;
888
}
889
 
890
function parse_quoting($str) {
891
	if ((substr($str,0,1) == '"') && (substr($str,-1,1) == '"')) {
892
		$str = substr($str,1,strlen($str)-2);
893
 
894
		$escape = false;
895
		$out = '';
896
		for ($i=0; $i<strlen($str); $i++) {
897
			$char = $str[$i];
898
 
899
			if ($char == '\\') {
900
				if ($escape) {
901
					$out .= $char;
902
					$escape = false;
903
				} else {
904
					$escape = true;
905
				}
906
			} else {
907
				$out .= $char;
908
			}
909
 
910
		}
911
		$str = $out;
912
 
913
	}
914
 
915
	return $str;
916
}
917
 
918
function ytdwn_register_fail($type, $video_id, $code) {
919
	// Note: Error code $code ist currently not used
920
 
921
	$file = _getFailList();
922
	$cont = file_get_contents($file);
7 daniel-mar 923
	$m = null;
2 daniel-mar 924
	if (preg_match("@Video ID ".preg_quote($video_id,'@')." failed (\d+) time\(s\) with type ".preg_quote($type,'@')."@ismU", $cont, $m)) {
925
		$cont = preg_replace("@Video ID ".preg_quote($video_id,'@')." failed (\d+) time\(s\) with type ".preg_quote($type,'@')."@ismU",
19 daniel-mar 926
		                     "Video ID $video_id failed ".(((int)$m[1])+1)." time(s) with type $type", $cont);
2 daniel-mar 927
		file_put_contents($file, $cont);
928
	} else {
929
		file_put_contents($file, "Video ID $video_id failed 1 time(s) with type $type\n", FILE_APPEND);
930
	}
931
}
932
 
933
function ytdwn_fail_counter($type, $video_id) {
934
	$file = _getFailList();
935
	$cont = file_get_contents($file);
7 daniel-mar 936
	$m = null;
2 daniel-mar 937
	if (preg_match("@Video ID ".preg_quote($video_id,'@')." failed (\d+) time\(s\) with type ".preg_quote($type,'@')."@ismU", $cont, $m)) {
938
		return $m[1];
939
	} else {
940
		return 0;
941
	}
942
}
943
 
21 daniel-mar 944
/**
945
 * @param string $fileName
946
 * @param bool $caseSensitive
947
 * @return bool
948
 * @see https://stackoverflow.com/questions/3964793/php-case-insensitive-version-of-file-exists
949
 */
950
function file_exists_ext(string $fileName, bool $caseSensitive = true): bool {
951
	if(file_exists($fileName)) {
952
		return true; // $fileName;
953
	}
954
	if($caseSensitive) return false;
955
 
956
	// Handle case insensitive requests
957
	$directoryName = dirname($fileName);
958
	$fileArray = glob($directoryName . '/*', GLOB_NOSORT);
959
	$fileNameLowerCase = strtolower($fileName);
960
	foreach($fileArray as $file) {
961
		if(strtolower($file) == $fileNameLowerCase) {
962
			return true; // $file;
963
		}
964
	}
965
	return false;
966
}
967
 
15 daniel-mar 968
function intelligent_rename($src, &$dest) {
2 daniel-mar 969
	$pos = strrpos($dest, '.');
970
	$ext = substr($dest, $pos);
971
	$namewoext = substr($dest, 0, $pos);
21 daniel-mar 972
	$duplicate_count = 1;
2 daniel-mar 973
	$dest_neu = $dest;
21 daniel-mar 974
	while (file_exists_ext($dest_neu, false)) {
975
		$duplicate_count++;
976
		$dest_neu = "$namewoext ($duplicate_count)$ext";
2 daniel-mar 977
	}
15 daniel-mar 978
	$res = rename($src, $dest_neu);
979
	if ($res) $dest = $dest_neu;
980
	return $res;
2 daniel-mar 981
}
5 daniel-mar 982
 
16 daniel-mar 983
function download_latest_downloader() {
984
	$download_url = false;
985
 
986
	if (DOWNLOAD_YT_FORK == 'yt-dlp') {
987
		$ch = curl_init();
988
		curl_setopt($ch, CURLOPT_URL, 'https://github.com/yt-dlp/yt-dlp/releases/latest/download/SHA2-256SUMS');
989
		#curl_setopt($ch, CURLOPT_HEADER, false);
990
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
991
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
992
		$cont = curl_exec($ch);
993
		$m = null;
994
		if (preg_match('@^(.+)\s+yt-dlp$@ismU', $cont, $m)) {
995
			$newest_version_sha2 = $m[1];
996
		} else {
997
			$newest_version_sha2 = false;
998
		}
999
 
1000
		if (!$newest_version_sha2) {
1001
			fwrite(STDERR, "Failed to get SHA2 sum of latest version of '".DOWNLOAD_YT_FORK."' online. Will not try to download/update '".DOWNLOAD_YT_FORK."' into local directory.\n");
1002
		} else {
1003
			if (!file_exists(__DIR__.'/'.DOWNLOAD_YT_FORK) || ($newest_version_sha2 != hash_file('sha256',__DIR__.'/'.DOWNLOAD_YT_FORK))) {
1004
				$download_url = 'https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp';
1005
			}
1006
		}
5 daniel-mar 1007
	}
16 daniel-mar 1008
 
1009
	if (DOWNLOAD_YT_FORK == 'youtube-dlc') {
1010
		// TODO: What is the difference between these two? Which one should be chosen?!
1011
		// https://github.com/blackjack4494/yt-dlc (18372 commits, last commit 23 Aug 2021, last release 2020.11.11-3)
1012
		// https://github.com/blackjack4494/youtube-dlc (18888 commits, last commit 26 Jul 2021, last release 2020.10.09)
1013
 
1014
		// TODO: yt-dlc The sha256 does not fit to the binary! Therefore, it is always downloaded!
1015
		// TODO: youtube-dlc has no hashes online...
1016
		/*
1017
		$ch = curl_init();
1018
		curl_setopt($ch, CURLOPT_URL, 'https://github.com/blackjack4494/yt-dlc/releases/latest/download/SHA2-256SUMS');
1019
		#curl_setopt($ch, CURLOPT_HEADER, false);
1020
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
1021
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
1022
		$cont = curl_exec($ch);
1023
		$m = null;
1024
		if (preg_match('@^youtube\-dlc:(.+)$@ismU', $cont, $m)) {
1025
			$newest_version_sha2 = $m[1];
1026
		} else {
1027
			$newest_version_sha2 = false;
1028
		}
1029
 
1030
		if (!$newest_version_sha2) {
1031
			fwrite(STDERR, "Failed to get SHA2 sum of latest version of '".DOWNLOAD_YT_FORK."' online. Will not try to download/update '".DOWNLOAD_YT_FORK."' into local directory.\n");
1032
		} else {
1033
			if (!file_exists(__DIR__.'/'.DOWNLOAD_YT_FORK) || ($newest_version_sha2 != hash_file('sha256',__DIR__.'/'.DOWNLOAD_YT_FORK))) {
1034
				$download_url = 'https://github.com/blackjack4494/yt-dlc/releases/latest/download/youtube-dlc';
1035
			}
1036
		}
1037
		*/
1038
 
1039
		if (!file_exists(__DIR__.'/'.DOWNLOAD_YT_FORK) || (time()-filemtime(__DIR__.'/'.DOWNLOAD_YT_FORK) > 24*60*60)) {
1040
			$download_url = 'https://github.com/blackjack4494/yt-dlc/releases/latest/download/youtube-dlc';
1041
		}
1042
	}
1043
 
1044
	if (DOWNLOAD_YT_FORK == 'youtube-dl') {
1045
		$ch = curl_init();
1046
		curl_setopt($ch, CURLOPT_URL, 'https://yt-dl.org/downloads/latest/MD5SUMS');
1047
		#curl_setopt($ch, CURLOPT_HEADER, false);
1048
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
1049
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
1050
		$cont = curl_exec($ch);
1051
		$m = null;
1052
		if (preg_match('@^(.+)  youtube\-dl$@ismU', $cont, $m)) {
1053
			$newest_version_md5 = $m[1];
1054
		} else {
1055
			$newest_version_md5 = false;
1056
		}
1057
 
1058
		if (!$newest_version_md5) {
1059
			fwrite(STDERR, "Failed to get MD5 sum of latest version of '".DOWNLOAD_YT_FORK."' online. Will not try to download/update '".DOWNLOAD_YT_FORK."' into local directory.\n");
1060
		} else {
1061
			if (!file_exists(__DIR__.'/'.DOWNLOAD_YT_FORK) || ($newest_version_md5 != md5_file(__DIR__.'/'.DOWNLOAD_YT_FORK))) {
1062
				$download_url = 'https://yt-dl.org/latest/youtube-dl';
1063
			}
1064
		}
1065
	}
1066
 
18 daniel-mar 1067
	if ($download_url) {
16 daniel-mar 1068
		// Try to download/update the file in our directory. It should be the newest available, since YT often breaks downloader
1069
		if (file_exists(__DIR__.'/'.DOWNLOAD_YT_FORK)) {
1070
			echo "Trying to update '".DOWNLOAD_YT_FORK."' in local directory...\n";
1071
		} else {
1072
			echo "Trying to download '".DOWNLOAD_YT_FORK."' into local directory...\n";
1073
		}
1074
 
1075
		@chmod(__DIR__.'/'.DOWNLOAD_YT_FORK, 0777); // otherwise we might not be able to write to it
1076
 
1077
		if (!($binary = file_get_contents($download_url))) {
1078
			fwrite(STDERR, "Failed to download '".DOWNLOAD_YT_FORK."' into local directory (file_get_contents).\n");
1079
		} else if (!@file_put_contents(__DIR__.'/'.DOWNLOAD_YT_FORK, $binary)) {
1080
			fwrite(STDERR, "Failed to download '".DOWNLOAD_YT_FORK."' into local directory (file_put_contents).\n");
1081
		} else {
1082
			if (!@chmod(__DIR__.'/'.DOWNLOAD_YT_FORK, 0544)) {
1083
				fwrite(STDERR, "Failed to download '".DOWNLOAD_YT_FORK."' into local directory (chmod 544).\n");
1084
				@unlink(__DIR__.'/'.DOWNLOAD_YT_FORK); // try to delete, otherwise we might try to execute a non-executable file
1085
			}
1086
		}
1087
	}
5 daniel-mar 1088
}