Subversion Repositories yt_downloader

Rev

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