Subversion Repositories yt_downloader

Rev

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