Rev 5 | Rev 7 | Go to most recent revision | Show entire file | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed
Rev 5 | Rev 6 | ||
---|---|---|---|
Line 1... | Line 1... | ||
1 | #!/usr/bin/php |
1 | #!/usr/bin/php |
2 | <?php |
2 | <?php |
3 | 3 | ||
4 | // ViaThinkSoft YouTube Downloader Util 2.1.1 |
4 | // ViaThinkSoft YouTube Downloader Util 2.2 |
5 | // Revision: 2019-08-05 |
5 | // Revision: 2020-07-25 |
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 | // Syntax: |
- | |
10 | // ./ytdwn [-t|--type v:[ext]|a:[ext]] (default v:) |
- | |
11 | // [-o|--outputDir <dir>] (default current working directory) |
- | |
12 | // [-a|--alreadyDownloaded <file>] |
- | |
13 | // [-f|--failList <file> <treshold>] (This file logs failures) |
- | |
14 | // [-F|--failTreshold <num>] (Don't download if failure (-f) treshold is reached. Default: 3) |
- | |
15 | // [-V|--version] (shows version) |
- | |
16 | // [-v|--verbose] (displays verbose information to STDOUT) |
- | |
17 | // [-h|--help] (shows help) |
- | |
18 | // [-N|--no-mp3-tagtransfer] (disables transfer of video ID to MP3 ID tag) |
- | |
19 | // (This feature requires the package "id3v2") |
- | |
20 | // [-T|--default-template <t>] (Sets default filename template.) |
- | |
21 | // (Default: '%(title)s-%(id)s.%(ext)s') |
- | |
22 | // [-X|--extra-args <args>] (Additional arguments passed through) |
- | |
23 | // (youtube-dl. Default "-ic") |
- | |
24 | // [-A|--api-key <file|key>] (specifies the API key, or a file containing the API key) |
- | |
25 | // (Default: ~/.yt_api_key) |
- | |
26 | // [-C|--resultcache <file>] (allows video results to be cached in this file) |
- | |
27 | // (only for playlists or channels) |
- | |
28 | // [-O|--create-outputdir] (allows creation of the output directories, recursively) |
- | |
29 | // [--] |
- | |
30 | // <resource> [<resource> ...] |
- | |
31 | // |
- | |
32 | // For all paths (outputDir, alreadyDownloaded, apikey, failList and resultcache), you can use the |
- | |
33 | // term '[listname]' which will be replaced by the basename of the current list file (without file extension). |
- | |
34 | // For example you can do following: |
- | |
35 | // ./ytdwn -o 'downloads/[listname]' -- list:*.list |
- | |
36 | // If no list file is processed, it will be replaced with nothing. |
- | |
37 | // |
- | |
38 | // The "alreadyDownloaded" argument contains a file which will be managed by ytdwn. |
- | |
39 | // It will contain all video IDs which have been downloaded. This allows you to |
- | |
40 | // move away the already downloaded files, and ytdwn will not download them again. |
- | |
41 | // |
- | |
42 | // Examples for type: |
- | |
43 | // v: best video quality |
- | |
44 | // a: best audio only |
- | |
45 | // a:mp3 audio only, mp3 |
- | |
46 | // Valid audio formats according to "man youtube-dl": |
- | |
47 | // "best", "aac", "flac", "mp3", "m4a", "opus", "vorbis", or "wav"; "best" by default |
- | |
48 | // |
- | |
49 | // A <resource> can be one of the following: |
- | |
50 | // vid:<video ID> |
- | |
51 | // vurl:<youtube video URL> |
- | |
52 | // pid:<playlist ID> |
- | |
53 | // purl:<playlist URL> |
- | |
54 | // cid:<channel id> |
- | |
55 | // cname:<channel name> |
- | |
56 | // curl:<channel or username URL> |
- | |
57 | // list:<file with resource entries> (comments can be #) |
- | |
58 | // search:<searchterm> |
- | |
59 | // |
- | |
60 | // For channels (cid, cname, curl) you can also perform a search to filter the results. |
- | |
61 | // This can be done like this: |
- | |
62 | // cname:[search="Elvis Presley"]channel_1234 |
- | |
63 | // For the search option, following parameters are possible: |
9 | // For syntax and other documentation, please read the file README. |
64 | // search:[order=date][maxresults=50]"Elvis Presley" |
- | |
65 | // Acceptable order values are: date, rating, relevance, title, videoCount, viewCount |
- | |
66 | // Default values are order=relevance and maxresults=10 |
- | |
67 | // Use maxresults=-1 to download everything which matches the searchterm. |
- | |
68 | // |
- | |
69 | // Requirements: |
- | |
70 | // - PHP CLI |
- | |
71 | // - Package "youtube-dl" (ytdwn will try to download it automatically, if possible) |
- | |
72 | // - A YouTube API key (can be obtained here: https://console.developers.google.com/apis/credentials ) |
- | |
73 | // - If you want to extract audio, you need additionally: ffmpeg or avconv and ffprobe or avprobe. |
- | |
74 | // - Optional: package "id3v2" to allow the YouTube video id to be transferred to the MP3 ID tag |
- | |
75 | 10 | ||
76 | // ------------------------------------------------------------------------------------------------ |
11 | // ------------------------------------------------------------------------------------------------ |
77 | 12 | ||
78 | error_reporting(E_ALL | E_NOTICE | E_STRICT | E_DEPRECATED); |
13 | error_reporting(E_ALL | E_NOTICE | E_STRICT | E_DEPRECATED); |
79 | 14 | ||
80 | define('AUTO_API_KEY', '~/.yt_api_key'); |
15 | define('AUTO_API_KEY', '~/.yt_api_key'); |
- | 16 | define('AUTO_COOKIE_FILE', '~/.yt_cookies'); |
|
81 | define('DOWNLOAD_SIMULATION_MODE', false); |
17 | define('DOWNLOAD_SIMULATION_MODE', false); |
82 | define('DEFAULT_SEARCH_ORDER', 'relevance'); |
18 | define('DEFAULT_SEARCH_ORDER', 'relevance'); |
83 | define('DEFAULT_SEARCH_MAXRESULTS', 10); |
19 | define('DEFAULT_SEARCH_MAXRESULTS', 10); |
84 | 20 | ||
85 | 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 |
Line 113... | Line 49... | ||
113 | $extra_args = |
49 | $extra_args = |
114 | // '-k ' . // The additional "-k" option in the above makes youtube-dl keep downloaded videos. |
50 | // '-k ' . // The additional "-k" option in the above makes youtube-dl keep downloaded videos. |
115 | '-i ' . // continue upon download errors |
51 | '-i ' . // continue upon download errors |
116 | '-c '; // resume partially downloaded video files |
52 | '-c '; // resume partially downloaded video files |
117 | $default_template = '%(title)s-%(id)s.%(ext)s'; |
53 | $default_template = '%(title)s-%(id)s.%(ext)s'; |
- | 54 | $cookie_file = AUTO_COOKIE_FILE; |
|
118 | 55 | ||
119 | // Parse arguments |
56 | // Parse arguments |
120 | // 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 |
57 | // 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 |
121 | 58 | ||
122 | $init_extra_args = false; |
59 | $init_extra_args = false; |
Line 155... | Line 92... | ||
155 | $default_template = $m[3]; |
92 | $default_template = $m[3]; |
156 | } else if (preg_match('@^(/A|\-A|\-\-api-key)(\s+|=)(.*)$@s', $arg2, $m)) { |
93 | } else if (preg_match('@^(/A|\-A|\-\-api-key)(\s+|=)(.*)$@s', $arg2, $m)) { |
157 | array_shift($argv_bak); |
94 | array_shift($argv_bak); |
158 | 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]); |
159 | $apikey = file_exists($m[3]) ? trim(file_get_contents($m[3])) : $m[3]; |
96 | $apikey = file_exists($m[3]) ? trim(file_get_contents($m[3])) : $m[3]; |
- | 97 | } else if (preg_match('@^(\-\-cookies)(\s+|=)(.*)$@s', $arg2, $m)) { |
|
- | 98 | array_shift($argv_bak); |
|
- | 99 | if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]); |
|
- | 100 | $cookie_file = file_exists($m[3]) ? trim(file_get_contents($m[3])) : $m[3]; |
|
160 | } else if (preg_match('@^(/X|\-X|\-\-extra-args)(\s+|=)(.*)$@s', $arg2, $m)) { |
101 | } else if (preg_match('@^(/X|\-X|\-\-extra-args)(\s+|=)(.*)$@s', $arg2, $m)) { |
161 | array_shift($argv_bak); |
102 | array_shift($argv_bak); |
162 | if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]); |
103 | if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]); |
163 | if ($init_extra_args) { |
104 | if ($init_extra_args) { |
164 | $extra_args .= ' ' . $m[3]; // user has multiple "-X" arguments. append. |
105 | $extra_args .= ' ' . $m[3]; // user has multiple "-X" arguments. append. |
Line 199... | Line 140... | ||
199 | 140 | ||
200 | if (count($rest_args) == 0) syntax_error("Please enter at least one desired video for downloading"); |
141 | if (count($rest_args) == 0) syntax_error("Please enter at least one desired video for downloading"); |
201 | 142 | ||
202 | if ($failTreshold <= 0) syntax_error("Fail treshold has invalid value. Must be >0."); |
143 | if ($failTreshold <= 0) syntax_error("Fail treshold has invalid value. Must be >0."); |
203 | 144 | ||
- | 145 | $cookie_file = expand_tilde($cookie_file); |
|
- | 146 | if (!file_exists($cookie_file)) $cookie_file = ''; |
|
- | 147 | ||
204 | // Try to download/update youtube-dl into local directory |
148 | // Try to download/update youtube-dl into local directory |
205 | 149 | ||
206 | $newest_version_md5 = get_latest_ytdl_md5sum(); |
150 | $newest_version_md5 = get_latest_ytdl_md5sum(); |
207 | if (!$newest_version_md5) { |
151 | if (!$newest_version_md5) { |
208 | fwrite(STDERR, "Failed to get MD5 sum of latest version of 'youtube-dl' from GitHub. Will not try to download/update 'youtube-dl' into local directory.\n"); |
152 | fwrite(STDERR, "Failed to get MD5 sum of latest version of 'youtube-dl' from GitHub. Will not try to download/update 'youtube-dl' into local directory.\n"); |
Line 431... | Line 375... | ||
431 | fwrite(STDERR, "Cannot write result cache\n"); |
375 | fwrite(STDERR, "Cannot write result cache\n"); |
432 | } |
376 | } |
433 | 377 | ||
434 | // Now download |
378 | // Now download |
435 | 379 | ||
- | 380 | if (!$out[$key]['results']) { |
|
- | 381 | fwrite(STDERR, "Cannot get result for channel with ID '$channel_id'\n"); |
|
- | 382 | return; |
|
- | 383 | } |
|
436 | foreach ($out[$key]['results'] as list($id, $title)) { |
384 | foreach ($out[$key]['results'] as list($id, $title)) { |
437 | if ($verbose) echo "Downloading '$title' as ".hf_type($type)." ...\n"; |
385 | if ($verbose) echo "Downloading '$title' as ".hf_type($type)." ...\n"; |
438 | ytdwn_video_id($id); |
386 | ytdwn_video_id($id); |
439 | } |
387 | } |
440 | } |
388 | } |
Line 481... | Line 429... | ||
481 | fwrite(STDERR, "Cannot write result cache\n"); |
429 | fwrite(STDERR, "Cannot write result cache\n"); |
482 | } |
430 | } |
483 | 431 | ||
484 | // Now download |
432 | // Now download |
485 | 433 | ||
- | 434 | if (!$out[$key]['results']) { |
|
- | 435 | fwrite(STDERR, "Cannot get result for playlist with ID '$playlist_id'\n"); |
|
- | 436 | return; |
|
- | 437 | } |
|
486 | foreach ($out[$key]['results'] as list($id, $title)) { |
438 | foreach ($out[$key]['results'] as list($id, $title)) { |
487 | if ($verbose) echo "Downloading '$title' as ".hf_type($type)." ...\n"; |
439 | if ($verbose) echo "Downloading '$title' as ".hf_type($type)." ...\n"; |
488 | ytdwn_video_id($id); |
440 | ytdwn_video_id($id); |
489 | } |
441 | } |
490 | } |
442 | } |
Line 499... | Line 451... | ||
499 | 451 | ||
500 | $results = yt_search_items($search, $order, $maxresults); |
452 | $results = yt_search_items($search, $order, $maxresults); |
501 | 453 | ||
502 | // Now download |
454 | // Now download |
503 | 455 | ||
- | 456 | if (!$results) { |
|
- | 457 | fwrite(STDERR, "Cannot get data for search '$search'\n"); |
|
- | 458 | return; |
|
- | 459 | } |
|
504 | foreach ($results as list($id, $title)) { |
460 | foreach ($results as list($id, $title)) { |
505 | if ($verbose) echo "Downloading '$title' as ".hf_type($type)." ...\n"; |
461 | if ($verbose) echo "Downloading '$title' as ".hf_type($type)." ...\n"; |
506 | ytdwn_video_id($id); |
462 | ytdwn_video_id($id); |
507 | } |
463 | } |
508 | } |
464 | } |
Line 512... | Line 468... | ||
512 | global $verbose; |
468 | global $verbose; |
513 | global $mp3id_transfer; |
469 | global $mp3id_transfer; |
514 | global $extra_args; |
470 | global $extra_args; |
515 | global $default_template; |
471 | global $default_template; |
516 | global $failTreshold; |
472 | global $failTreshold; |
- | 473 | global $cookie_file; |
|
517 | 474 | ||
518 | if (DOWNLOAD_SIMULATION_MODE) { |
475 | if (DOWNLOAD_SIMULATION_MODE) { |
519 | echo "SIMULATE download of video id $video_id as ".hf_type($type)." to "._getOutputDir()."\n"; |
476 | echo "SIMULATE download of video id $video_id as ".hf_type($type)." to "._getOutputDir()."\n"; |
520 | return; |
477 | return; |
521 | } |
478 | } |
Line 536... | Line 493... | ||
536 | $outputTemplate = rtrim(_getOutputDir(), '/').'/'.$default_template; |
493 | $outputTemplate = rtrim(_getOutputDir(), '/').'/'.$default_template; |
537 | 494 | ||
538 | if (substr($type,0,2) == 'v:') { |
495 | if (substr($type,0,2) == 'v:') { |
539 | $format = substr($type,2); |
496 | $format = substr($type,2); |
540 | if (!empty($format)) { |
497 | if (!empty($format)) { |
541 | exec(YTDL_EXE.' -o '.escapeshellarg($outputTemplate).' '.$extra_args.' '.escapeshellarg(vid_to_vurl($video_id)).' --format '.escapeshellarg($format), $out, $code); |
498 | 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); |
542 | } else { |
499 | } else { |
543 | exec(YTDL_EXE.' -o '.escapeshellarg($outputTemplate).' '.$extra_args.' '.escapeshellarg(vid_to_vurl($video_id)), $out, $code); |
500 | exec(YTDL_EXE.' -o '.escapeshellarg($outputTemplate).' '.$extra_args.(empty($cookie_file) ? '' : ' --cookies '.$cookie_file).' '.escapeshellarg(vid_to_vurl($video_id)), $out, $code); |
544 | } |
501 | } |
545 | } else if (substr($type,0,2) == 'a:') { |
502 | } else if (substr($type,0,2) == 'a:') { |
546 | $format = substr($type,2); |
503 | $format = substr($type,2); |
547 | if (!empty($format)) { |
504 | if (!empty($format)) { |
548 | exec(YTDL_EXE.' -o '.escapeshellarg($outputTemplate).' '.$extra_args.' '.escapeshellarg(vid_to_vurl($video_id)).' --extract-audio --audio-format '.escapeshellarg($format), $out, $code); |
505 | 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 { |
506 | } else { |
550 | exec(YTDL_EXE.' -o '.escapeshellarg($outputTemplate).' '.$extra_args.' '.escapeshellarg(vid_to_vurl($video_id)).' --extract-audio', $out, $code); |
507 | 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 | } |
508 | } |
552 | if (($mp3id_transfer) && ($format == 'mp3')) mp3_transfer_vid_to_id(); |
509 | if (($mp3id_transfer) && ($format == 'mp3')) mp3_transfer_vid_to_id(); |
553 | } else assert(false); |
510 | } else assert(false); |
554 | 511 | ||
555 | if ($code == 0) { |
512 | if ($code == 0) { |