Subversion Repositories yt_downloader

Rev

Rev 19 | Rev 21 | Go to most recent revision | Only display areas with differences | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

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