Subversion Repositories yt_downloader

Rev

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

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