Subversion Repositories yt_downloader

Rev

Rev 15 | Rev 17 | Go to most recent revision | Show entire file | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 15 Rev 16
Line 1... Line 1...
1
#!/usr/bin/php
1
#!/usr/bin/php
2
<?php
2
<?php
3
 
3
 
4
// ViaThinkSoft YouTube Downloader Util 2.3
4
// ViaThinkSoft YouTube Downloader Util 2.3.1
5
// Revision: 2022-02-07
5
// Revision: 2022-02-07
6
// Author: Daniel Marschall <www.daniel-marschall.de>
6
// Author: Daniel Marschall <www.daniel-marschall.de>
7
// Licensed under the terms of the Apache 2.0 License
7
// Licensed under the terms of the Apache 2.0 License
8
//
8
//
9
// For syntax and other documentation, please read the file README.
9
// For syntax and other documentation, please read the file README.
Line 52... Line 52...
52
//            '-k ' . // The additional "-k" option in the above makes youtube-dl keep downloaded videos.
52
//            '-k ' . // The additional "-k" option in the above makes youtube-dl keep downloaded videos.
53
              '-i ' . // continue upon download errors
53
              '-i ' . // continue upon download errors
54
              '-c ';  // resume partially downloaded video files
54
              '-c ';  // resume partially downloaded video files
55
$default_template = '%(title)s-%(id)s.%(ext)s';
55
$default_template = '%(title)s-%(id)s.%(ext)s';
56
$cookie_file = AUTO_COOKIE_FILE;
56
$cookie_file = AUTO_COOKIE_FILE;
-
 
57
$downloader = 'yt-dlp';
57
 
58
 
58
// Parse arguments
59
// Parse arguments
59
// 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
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
60
 
61
 
61
$init_extra_args = false;
62
$init_extra_args = false;
Line 108... Line 109...
108
		$apikey = file_exists($m[3]) ? trim(file_get_contents($m[3])) : $m[3];
109
		$apikey = file_exists($m[3]) ? trim(file_get_contents($m[3])) : $m[3];
109
	} else if (preg_match('@^(\-\-cookies)(\s+|=)(.*)$@s', $arg2, $m)) {
110
	} else if (preg_match('@^(\-\-cookies)(\s+|=)(.*)$@s', $arg2, $m)) {
110
		array_shift($argv_bak);
111
		array_shift($argv_bak);
111
		if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
112
		if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
112
		$cookie_file = file_exists($m[3]) ? trim(file_get_contents($m[3])) : $m[3];
113
		$cookie_file = file_exists($m[3]) ? trim(file_get_contents($m[3])) : $m[3];
-
 
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];
113
	} else if (preg_match('@^(/X|\-X|\-\-extra-args)(\s+|=)(.*)$@s', $arg2, $m)) {
118
	} else if (preg_match('@^(/X|\-X|\-\-extra-args)(\s+|=)(.*)$@s', $arg2, $m)) {
114
		array_shift($argv_bak);
119
		array_shift($argv_bak);
115
		if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
120
		if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
116
		if ($init_extra_args) {
121
		if ($init_extra_args) {
117
			$extra_args .= ' ' . $m[3]; // user has multiple "-X" arguments. append.
122
			$extra_args .= ' ' . $m[3]; // user has multiple "-X" arguments. append.
Line 144... Line 149...
144
}
149
}
145
unset($arg);
150
unset($arg);
146
unset($argv_bak);
151
unset($argv_bak);
147
unset($init_extra_args);
152
unset($init_extra_args);
148
 
153
 
-
 
154
define('DOWNLOAD_YT_FORK', $downloader);
-
 
155
 
149
// Validity checks
156
// Validity checks
150
 
157
 
151
if ((substr($type,0,2) != 'a:') && (substr($type,0,2) != 'v:')) syntax_error("Type must be either 'v:' or 'a:'. '$type' is not valid.");
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.");
152
 
159
 
153
if (count($rest_args) == 0) syntax_error("Please enter at least one desired video for downloading");
160
if (count($rest_args) == 0) syntax_error("Please enter at least one desired video for downloading");
Line 155... Line 162...
155
if ($failTreshold <= 0) syntax_error("Fail treshold has invalid value. Must be >0.");
162
if ($failTreshold <= 0) syntax_error("Fail treshold has invalid value. Must be >0.");
156
 
163
 
157
$cookie_file = expand_tilde($cookie_file);
164
$cookie_file = expand_tilde($cookie_file);
158
if (!file_exists($cookie_file)) $cookie_file = '';
165
if (!file_exists($cookie_file)) $cookie_file = '';
159
 
166
 
160
// Try to download/update youtube-dl into local directory
167
// Try to download/update youtube-dl/yt-dlp into local directory
161
 
-
 
162
$newest_version_md5 = get_latest_ytdl_md5sum();
-
 
163
if (!$newest_version_md5) {
168
download_latest_downloader();
164
	fwrite(STDERR, "Failed to get MD5 sum of latest version of 'youtube-dl' from GitHub. Will not try to download/update 'youtube-dl' into local directory.\n");
-
 
165
} else {
-
 
166
	if (!file_exists(__DIR__.'/youtube-dl') || ($newest_version_md5 != md5_file(__DIR__.'/youtube-dl'))) {
-
 
167
		// Try to download/update the file in our directory. It should be the newest available, since YT often breaks downloader
-
 
168
		if (file_exists(__DIR__.'/youtube-dl')) {
-
 
169
			echo "Trying to update 'youtube-dl' in local directory...\n";
-
 
170
		} else {
-
 
171
			echo "Trying to download 'youtube-dl' into local directory...\n";
-
 
172
		}
-
 
173
 
-
 
174
		@chmod(__DIR__.'/youtube-dl', 0777); // otherwise we might not be able to write to it
-
 
175
 
169
 
176
		if (!($binary = file_get_contents('https://yt-dl.org/latest/youtube-dl'))) {
-
 
177
			fwrite(STDERR, "Failed to download 'youtube-dl' into local directory (file_get_contents).\n");
-
 
178
		} else if (!@file_put_contents(__DIR__.'/youtube-dl', $binary)) {
-
 
179
			fwrite(STDERR, "Failed to download 'youtube-dl' into local directory (file_put_contents).\n");
-
 
180
		} else {
-
 
181
			if (!@chmod(__DIR__.'/youtube-dl', 0544)) {
-
 
182
				fwrite(STDERR, "Failed to download 'youtube-dl' into local directory (chmod 544).\n");
-
 
183
				@unlink(__DIR__.'/youtube-dl'); // try to delete, otherwise we might try to execute a non-executable file
-
 
184
			}
-
 
185
		}
-
 
186
	}
-
 
187
}
-
 
188
 
-
 
189
if (command_exists(__DIR__.'/youtube-dl')) {
170
if (command_exists(__DIR__.'/'.DOWNLOAD_YT_FORK)) {
190
	echo "Will use 'youtube-dl' from local directory\n";
171
	echo "Will use '".DOWNLOAD_YT_FORK."' from local directory\n";
191
	define('YTDL_EXE', __DIR__.'/youtube-dl');
172
	define('YTDL_EXE', __DIR__.'/'.DOWNLOAD_YT_FORK);
192
} else {
173
} else {
193
	// Download failed. Is at least a package installed?
174
	// Download failed. Is at least a package installed?
194
	if (command_exists('youtube-dl')) {
175
	if (command_exists(DOWNLOAD_YT_FORK)) {
195
		echo "Will use 'youtube-dl' from Linux package\n";
176
		echo "Will use '".DOWNLOAD_YT_FORK."' from Linux package\n";
196
		define('YTDL_EXE', 'youtube-dl');
177
		define('YTDL_EXE', DOWNLOAD_YT_FORK);
197
	} else {
178
	} else {
198
		fwrite(STDERR, "This script requires the tool/package 'youtube-dl'. Please install it first.\n");
179
		fwrite(STDERR, "This script requires the tool/package '".DOWNLOAD_YT_FORK."'. Please install it first.\n");
199
		exit(1);
180
		exit(1);
200
	}
181
	}
201
}
182
}
202
 
183
 
203
// Now process the videos
184
// Now process the videos
Line 355... Line 336...
355
 
336
 
356
	if ($verbose) echo "Processing channel ID '$channel_id' ...\n";
337
	if ($verbose) echo "Processing channel ID '$channel_id' ...\n";
357
 
338
 
358
	// List the videos of the channel
339
	// List the videos of the channel
359
 
340
 
360
	$cont = !empty(_getResultcache()) && file_exists(_getResultcache()) ? file_get_contents(_getResultcache()) : '';
341
	$use_cache = !empty(_getResultcache()) && file_exists(_getResultcache());
-
 
342
	$cont = $use_cache ? file_get_contents(_getResultcache()) : '';
361
	$out = json_decode($cont, true);
343
	$out = json_decode($cont, true);
362
	if ($out == NULL) $out = array();
344
	if ($out == NULL) $out = array();
363
 
345
 
364
	if (!empty(_getResultcache())) {
346
	if ($use_cache) {
365
		$stats = yt_get_channel_stats($channel_id);
347
		$stats = yt_get_channel_stats($channel_id);
366
		if ($stats === false) {
348
		if ($stats === false) {
367
			fwrite(STDERR, "Cannot get stats for channel with ID '$channel_id'\n");
349
			fwrite(STDERR, "Cannot get stats for channel with ID '$channel_id'\n");
368
			return;
350
			return;
369
		}
351
		}
Line 378... Line 360...
378
		$videocount_old = -2;
360
		$videocount_old = -2;
379
		$key = '';
361
		$key = '';
380
	}
362
	}
381
 
363
 
382
	if ($videocount_old != $videocount) { // Attention: This might not work if videos are deleted and added (= the count stays the same)
364
	if ($videocount_old != $videocount) { // Attention: This might not work if videos are deleted and added (= the count stays the same)
383
		if ($verbose && !empty(_getResultcache())) echo "Video count changed from $videocount_old to $videocount\n";
365
		if ($verbose && $use_cache) echo "Video count changed from $videocount_old to $videocount\n";
384
		$out[$key]['count'] = $videocount;
366
		$out[$key]['count'] = $videocount;
385
		if (!empty($search)) {
367
		if (!empty($search)) {
386
			$out[$key]['results'] = yt_channel_items($channel_id, $search);
368
			$out[$key]['results'] = yt_channel_items($channel_id, $search);
387
		} else {
369
		} else {
388
			$out[$key]['results'] = yt_channel_items($channel_id);
370
			$out[$key]['results'] = yt_channel_items($channel_id);
389
		}
371
		}
-
 
372
		if (!$out[$key]['results']) {
-
 
373
			fwrite(STDERR, "Cannot get result for channel with ID '$channel_id'\n");
-
 
374
			return;
-
 
375
		}
390
	} else {
376
	} else {
391
		if ($verbose) echo "Video count for channel is still $videocount, keep ".count($out[$key]['results'])." results.\n";
377
		if ($verbose) echo "Video count for channel is still $videocount, keep ".count($out[$key]['results'])." results.\n";
392
	}
378
	}
393
 
379
 
394
	// Save the cache
380
	// Save the cache
395
 
381
 
396
	try {
382
	try {
397
		if (!empty(_getResultcache())) file_put_contents(_getResultcache(), json_encode($out));
383
		if ($use_cache) file_put_contents(_getResultcache(), json_encode($out));
398
	} catch(Exception $e) {
384
	} catch(Exception $e) {
399
		fwrite(STDERR, "Cannot write result cache\n");
385
		fwrite(STDERR, "Cannot write result cache\n");
400
	}
386
	}
401
 
387
 
402
	// Now download
388
	// Now download
Line 417... Line 403...
417
 
403
 
418
	if ($verbose) echo "Processing playlist ID '$playlist_id' ...\n";
404
	if ($verbose) echo "Processing playlist ID '$playlist_id' ...\n";
419
 
405
 
420
	// List the videos of the playlist
406
	// List the videos of the playlist
421
 
407
 
422
	$cont = !empty(_getResultcache()) && file_exists(_getResultcache()) ? file_get_contents(_getResultcache()) : '';
408
	$use_cache = !empty(_getResultcache()) && file_exists(_getResultcache());
-
 
409
	$cont = $use_cache ? file_get_contents(_getResultcache()) : '';
423
	$out = json_decode($cont, true);
410
	$out = json_decode($cont, true);
424
	if ($out == NULL) $out = array();
411
	if ($out == NULL) $out = array();
425
 
412
 
426
	if (!empty(_getResultcache())) {
413
	if ($use_cache) {
427
		$stats = yt_get_playlist_stats($playlist_id);
414
		$stats = yt_get_playlist_stats($playlist_id);
428
		if ($stats === false) {
415
		if ($stats === false) {
429
			fwrite(STDERR, "Cannot get stats for playlist with ID '$playlist_id'\n");
416
			fwrite(STDERR, "Cannot get stats for playlist with ID '$playlist_id'\n");
430
			return;
417
			return;
431
		}
418
		}
Line 440... Line 427...
440
		$videocount_old = -2;
427
		$videocount_old = -2;
441
		$key = '';
428
		$key = '';
442
	}
429
	}
443
 
430
 
444
	if ($videocount_old != $videocount) { // Attention: This might not work if videos are deleted and added (= the count stays the same)
431
	if ($videocount_old != $videocount) { // Attention: This might not work if videos are deleted and added (= the count stays the same)
445
		if ($verbose && !empty(_getResultcache())) echo "Video count changed from $videocount_old to $videocount\n";
432
		if ($verbose && $use_cache) echo "Video count changed from $videocount_old to $videocount\n";
446
		$out[$key]['count'] = $videocount;
433
		$out[$key]['count'] = $videocount;
447
		$out[$key]['results'] = yt_playlist_items($playlist_id);
434
		$out[$key]['results'] = yt_playlist_items($playlist_id);
-
 
435
		if (!$out[$key]['results']) {
-
 
436
			fwrite(STDERR, "Cannot get result for playlist with ID '$playlist_id'\n");
-
 
437
			return;
-
 
438
		}
448
	} else {
439
	} else {
449
		if ($verbose) echo "Video count for playlist is still $videocount, keep ".count($out[$key]['results'])." results.\n";
440
		if ($verbose) echo "Video count for playlist is still $videocount, keep ".count($out[$key]['results'])." results.\n";
450
	}
441
	}
451
 
442
 
452
	// Save the cache
443
	// Save the cache
453
 
444
 
454
	try {
445
	try {
455
		if (!empty(_getResultcache())) file_put_contents(_getResultcache(), json_encode($out));
446
		if ($use_cache) file_put_contents(_getResultcache(), json_encode($out));
456
	} catch(Exception $e) {
447
	} catch(Exception $e) {
457
		fwrite(STDERR, "Cannot write result cache\n");
448
		fwrite(STDERR, "Cannot write result cache\n");
458
	}
449
	}
459
 
450
 
460
	// Now download
451
	// Now download
Line 928... Line 919...
928
	$res = rename($src, $dest_neu);
919
	$res = rename($src, $dest_neu);
929
	if ($res) $dest = $dest_neu;
920
	if ($res) $dest = $dest_neu;
930
	return $res;
921
	return $res;
931
}
922
}
932
 
923
 
933
function get_latest_ytdl_md5sum() {
924
function download_latest_downloader() {
-
 
925
	$download_url = false;
-
 
926
 
-
 
927
	if (DOWNLOAD_YT_FORK == 'yt-dlp') {
-
 
928
		$ch = curl_init();
-
 
929
		curl_setopt($ch, CURLOPT_URL, 'https://github.com/yt-dlp/yt-dlp/releases/latest/download/SHA2-256SUMS');
-
 
930
		#curl_setopt($ch, CURLOPT_HEADER, false);
-
 
931
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
-
 
932
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
-
 
933
		$cont = curl_exec($ch);
-
 
934
		$m = null;
-
 
935
		if (preg_match('@^(.+)\s+yt-dlp$@ismU', $cont, $m)) {
-
 
936
			$newest_version_sha2 = $m[1];
-
 
937
		} else {
-
 
938
			$newest_version_sha2 = false;
-
 
939
		}
-
 
940
 
-
 
941
		if (!$newest_version_sha2) {
-
 
942
			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");
-
 
943
		} else {
-
 
944
			if (!file_exists(__DIR__.'/'.DOWNLOAD_YT_FORK) || ($newest_version_sha2 != hash_file('sha256',__DIR__.'/'.DOWNLOAD_YT_FORK))) {
-
 
945
				$download_url = 'https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp';
-
 
946
			}
-
 
947
		}
-
 
948
	}
-
 
949
 
-
 
950
	if (DOWNLOAD_YT_FORK == 'youtube-dlc') {
-
 
951
		// TODO: What is the difference between these two? Which one should be chosen?!
-
 
952
		// https://github.com/blackjack4494/yt-dlc (18372 commits, last commit 23 Aug 2021, last release 2020.11.11-3)
-
 
953
		// https://github.com/blackjack4494/youtube-dlc (18888 commits, last commit 26 Jul 2021, last release 2020.10.09)
-
 
954
 
-
 
955
		// TODO: yt-dlc The sha256 does not fit to the binary! Therefore, it is always downloaded!
-
 
956
		// TODO: youtube-dlc has no hashes online...
-
 
957
		/*
-
 
958
		$ch = curl_init();
-
 
959
		curl_setopt($ch, CURLOPT_URL, 'https://github.com/blackjack4494/yt-dlc/releases/latest/download/SHA2-256SUMS');
-
 
960
		#curl_setopt($ch, CURLOPT_HEADER, false);
-
 
961
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
-
 
962
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
-
 
963
		$cont = curl_exec($ch);
-
 
964
		$m = null;
-
 
965
		if (preg_match('@^youtube\-dlc:(.+)$@ismU', $cont, $m)) {
-
 
966
			$newest_version_sha2 = $m[1];
-
 
967
		} else {
-
 
968
			$newest_version_sha2 = false;
-
 
969
		}
-
 
970
 
-
 
971
		if (!$newest_version_sha2) {
-
 
972
			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");
-
 
973
		} else {
-
 
974
			if (!file_exists(__DIR__.'/'.DOWNLOAD_YT_FORK) || ($newest_version_sha2 != hash_file('sha256',__DIR__.'/'.DOWNLOAD_YT_FORK))) {
-
 
975
				$download_url = 'https://github.com/blackjack4494/yt-dlc/releases/latest/download/youtube-dlc';
-
 
976
			}
-
 
977
		}
-
 
978
		*/
-
 
979
 
-
 
980
		if (!file_exists(__DIR__.'/'.DOWNLOAD_YT_FORK) || (time()-filemtime(__DIR__.'/'.DOWNLOAD_YT_FORK) > 24*60*60)) {
-
 
981
			$download_url = 'https://github.com/blackjack4494/yt-dlc/releases/latest/download/youtube-dlc';
-
 
982
		}
-
 
983
	}
-
 
984
 
-
 
985
	if (DOWNLOAD_YT_FORK == 'youtube-dl') {
934
	$ch = curl_init();
986
		$ch = curl_init();
935
	curl_setopt($ch, CURLOPT_URL, 'https://yt-dl.org/downloads/latest/MD5SUMS');
987
		curl_setopt($ch, CURLOPT_URL, 'https://yt-dl.org/downloads/latest/MD5SUMS');
936
	#curl_setopt($ch, CURLOPT_HEADER, false);
988
		#curl_setopt($ch, CURLOPT_HEADER, false);
937
	curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
989
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
938
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
990
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
939
	$cont = curl_exec($ch);
991
		$cont = curl_exec($ch);
940
	$m = null;
992
		$m = null;
941
	if (preg_match('@^(.+)  youtube\-dl$@ismU', $cont, $m)) {
993
		if (preg_match('@^(.+)  youtube\-dl$@ismU', $cont, $m)) {
942
		return $m[1];
994
			$newest_version_md5 = $m[1];
943
	} else {
995
		} else {
-
 
996
			$newest_version_md5 = false;
-
 
997
		}
-
 
998
 
-
 
999
		if (!$newest_version_md5) {
-
 
1000
			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");
944
		return false;
1001
		} else {
-
 
1002
			if (!file_exists(__DIR__.'/'.DOWNLOAD_YT_FORK) || ($newest_version_md5 != md5_file(__DIR__.'/'.DOWNLOAD_YT_FORK))) {
-
 
1003
				$download_url = 'https://yt-dl.org/latest/youtube-dl';
-
 
1004
			}
-
 
1005
		}
-
 
1006
	}
-
 
1007
 
-
 
1008
	if ($download_url !== false) {
-
 
1009
		// Try to download/update the file in our directory. It should be the newest available, since YT often breaks downloader
-
 
1010
		if (file_exists(__DIR__.'/'.DOWNLOAD_YT_FORK)) {
-
 
1011
			echo "Trying to update '".DOWNLOAD_YT_FORK."' in local directory...\n";
-
 
1012
		} else {
-
 
1013
			echo "Trying to download '".DOWNLOAD_YT_FORK."' into local directory...\n";
-
 
1014
		}
-
 
1015
 
-
 
1016
		@chmod(__DIR__.'/'.DOWNLOAD_YT_FORK, 0777); // otherwise we might not be able to write to it
-
 
1017
 
-
 
1018
		if (!($binary = file_get_contents($download_url))) {
-
 
1019
			fwrite(STDERR, "Failed to download '".DOWNLOAD_YT_FORK."' into local directory (file_get_contents).\n");
-
 
1020
		} else if (!@file_put_contents(__DIR__.'/'.DOWNLOAD_YT_FORK, $binary)) {
-
 
1021
			fwrite(STDERR, "Failed to download '".DOWNLOAD_YT_FORK."' into local directory (file_put_contents).\n");
-
 
1022
		} else {
-
 
1023
			if (!@chmod(__DIR__.'/'.DOWNLOAD_YT_FORK, 0544)) {
-
 
1024
				fwrite(STDERR, "Failed to download '".DOWNLOAD_YT_FORK."' into local directory (chmod 544).\n");
-
 
1025
				@unlink(__DIR__.'/'.DOWNLOAD_YT_FORK); // try to delete, otherwise we might try to execute a non-executable file
-
 
1026
			}
-
 
1027
		}
945
	}
1028
	}
946
}
1029
}