Subversion Repositories yt_downloader

Rev

Rev 19 | Rev 21 | 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
 
20 daniel-mar 4
// ViaThinkSoft YouTube Downloader Util 2.4
5
// Revision: 2022-12-27
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
 
15 daniel-mar 640
function mp3_transfer_vid_to_id(&$written_file, $video_id) {
2 daniel-mar 641
	global $verbose;
642
	global $default_template;
643
 
644
	if (!command_exists('id3v2')) {
15 daniel-mar 645
		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 646
		return false;
647
	}
648
 
15 daniel-mar 649
	$orig_ts = filemtime($written_file);
650
	$ec = -1;
651
	system('id3v2 -c '.escapeshellarg($video_id).' '.escapeshellarg($written_file), $ec);
652
	touch($written_file, $orig_ts);
653
	if ($ec != 0) {
654
		fwrite(STDERR, "Cannot set ID tag for file $written_file\n");
2 daniel-mar 655
		return false;
656
	}
657
 
15 daniel-mar 658
	$target_filename = $written_file;
2 daniel-mar 659
 
15 daniel-mar 660
	// Things like '<title>-<id>.mp3' become '<title>.mp3' (our default template)
661
	// But templates like '<title> (<id>).mp3' could become '<title> ().mp3', which is not nice
662
	// So, we try our best to find the most common template types...
663
	$target_filename = str_replace('-'.$video_id, '', $target_filename);
664
	$target_filename = str_replace('_'.$video_id, '', $target_filename);
665
	$target_filename = str_replace(' '.$video_id, '', $target_filename);
666
	$target_filename = str_replace('('.$video_id.')', '', $target_filename);
667
	$target_filename = str_replace('['.$video_id.']', '', $target_filename);
668
	$target_filename = str_replace($video_id, '', $target_filename); // must be the last!
669
	if ($target_filename === $written_file) {
670
		fwrite(STDERR, "Could not remove VideoID from filename '$written_file'\n"); // should not happen
671
		return false;
672
	}
2 daniel-mar 673
 
15 daniel-mar 674
	if (!intelligent_rename($written_file, $target_filename)) {
675
		fwrite(STDERR, "Could not rename '$written_file' to '$target_filename'\n");
676
		return false;
677
	}
2 daniel-mar 678
 
15 daniel-mar 679
	$written_file = $target_filename; // was modified by intelligent_rename()
680
	return true;
2 daniel-mar 681
}
682
 
683
function curl_to_cid($channel_url) {
13 daniel-mar 684
	return yt_get_channel_id_from_url($channel_url);
2 daniel-mar 685
}
686
 
687
function in_alreadydownloaded_file($type, $video_id) {
688
	$lines = file(_getAlreadyDownloaded());
689
	foreach ($lines as $line) {
690
		if (trim($line) == rtrim($type,':').':'.$video_id) {
691
			return true;
692
		}
693
	}
694
	return false;
695
}
696
 
697
function addto_alreadydownloaded_file($type, $video_id) {
698
	file_put_contents(_getAlreadyDownloaded(), rtrim($type,':').':'.$video_id."\n", FILE_APPEND);
699
}
700
 
701
function syntax_error($msg) {
702
	fwrite(STDERR, "Syntax error: ".trim($msg)."\n");
703
	fwrite(STDERR, "Please use argument '--help' to show the syntax rules.\n");
704
	exit(2);
705
}
706
 
707
function _help() {
708
	global $argv;
709
	$out = '';
710
	$own = file_get_contents($argv[0]);
711
	$help = explode('// ----', $own, 2)[0];
7 daniel-mar 712
	$m = null;
2 daniel-mar 713
	$help = preg_match_all('@^//(.*)$@mU', $help, $m);
714
	foreach ($m[1] as $line) {
715
		$out .= substr($line,1)."\n";
716
	}
717
	return $out;
718
}
719
 
720
function help() {
721
	echo _help();
722
	exit(0);
723
}
724
 
725
function version() {
726
	echo explode("\n\n", _help(), 2)[0]."\n";
727
	exit(0);
728
}
729
 
730
function command_exists($command) {
731
	// https://stackoverflow.com/questions/592620/check-if-a-program-exists-from-a-bash-script
732
 
733
	$ec = -1;
734
	system('command -v '.escapeshellarg($command).' > /dev/null', $ec);
735
	return ($ec == 0);
736
}
737
 
738
function hf_type($type) {
739
	if (strpos($type, ':') === false) return $type; // invalid type (missing ':')
740
	list($av, $format) = explode(':', $type);
741
 
742
	if ($av == 'a') $av = 'audio';
743
	else if ($av == 'v') $av = 'video';
744
	else return $type; // invalid type
745
 
746
	return (!empty($format)) ? $format.'-'.$av : $av;
747
}
748
 
749
function expand_tilde($path) {
750
	// Source: http://jonathonhill.net/2013-09-03/tilde-expansion-in-php/
751
 
752
	if (function_exists('posix_getuid') && strpos($path, '~') !== false) {
753
		$info = posix_getpwuid(posix_getuid());
754
		$path = str_replace('~', $info['dir'], $path);
755
	}
756
 
757
	return $path;
758
}
759
 
760
function _getLastListname() {
761
	global $listFilenameStack;
762
	$listname = ''; // default
763
	if (count($listFilenameStack) > 0) {
764
		$listname = $listFilenameStack[count($listFilenameStack)-1];
765
		$listname = pathinfo($listname, PATHINFO_FILENAME); // remove file extension, e.g. ".list"
766
	}
767
	return $listname;
768
}
769
 
770
function _getApiKey() {
771
	global $apikey;
772
 
773
	$out = $apikey;
774
	if (empty($out)) {
775
		$auto_api_key = AUTO_API_KEY;
776
		$auto_api_key = expand_tilde($auto_api_key);
777
		$auto_api_key = str_replace('[listname]', _getLastListname(), $auto_api_key);
778
 
779
		if (file_exists($auto_api_key)) {
780
			$out = trim(file_get_contents($auto_api_key));
781
		} else {
782
			syntax_error("Please specify a YouTube API key with argument '-A'.");
783
		}
784
	} else {
785
		$out = str_replace('[listname]', _getLastListname(), $out);
786
		$out = expand_tilde($out);
787
 
788
		if (file_exists($out)) {
789
			$out = trim(file_get_contents($out));
790
		} else {
791
			// Assume, $out is a key, not a file
792
		}
793
	}
794
 
795
	if (!yt_check_apikey_syntax($out)) syntax_error("'$out' is not a valid API key, not an existing file containing an API key.\n");
796
 
797
	return $out;
798
}
799
 
800
function _getResultCache() {
801
	global $resultcache;
802
	if (empty($resultcache)) return '';
803
 
804
	$out = expand_tilde($resultcache);
805
 
806
	$out = str_replace('[listname]', _getLastListname(), $out);
807
 
808
	if (!file_exists($out)) {
809
		@touch($out);
810
		if (!file_exists($out)) {
811
			fwrite(STDERR, "File '$out' cannot be created. Disable feature.\n");
812
			return '';
813
		}
814
	}
815
 
816
	return $out;
817
}
818
 
819
function _getAlreadyDownloaded() {
820
	global $alreadyDownloaded;
821
	if (empty($alreadyDownloaded)) return '';
822
 
823
	$out = expand_tilde($alreadyDownloaded);
824
 
825
	$out = str_replace('[listname]', _getLastListname(), $out);
826
 
827
	if (!file_exists($out)) {
828
		@touch($out);
829
		if (!file_exists($out)) {
830
			fwrite(STDERR, "File '$out' cannot be created. Disable feature.\n");
831
			return '';
832
		}
833
	}
834
 
835
	return $out;
836
}
837
 
838
function _getFailList() {
839
	global $failList;
840
	if (empty($failList)) return '';
841
 
842
	$out = expand_tilde($failList);
843
 
844
	$out = str_replace('[listname]', _getLastListname(), $out);
845
 
846
	if (!file_exists($out)) {
847
		@touch($out);
848
		if (!file_exists($out)) {
849
			fwrite(STDERR, "File '$out' cannot be created. Disable feature.\n");
850
			return '';
851
		}
852
	}
853
 
854
	return $out;
855
}
856
 
857
function _getOutputDir() {
858
	global $outputDir, $allow_creation_outputdir;
859
	if (empty($outputDir)) return '.';
860
 
861
	$out = expand_tilde($outputDir);
862
 
863
	$out = str_replace('[listname]', _getLastListname(), $out);
864
 
865
	if ($allow_creation_outputdir) {
866
		if (!is_dir($out)) {
12 daniel-mar 867
			mkdir($out, 0777, true);
2 daniel-mar 868
			if (!is_dir($out)) {
869
				fwrite(STDERR, "Output directory '$out' does not exist.\n");
870
				exit(1);
871
			}
872
		}
873
	} else {
874
		if (!is_dir($out)) {
875
			fwrite(STDERR, "Output directory '$out' does not exist.\n");
876
			exit(1);
877
		}
878
	}
879
 
880
	return $out;
881
}
882
 
883
function parse_quoting($str) {
884
	if ((substr($str,0,1) == '"') && (substr($str,-1,1) == '"')) {
885
		$str = substr($str,1,strlen($str)-2);
886
 
887
		$escape = false;
888
		$out = '';
889
		for ($i=0; $i<strlen($str); $i++) {
890
			$char = $str[$i];
891
 
892
			if ($char == '\\') {
893
				if ($escape) {
894
					$out .= $char;
895
					$escape = false;
896
				} else {
897
					$escape = true;
898
				}
899
			} else {
900
				$out .= $char;
901
			}
902
 
903
		}
904
		$str = $out;
905
 
906
	}
907
 
908
	return $str;
909
}
910
 
911
function ytdwn_register_fail($type, $video_id, $code) {
912
	// Note: Error code $code ist currently not used
913
 
914
	$file = _getFailList();
915
	$cont = file_get_contents($file);
7 daniel-mar 916
	$m = null;
2 daniel-mar 917
	if (preg_match("@Video ID ".preg_quote($video_id,'@')." failed (\d+) time\(s\) with type ".preg_quote($type,'@')."@ismU", $cont, $m)) {
918
		$cont = preg_replace("@Video ID ".preg_quote($video_id,'@')." failed (\d+) time\(s\) with type ".preg_quote($type,'@')."@ismU",
19 daniel-mar 919
		                     "Video ID $video_id failed ".(((int)$m[1])+1)." time(s) with type $type", $cont);
2 daniel-mar 920
		file_put_contents($file, $cont);
921
	} else {
922
		file_put_contents($file, "Video ID $video_id failed 1 time(s) with type $type\n", FILE_APPEND);
923
	}
924
}
925
 
926
function ytdwn_fail_counter($type, $video_id) {
927
	$file = _getFailList();
928
	$cont = file_get_contents($file);
7 daniel-mar 929
	$m = null;
2 daniel-mar 930
	if (preg_match("@Video ID ".preg_quote($video_id,'@')." failed (\d+) time\(s\) with type ".preg_quote($type,'@')."@ismU", $cont, $m)) {
931
		return $m[1];
932
	} else {
933
		return 0;
934
	}
935
}
936
 
15 daniel-mar 937
function intelligent_rename($src, &$dest) {
2 daniel-mar 938
	$pos = strrpos($dest, '.');
939
	$ext = substr($dest, $pos);
940
	$namewoext = substr($dest, 0, $pos);
941
	$failcnt = 1;
942
	$dest_neu = $dest;
943
	while (file_exists($dest_neu)) {
944
		$failcnt++;
945
		$dest_neu = "$namewoext ($failcnt)$ext";
946
	}
15 daniel-mar 947
	$res = rename($src, $dest_neu);
948
	if ($res) $dest = $dest_neu;
949
	return $res;
2 daniel-mar 950
}
5 daniel-mar 951
 
16 daniel-mar 952
function download_latest_downloader() {
953
	$download_url = false;
954
 
955
	if (DOWNLOAD_YT_FORK == 'yt-dlp') {
956
		$ch = curl_init();
957
		curl_setopt($ch, CURLOPT_URL, 'https://github.com/yt-dlp/yt-dlp/releases/latest/download/SHA2-256SUMS');
958
		#curl_setopt($ch, CURLOPT_HEADER, false);
959
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
960
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
961
		$cont = curl_exec($ch);
962
		$m = null;
963
		if (preg_match('@^(.+)\s+yt-dlp$@ismU', $cont, $m)) {
964
			$newest_version_sha2 = $m[1];
965
		} else {
966
			$newest_version_sha2 = false;
967
		}
968
 
969
		if (!$newest_version_sha2) {
970
			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");
971
		} else {
972
			if (!file_exists(__DIR__.'/'.DOWNLOAD_YT_FORK) || ($newest_version_sha2 != hash_file('sha256',__DIR__.'/'.DOWNLOAD_YT_FORK))) {
973
				$download_url = 'https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp';
974
			}
975
		}
5 daniel-mar 976
	}
16 daniel-mar 977
 
978
	if (DOWNLOAD_YT_FORK == 'youtube-dlc') {
979
		// TODO: What is the difference between these two? Which one should be chosen?!
980
		// https://github.com/blackjack4494/yt-dlc (18372 commits, last commit 23 Aug 2021, last release 2020.11.11-3)
981
		// https://github.com/blackjack4494/youtube-dlc (18888 commits, last commit 26 Jul 2021, last release 2020.10.09)
982
 
983
		// TODO: yt-dlc The sha256 does not fit to the binary! Therefore, it is always downloaded!
984
		// TODO: youtube-dlc has no hashes online...
985
		/*
986
		$ch = curl_init();
987
		curl_setopt($ch, CURLOPT_URL, 'https://github.com/blackjack4494/yt-dlc/releases/latest/download/SHA2-256SUMS');
988
		#curl_setopt($ch, CURLOPT_HEADER, false);
989
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
990
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
991
		$cont = curl_exec($ch);
992
		$m = null;
993
		if (preg_match('@^youtube\-dlc:(.+)$@ismU', $cont, $m)) {
994
			$newest_version_sha2 = $m[1];
995
		} else {
996
			$newest_version_sha2 = false;
997
		}
998
 
999
		if (!$newest_version_sha2) {
1000
			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");
1001
		} else {
1002
			if (!file_exists(__DIR__.'/'.DOWNLOAD_YT_FORK) || ($newest_version_sha2 != hash_file('sha256',__DIR__.'/'.DOWNLOAD_YT_FORK))) {
1003
				$download_url = 'https://github.com/blackjack4494/yt-dlc/releases/latest/download/youtube-dlc';
1004
			}
1005
		}
1006
		*/
1007
 
1008
		if (!file_exists(__DIR__.'/'.DOWNLOAD_YT_FORK) || (time()-filemtime(__DIR__.'/'.DOWNLOAD_YT_FORK) > 24*60*60)) {
1009
			$download_url = 'https://github.com/blackjack4494/yt-dlc/releases/latest/download/youtube-dlc';
1010
		}
1011
	}
1012
 
1013
	if (DOWNLOAD_YT_FORK == 'youtube-dl') {
1014
		$ch = curl_init();
1015
		curl_setopt($ch, CURLOPT_URL, 'https://yt-dl.org/downloads/latest/MD5SUMS');
1016
		#curl_setopt($ch, CURLOPT_HEADER, false);
1017
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
1018
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
1019
		$cont = curl_exec($ch);
1020
		$m = null;
1021
		if (preg_match('@^(.+)  youtube\-dl$@ismU', $cont, $m)) {
1022
			$newest_version_md5 = $m[1];
1023
		} else {
1024
			$newest_version_md5 = false;
1025
		}
1026
 
1027
		if (!$newest_version_md5) {
1028
			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");
1029
		} else {
1030
			if (!file_exists(__DIR__.'/'.DOWNLOAD_YT_FORK) || ($newest_version_md5 != md5_file(__DIR__.'/'.DOWNLOAD_YT_FORK))) {
1031
				$download_url = 'https://yt-dl.org/latest/youtube-dl';
1032
			}
1033
		}
1034
	}
1035
 
18 daniel-mar 1036
	if ($download_url) {
16 daniel-mar 1037
		// Try to download/update the file in our directory. It should be the newest available, since YT often breaks downloader
1038
		if (file_exists(__DIR__.'/'.DOWNLOAD_YT_FORK)) {
1039
			echo "Trying to update '".DOWNLOAD_YT_FORK."' in local directory...\n";
1040
		} else {
1041
			echo "Trying to download '".DOWNLOAD_YT_FORK."' into local directory...\n";
1042
		}
1043
 
1044
		@chmod(__DIR__.'/'.DOWNLOAD_YT_FORK, 0777); // otherwise we might not be able to write to it
1045
 
1046
		if (!($binary = file_get_contents($download_url))) {
1047
			fwrite(STDERR, "Failed to download '".DOWNLOAD_YT_FORK."' into local directory (file_get_contents).\n");
1048
		} else if (!@file_put_contents(__DIR__.'/'.DOWNLOAD_YT_FORK, $binary)) {
1049
			fwrite(STDERR, "Failed to download '".DOWNLOAD_YT_FORK."' into local directory (file_put_contents).\n");
1050
		} else {
1051
			if (!@chmod(__DIR__.'/'.DOWNLOAD_YT_FORK, 0544)) {
1052
				fwrite(STDERR, "Failed to download '".DOWNLOAD_YT_FORK."' into local directory (chmod 544).\n");
1053
				@unlink(__DIR__.'/'.DOWNLOAD_YT_FORK); // try to delete, otherwise we might try to execute a non-executable file
1054
			}
1055
		}
1056
	}
5 daniel-mar 1057
}