Subversion Repositories yt_downloader

Compare Revisions

No changes between revisions

Regard whitespace Rev 1 → Rev 2

/trunk/build_stable
0,0 → 1,20
#!/bin/bash
 
# TODO: also allow $DIR to be determinated inside a symlink'ed dir
DIR=$( dirname "$0" )
 
cd "$DIR"
dirname=$( basename "$( pwd )" )
tmpfile=$( mktemp --suffix="_buildytdwn" )
 
FILENAME="stable.tar.gz"
 
cd ..
rm "$dirname"/"$FILENAME"
tar cvfz "$tmpfile" --exclude "yt_downloader2/ffmpeg" --exclude "yt_downloader2/libav" --exclude "yt_downloader2/marschall" --exclude "yt_downloader2/youtube-dl" --exclude "yt_downloader2/upload_to_vts" -- "$dirname"
 
# otherwise: "the file has changed while reading"
mv "$tmpfile" "$dirname"/"$FILENAME"
 
chmod 644 "$dirname"/"$FILENAME"
 
Property changes:
Added: svn:executable
+*
\ No newline at end of property
/trunk/stable.tar.gz
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/youtube_functions.inc.phps
0,0 → 1,221
<?php
 
// ViaThinkSoft YouTube Downloader Functions 2.1
// Revision: 2018-05-21
// Author: Daniel Marschall <www.daniel-marschall.de>
// Licensed under the terms of GPLv3
 
// Get API key: https://console.developers.google.com/apis/credentials
// Test API here: https://developers.google.com/apis-explorer/?hl=de#p/youtube/v3/youtube.playlistItems.list
 
$yt_apikey = null;
$yt_apikey_callback = null;
 
function yt_set_apikey($apikey) {
global $yt_apikey;
$yt_apikey = $apikey;
}
 
function yt_set_apikey_callback($apikey_callback) {
global $yt_apikey_callback;
$yt_apikey_callback = $apikey_callback;
}
 
function yt_get_apikey() {
global $yt_apikey, $yt_apikey_callback;
 
if (!empty($yt_apikey_callback)) {
$apikey = call_user_func($yt_apikey_callback);
if (!yt_check_apikey_syntax($apikey)) throw new Exception("Invalid API key '$apikey'");
} else if (!empty($yt_apikey)) {
$apikey = $yt_apikey;
if (!yt_check_apikey_syntax($apikey)) throw new Exception("Invalid API key '$apikey'");
} else {
throw new Exception("This function requires a YouTube API key.\n");
}
 
return $apikey;
}
 
function yt_playlist_items($playlist_id, $maxresults=-1) {
$out = array();
 
$next_page_token = '';
 
do {
$cont = file_get_contents('https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&playlistId='.urlencode($playlist_id).'&maxResults=50'.(($next_page_token!='') ? '&pageToken='.urlencode($next_page_token) : '').'&key='.urlencode(yt_get_apikey()));
if (!$cont) return false;
 
$obj = json_decode($cont, true);
if (!$obj) return false;
 
foreach ($obj['items'] as $item) {
if ($item['snippet']['resourceId']['kind'] == 'youtube#video') {
$title = $item['snippet']['title'];
$video_id = $item['snippet']['resourceId']['videoId'];
$out[] = array($video_id, $title);
if (($maxresults != -1) && ($maxresults == count($out))) return $out;
}
}
 
$next_page_token = isset($obj['nextPageToken']) ? $obj['nextPageToken'] : '';
} while ($next_page_token != '');
 
return $out;
}
 
function yt_get_channel_id($username) {
$cont = file_get_contents('https://www.googleapis.com/youtube/v3/channels?key='.urlencode(yt_get_apikey()).'&forUsername='.urlencode($username).'&part=id');
if (!$cont) return false;
 
$obj = json_decode($cont, true);
if (!$obj) return false;
 
foreach ($obj['items'] as $item) {
if ($item['kind'] == 'youtube#channel') {
return $item['id'];
}
}
}
 
function yt_get_channel_id_and_stats($username) {
$cont = file_get_contents('https://www.googleapis.com/youtube/v3/channels?key='.urlencode(yt_get_apikey()).'&forUsername='.urlencode($username).'&part=id,statistics');
if (!$cont) return false;
 
$obj = json_decode($cont, true);
if (!$obj) return false;
 
foreach ($obj['items'] as $item) {
if ($item['kind'] == 'youtube#channel') {
return array($item['id'], $item['statistics']);
}
}
}
 
function yt_get_channel_stats($channel_id) {
$cont = file_get_contents('https://www.googleapis.com/youtube/v3/channels?key='.urlencode(yt_get_apikey()).'&id='.urlencode($channel_id).'&part=statistics');
if (!$cont) return false;
 
$obj = json_decode($cont, true);
if (!$obj) return false;
 
foreach ($obj['items'] as $item) {
if ($item['kind'] == 'youtube#channel') {
return $item['statistics'];
}
}
}
 
function yt_get_playlist_stats($playlist_id) {
$cont = file_get_contents('https://www.googleapis.com/youtube/v3/playlists?part=contentDetails&id='.urlencode($playlist_id).'&key='.urlencode(yt_get_apikey()));
if (!$cont) return false;
 
$obj = json_decode($cont, true);
if (!$obj) return false;
 
foreach ($obj['items'] as $item) {
if ($item['kind'] == 'youtube#playlist') {
return $item['contentDetails'];
}
}
}
 
function yt_channel_items($channel_id, $searchterms='', $maxresults=-1) {
$out = array();
 
$next_page_token = '';
 
do {
$cont = file_get_contents('https://www.googleapis.com/youtube/v3/search?part=snippet&channelId='.urlencode($channel_id).(($searchterms!='') ? '&q='.urlencode($searchterms) : '').'&maxResults=50'.(($next_page_token!='') ? '&pageToken='.urlencode($next_page_token) : '').'&key='.urlencode(yt_get_apikey()));
if (!$cont) return false;
 
$obj = json_decode($cont, true);
if (!$obj) return false;
 
foreach ($obj['items'] as $item) {
if ($item['id']['kind'] == 'youtube#video') {
$title = $item['snippet']['title'];
$video_id = $item['id']['videoId'];
$out[] = array($video_id, $title);
if (($maxresults != -1) && ($maxresults == count($out))) return $out;
}
}
 
$next_page_token = isset($obj['nextPageToken']) ? $obj['nextPageToken'] : '';
} while ($next_page_token != '');
 
return $out;
}
 
// Acceptable order values are: date, rating, relevance(default), title, videoCount, viewCount
function yt_search_items($searchterms, $order='', $maxresults=-1) {
$out = array();
 
$next_page_token = '';
 
do {
$cont = file_get_contents('https://www.googleapis.com/youtube/v3/search?part=snippet&q='.urlencode($searchterms).(($order!='') ? '&order='.urlencode($order) : '').'&maxResults=50'.(($next_page_token!='') ? '&pageToken='.urlencode($next_page_token) : '').'&key='.urlencode(yt_get_apikey()));
if (!$cont) return false;
 
$obj = json_decode($cont, true);
if (!$obj) return false;
 
foreach ($obj['items'] as $item) {
if ($item['id']['kind'] == 'youtube#video') {
$title = $item['snippet']['title'];
$video_id = $item['id']['videoId'];
$out[] = array($video_id, $title);
if (($maxresults != -1) && ($maxresults == count($out))) return $out;
}
}
 
$next_page_token = isset($obj['nextPageToken']) ? $obj['nextPageToken'] : '';
} while ($next_page_token != '');
 
return $out;
}
 
function getVideoIDFromURL($url) {
// Extract video ID from the URL
// TODO: are there more formats?
 
$vid = false;
 
# Usual format
if (($vid === false) && (preg_match("@https{0,1}://(www\\.|)youtube\\.com/watch(.*)(&|\\?)v=(.+)&@ismU", $url.'&', $m))) {
$vid = $m[4];
}
 
# Short format
if (($vid === false) && (preg_match("@https{0,1}://(www\\.|)youtu\\.be/(.+)\$@ismU", $url, $m))) {
$vid = $m[2];
}
 
return $vid;
}
 
function getPlaylistIDFromURL($url) {
$pid = false;
 
# Usual format
if (($pid === false) && (preg_match("@https{0,1}://(www\\.|)youtube\\.com/(.*)(&|\\?)list=(.+)&@ismU", $url.'&', $m))) {
$pid = $m[4];
}
 
return $pid;
}
 
function yt_check_apikey_syntax($apikey) {
return preg_match('@^[a-zA-Z0-9]{39}$@', $apikey);
}
 
function yt_check_video_id($video_id) {
return preg_match('@^[a-zA-Z0-9\-_]{11}$@', $video_id);
}
 
// Examples:
//yt_set_apikey(trim(file_get_contents(__DIR__ . '/.yt_api_key')));
//print_r(yt_playlist_items('PL9GbGAd-gY1pyxZJIX5MOdYdRbdweVAID'));
//print_r(yt_channel_items('UCjPjcMEAN64opOoNQE9GykA'));
//print_r(yt_channel_items('UCjPjcMEAN64opOoNQE9GykA', 'Knight'));
//print_r(yt_search_items('Wesley Willis', 'date', 10));
/trunk/ytdwn
0,0 → 1,878
#!/usr/bin/php
<?php
 
// ViaThinkSoft YouTube Downloader Util 2.1
// Revision: 2018-04-16
// Author: Daniel Marschall <www.daniel-marschall.de>
// Licensed under the terms of GPLv3
//
// Syntax:
// ./ytdwn [-t|--type v:[ext]|a:[ext]] (default v:)
// [-o|--outputDir <dir>] (default current working directory)
// [-a|--alreadyDownloaded <file>]
// [-f|--failList <file> <treshold>] (This file logs failures)
// [-F|--failTreshold <num>] (Don't download if failure (-f) treshold is reached. Default: 3)
// [-V|--version] (shows version)
// [-v|--verbose] (displays verbose information to STDOUT)
// [-h|--help] (shows help)
// [-N|--no-mp3-tagtransfer] (disables transfer of video ID to MP3 ID tag)
// (This feature requires the package "id3v2")
// [-T|--default-template <t>] (Sets default filename template.)
// (Default: '%(title)s-%(id)s.%(ext)s')
// [-X|--extra-args <args>] (Additional arguments passed through)
// (youtube-dl. Default "-ic")
// [-A|--api-key <file|key>] (specifies the API key, or a file containing the API key)
// (Default: ~/.yt_api_key)
// [-C|--resultcache <file>] (allows video results to be cached in this file)
// (only for playlists or channels)
// [-O|--create-outputdir] (allows creation of the output directories, recursively)
// [--]
// <resource> [<resource> ...]
//
// For all paths (outputDir, alreadyDownloaded, apikey, failList and resultcache), you can use the
// term '[listname]' which will be replaced by the basename of the current list file (without file extension).
// For example you can do following:
// ./ytdwn -o 'downloads/[listname]' -- list:*.list
// If no list file is processed, it will be replaced with nothing.
//
// The "alreadyDownloaded" argument contains a file which will be managed by ytdwn.
// It will contain all video IDs which have been downloaded. This allows you to
// move away the already downloaded files, and ytdwn will not download them again.
//
// Examples for type:
// v: best video quality
// a: best audio only
// a:mp3 audio only, mp3
// Valid audio formats according to "man youtube-dl":
// "best", "aac", "flac", "mp3", "m4a", "opus", "vorbis", or "wav"; "best" by default
//
// A <resource> can be one of the following:
// vid:<video ID>
// vurl:<youtube video URL>
// pid:<playlist ID>
// purl:<playlist URL>
// cid:<channel id>
// cname:<channel name>
// curl:<channel or username URL>
// list:<file with resource entries> (comments can be #)
// search:<searchterm>
//
// For channels (cid, cname, curl) you can also perform a search to filter the results.
// This can be done like this:
// cname:[search="Elvis Presley"]channel_1234
// For the search option, following parameters are possible:
// search:[order=date][maxresults=50]"Elvis Presley"
// Acceptable order values are: date, rating, relevance, title, videoCount, viewCount
// Default values are order=relevance and maxresults=10
// Use maxresults=-1 to download everything which matches the searchterm.
//
// Requirements:
// - PHP CLI
// - Package "youtube-dl" (ytdwn will try to download it automatically, if possible)
// - A YouTube API key (can be obtained here: https://console.developers.google.com/apis/credentials )
// - If you want to extract audio, you need additionally: ffmpeg or avconv and ffprobe or avprobe.
// - Optional: package "id3v2" to allow the YouTube video id to be transferred to the MP3 ID tag
 
// ------------------------------------------------------------------------------------------------
 
error_reporting(E_ALL | E_NOTICE | E_STRICT | E_DEPRECATED);
 
define('AUTO_API_KEY', '~/.yt_api_key');
define('DOWNLOAD_SIMULATION_MODE', false);
define('DEFAULT_SEARCH_ORDER', 'relevance');
define('DEFAULT_SEARCH_MAXRESULTS', 10);
 
putenv("LANG=de_DE.UTF-8"); // required if video titles contain non-ASCII symbols
 
require_once __DIR__ . '/youtube_functions.inc.phps';
 
// Check if we are running in command line
 
if (PHP_SAPI !== 'cli') {
fwrite(STDERR, "Error: Can only run in CLI mode\n");
exit(2);
}
 
// Global vars
 
$listFilenameStack = array();
 
// Default values
 
$allow_creation_outputdir = false;
$type = 'v:';
$outputDir = '';
$alreadyDownloaded = '';
$failList = '';
$failTreshold = 3;
$rest_args = array();
$verbose = false;
$mp3id_transfer = true;
$apikey = '';
$resultcache = '';
$extra_args =
// '-k ' . // The additional "-k" option in the above makes youtube-dl keep downloaded videos.
'-i ' . // continue upon download errors
'-c '; // resume partially downloaded video files
$default_template = '%(title)s-%(id)s.%(ext)s';
 
// Parse arguments
// 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
 
$init_extra_args = false;
$argv_bak = $argv;
array_shift($argv_bak);
while (count($argv_bak) > 0) {
$arg = array_shift($argv_bak);
$arg2 = $arg . ' ' . (isset($argv_bak[0]) ? $argv_bak[0] : '');
if (preg_match('@^(/t|\-t|\-\-type)(\s+|=)(.*)$@s', $arg2, $m)) {
array_shift($argv_bak);
if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
$type = $m[3];
} else if (preg_match('@^(/o|\-o|\-\-outputDir)(\s+|=)(.*)$@s', $arg2, $m)) {
array_shift($argv_bak);
if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
$outputDir = $m[3];
} else if (preg_match('@^(/a|\-a|\-\-alreadyDownloaded)(\s+|=)(.*)$@s', $arg2, $m)) {
array_shift($argv_bak);
if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
$alreadyDownloaded = $m[3];
} else if (preg_match('@^(/f|\-f|\-\-failList)(\s+|=)(.*)$@s', $arg2, $m)) {
array_shift($argv_bak);
if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
$failList = $m[3];
} else if (preg_match('@^(/F|\-F|\-\-failTreshold)(\s+|=)(.*)$@s', $arg2, $m)) {
array_shift($argv_bak);
if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
$failTreshold = $m[3];
} else if (preg_match('@^(/C|\-C|\-\-resultcache)(\s+|=)(.*)$@s', $arg2, $m)) {
array_shift($argv_bak);
if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
$resultcache = $m[3];
} else if (preg_match('@^(/T|\-T|\-\-default-template)(\s+|=)(.*)$@s', $arg2, $m)) {
array_shift($argv_bak);
if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
$default_template = $m[3];
} else if (preg_match('@^(/A|\-A|\-\-api-key)(\s+|=)(.*)$@s', $arg2, $m)) {
array_shift($argv_bak);
if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
$apikey = file_exists($m[3]) ? trim(file_get_contents($m[3])) : $m[3];
} else if (preg_match('@^(/X|\-X|\-\-extra-args)(\s+|=)(.*)$@s', $arg2, $m)) {
array_shift($argv_bak);
if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
if ($init_extra_args) {
$extra_args .= ' ' . $m[3]; // user has multiple "-X" arguments. append.
} else {
$extra_args = $m[3]; // overwrite defaults
$init_extra_args = true;
}
} else if (preg_match('@^(/\?|/h|\-\?|\-h|\-\-help)$@s', $arg, $m)) {
if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
help();
} else if (preg_match('@^(/V|\-V|\-\-version)$@s', $arg, $m)) {
if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
version();
} else if (preg_match('@^(/v|\-v|\-\-verbose)$@s', $arg, $m)) {
if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
$verbose = true;
} else if (preg_match('@^(/N|\-N|\-\-no-mp3-tagtransfer)$@s', $arg, $m)) {
if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
$mp3id_transfer = false;
} else if (preg_match('@^(/O|\-O|\-\-create-outputdir)$@s', $arg, $m)) {
if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
$allow_creation_outputdir = true;
} else if ($arg == '--') {
if (count($rest_args) > 0) syntax_error("Invalid argument: ".$rest_args[0]);
$rest_args = $argv_bak;
break;
} else {
$rest_args[] = $arg;
}
}
unset($arg);
unset($argv_bak);
unset($init_extra_args);
 
// Validity checks
 
if ((substr($type,0,2) != 'a:') && (substr($type,0,2) != 'v:')) syntax_error("Type must be either 'v:' or 'a:'. '$type' is not valid.");
 
if (count($rest_args) == 0) syntax_error("Please enter at least one desired video for downloading");
 
if ($failTreshold <= 0) syntax_error("Fail treshold has invalid value. Must be >0.");
 
// Try to install youtube-dl, if it does not exist
 
if (command_exists(__DIR__.'/youtube-dl')) {
define('YTDL_EXE', __DIR__.'/youtube-dl');
} else if (command_exists('youtube-dl')) {
define('YTDL_EXE', 'youtube-dl');
} else {
@file_put_contents(__DIR__.'/youtube-dl', file_get_contents('https://yt-dl.org/downloads/latest/youtube-dl'));
@chmod(__DIR__.'/youtube-dl', 0544);
if (command_exists(__DIR__.'/youtube-dl')) {
define('YTDL_EXE', __DIR__.'/youtube-dl');
} else {
fwrite(STDERR, "This script requires the tool/package 'youtube-dl'. Please install it first.\n");
exit(1);
}
}
 
// Now process the videos
 
yt_set_apikey_callback('_getApikey');
 
foreach ($rest_args as $resource) {
if ($verbose) echo "Handle: $resource\n";
if (strpos($resource, ':') === false) {
fwrite(STDERR, "Invalid resource '$resource' (you are missing the prefix, e.g. vurl: or vid:). Skipping.\n");
} else {
list($resourceType, $resourceValue) = explode(':', $resource, 2);
ytdwn_handle_resource($resourceType, $resourceValue);
}
}
 
// ------------------------------------------------------------------------------------------------
 
function ytdwn_handle_resource($resourceType, $resourceValue) {
if ($resourceType == 'vid') {
$video_id = parse_quoting($resourceValue);
ytdwn_video_id($video_id);
} else if ($resourceType == 'vurl') {
$video_url = parse_quoting($resourceValue);
$video_id = getVideoIDFromURL($video_url);
if (!$video_id) {
fwrite(STDERR, "$video_url is not a valid YouTube video URL. Skipping.\n");
} else {
ytdwn_video_id($video_id);
}
} else if ($resourceType == 'pid') {
$playlist_id = parse_quoting($resourceValue);
ytdwn_playlist_id($playlist_id);
} else if ($resourceType == 'purl') {
$playlist_url = parse_quoting($resourceValue);
$playlist_id = getPlaylistIDFromURL($playlist_url);
if (!$playlist_id) {
fwrite(STDERR, "$playlist_url is not a valid YouTube playlist URL. Skipping\n");
} else {
ytdwn_playlist_id($playlist_id);
}
} else if ($resourceType == 'cid') {
$channel_id = parse_quoting($resourceValue);
 
if (preg_match('@\[search=(.+)\]@ismU', $channel_id, $m)) {
$search = $m[1];
$channel_id = preg_replace('@\[search=(.+)\]@ismU', '', $channel_id);
} else {
$search = ''; // default
}
$search = parse_quoting($search);
 
ytdwn_channel_id($channel_id, $search);
} else if ($resourceType == 'cname') {
$channel_name = parse_quoting($resourceValue);
 
if (preg_match('@\[search=(.+)\]@ismU', $channel_name, $m)) {
$search = $m[1];
$channel_name = preg_replace('@\[search=(.+)\]@ismU', '', $channel_name);
} else {
$search = ''; // default
}
$search = parse_quoting($search);
 
$channel_name = parse_quoting($channel_name);
$channel_id = yt_get_channel_id($channel_name);
if (!$channel_id) {
fwrite(STDERR, "URL $channel_name is a valid YouTube channel or username. Skipping.\n");
} else {
ytdwn_channel_id($channel_id, $search);
}
} else if ($resourceType == 'curl') {
$channel_url = parse_quoting($resourceValue);
 
if (preg_match('@\[search=(.+)\]@ismU', $channel_url, $m)) {
$search = $m[1];
$channel_url = preg_replace('@\[search=(.+)\]@ismU', '', $channel_url);
} else {
$search = ''; // default
}
$search = parse_quoting($search);
 
$channel_url = parse_quoting($channel_url);
$channel_id = curl_to_cid($channel_url);
if (!$channel_id) {
fwrite(STDERR, "URL $channel_url is a valid YouTube channel oder username URL. Skipping\n");
} else {
ytdwn_channel_id($channel_id, $search);
}
} else if ($resourceType == 'search') {
$searchterm = parse_quoting($resourceValue);
 
$order = '';
if (preg_match('@\[order=(.+)\]@ismU', $searchterm, $m)) {
$order = $m[1];
$searchterm = preg_replace('@\[order=(.+)\]@ismU', '', $searchterm);
} else {
$order = DEFAULT_SEARCH_ORDER; // default
}
$order = parse_quoting($order);
 
$maxresults = '';
if (preg_match('@\[maxresults=(.+)\]@ismU', $searchterm, $m)) {
$maxresults = $m[1];
$searchterm = preg_replace('@\[maxresults=(.+)\]@ismU', '', $searchterm);
} else {
$maxresults = DEFAULT_SEARCH_MAXRESULTS; // default
}
$maxresults = parse_quoting($maxresults);
 
$searchterm = parse_quoting($searchterm);
 
ytdwn_search($searchterm, $order, $maxresults);
} else if ($resourceType == 'list') {
$list_files = glob(parse_quoting($resourceValue)); // in case the user entered a wildcard, e.g. *.list
foreach ($list_files as $list_file) {
if (!file_exists($list_file)) {
fwrite(STDERR, "List file $list_file does not exist. Skipping\n");
} else {
ytdwn_list_file($list_file);
}
}
} else {
fwrite(STDERR, "Resource type '$resourceType' is not valid. Skipping $resourceType:$resourceValue.\n");
}
}
 
function ytdwn_list_file($list_file) {
global $listFilenameStack, $verbose;
 
if ($verbose) echo "Processing list file '$list_file' ...\n";
 
$listFilenameStack[] = $list_file;
$lines = file($list_file);
foreach ($lines as $line) {
$line = trim($line);
if ($line == '') continue;
if ($line[0] == '#') continue;
if (strpos($line, ':') === false) {
fwrite(STDERR, "Invalid resource '$line' (you are missing the prefix, e.g. vurl: or vid:). Skipping.\n");
} else {
list($resourceType, $resourceValue) = explode(':',$line,2);
ytdwn_handle_resource($resourceType, $resourceValue);
}
}
array_pop($listFilenameStack);
}
 
function ytdwn_channel_id($channel_id, $search='') {
global $type;
global $verbose;
 
if ($verbose) echo "Processing channel ID '$channel_id' ...\n";
 
// List the videos of the channel
 
$cont = !empty(_getResultcache()) && file_exists(_getResultcache()) ? file_get_contents(_getResultcache()) : '';
$out = json_decode($cont, true);
if ($out == NULL) $out = array();
 
if (!empty(_getResultcache())) {
$stats = yt_get_channel_stats($channel_id);
$videocount = $stats['videoCount'];
 
$key = (!empty($search)) ? 'cid:'.$channel_id.'/'.$search : 'cid:'.$channel_id;
 
if (!isset($out[$key])) $out[$key] = array();
$videocount_old = isset($out[$key]['count']) ? $out[$key]['count'] : -1;
} else {
$videocount = -1;
$videocount_old = -2;
$key = '';
}
 
if ($videocount_old != $videocount) { // Attention: This might not work if videos are deleted and added (= the count stays the same)
if ($verbose && !empty(_getResultcache())) echo "Video count changed from $videocount_old to $videocount\n";
$out[$key]['count'] = $videocount;
if (!empty($search)) {
$out[$key]['results'] = yt_channel_items($channel_id, $search);
} else {
$out[$key]['results'] = yt_channel_items($channel_id);
}
} else {
if ($verbose) echo "Video count for channel is still $videocount, keep ".count($out[$key]['results'])." results.\n";
}
 
// Save the cache
 
try {
if (!empty(_getResultcache())) file_put_contents(_getResultcache(), json_encode($out));
} catch(Exception $e) {
fwrite(STDERR, "Cannot write result cache\n");
}
 
// Now download
 
foreach ($out[$key]['results'] as list($id, $title)) {
if ($verbose) echo "Downloading '$title' as ".hf_type($type)." ...\n";
ytdwn_video_id($id);
}
}
 
function ytdwn_playlist_id($playlist_id) {
global $type;
global $verbose;
 
if ($verbose) echo "Processing playlist ID '$playlist_id' ...\n";
 
// List the videos of the playlist
 
$cont = !empty(_getResultcache()) && file_exists(_getResultcache()) ? file_get_contents(_getResultcache()) : '';
$out = json_decode($cont, true);
if ($out == NULL) $out = array();
 
if (!empty(_getResultcache())) {
$stats = yt_get_playlist_stats($playlist_id);
$videocount = $stats['itemCount'];
 
$key = 'pid:'.$playlist_id;
 
if (!isset($out[$key])) $out[$key] = array();
$videocount_old = isset($out[$key]['count']) ? $out[$key]['count'] : -1;
} else {
$videocount = -1;
$videocount_old = -2;
$key = '';
}
 
if ($videocount_old != $videocount) { // Attention: This might not work if videos are deleted and added (= the count stays the same)
if ($verbose && !empty(_getResultcache())) echo "Video count changed from $videocount_old to $videocount\n";
$out[$key]['count'] = $videocount;
$out[$key]['results'] = yt_playlist_items($playlist_id);
} else {
if ($verbose) echo "Video count for playlist is still $videocount, keep ".count($out[$key]['results'])." results.\n";
}
 
// Save the cache
 
try {
if (!empty(_getResultcache())) file_put_contents(_getResultcache(), json_encode($out));
} catch(Exception $e) {
fwrite(STDERR, "Cannot write result cache\n");
}
 
// Now download
 
foreach ($out[$key]['results'] as list($id, $title)) {
if ($verbose) echo "Downloading '$title' as ".hf_type($type)." ...\n";
ytdwn_video_id($id);
}
}
 
function ytdwn_search($search, $order='', $maxresults=-1) {
global $type;
global $verbose;
 
if ($verbose) echo "Searching for '$search' ...\n";
 
// Perform the search and list the videos
 
$results = yt_search_items($search, $order, $maxresults);
 
// Now download
 
foreach ($results as list($id, $title)) {
if ($verbose) echo "Downloading '$title' as ".hf_type($type)." ...\n";
ytdwn_video_id($id);
}
}
 
function ytdwn_video_id($video_id) {
global $type;
global $verbose;
global $mp3id_transfer;
global $extra_args;
global $default_template;
global $failTreshold;
 
if (DOWNLOAD_SIMULATION_MODE) {
echo "SIMULATE download of video id $video_id as ".hf_type($type)." to "._getOutputDir()."\n";
return;
}
 
if (!empty(_getAlreadyDownloaded()) && in_alreadydownloaded_file($type, $video_id)) {
if ($verbose) echo "Video $video_id has already been downloaded. Skip.\n";
return true;
}
 
if (!empty(_getFailList()) && (ytdwn_fail_counter($type, $video_id) >= $failTreshold)) {
if ($verbose) echo "Video $video_id has failed too often. Skip.\n";
return true;
}
 
$out = '';
$code = -1;
 
$outputTemplate = rtrim(_getOutputDir(), '/').'/'.$default_template;
 
if (substr($type,0,2) == 'v:') {
$format = substr($type,2);
if (!empty($format)) {
exec(YTDL_EXE.' -o '.escapeshellarg($outputTemplate).' '.$extra_args.' '.escapeshellarg(vid_to_vurl($video_id)).' --format '.escapeshellarg($format), $out, $code);
} else {
exec(YTDL_EXE.' -o '.escapeshellarg($outputTemplate).' '.$extra_args.' '.escapeshellarg(vid_to_vurl($video_id)), $out, $code);
}
} else if (substr($type,0,2) == 'a:') {
$format = substr($type,2);
if (!empty($format)) {
exec(YTDL_EXE.' -o '.escapeshellarg($outputTemplate).' '.$extra_args.' '.escapeshellarg(vid_to_vurl($video_id)).' --extract-audio --audio-format '.escapeshellarg($format), $out, $code);
} else {
exec(YTDL_EXE.' -o '.escapeshellarg($outputTemplate).' '.$extra_args.' '.escapeshellarg(vid_to_vurl($video_id)).' --extract-audio', $out, $code);
}
if (($mp3id_transfer) && ($format == 'mp3')) mp3_transfer_vid_to_id();
} else assert(false);
 
if ($code == 0) {
if ($verbose) fwrite(STDOUT, "Successfully downloaded video ID $video_id as ".hf_type($type)."\n");
if (!empty(_getAlreadyDownloaded())) {
try {
addto_alreadydownloaded_file($type, $video_id);
} catch(Exception $e) {
fwrite(STDERR, "Cannot add to already downloaded file\n");
}
}
} else {
fwrite(STDERR, "Error downloading $video_id! (Code $code)\n");
if (!empty(_getFailList())) {
try {
ytdwn_register_fail($type, $video_id, $code);
} catch(Exception $e) {
fwrite(STDERR, "Cannot register fail\n");
}
}
return false;
}
 
return true;
}
 
function vid_to_vurl($video_id) {
return "http://www.youtube.com/watch/?v=$video_id";
}
 
function EndsWith($Haystack, $Needle){
return strrpos($Haystack, $Needle) === strlen($Haystack)-strlen($Needle);
}
 
function mp3_transfer_vid_to_id() {
global $verbose;
global $default_template;
 
if (!command_exists('id3v2')) {
if ($verbose) echo "Note: Tool id3v2 is not installed. Will not transfer the YouTube ID into the MP3 ID Tag\n";
return false;
}
 
if (!EndsWith($default_template, '-%(id)s.%(ext)s')) {
if ($verbose) echo "Note: Cannot transfer video tag to MP3 because default template does not end with '-%(id)s.%(ext)s'.\n";
return false;
}
 
$allok = true;
$files = glob(rtrim(_getOutputDir(), '/').'/*-???????????.mp3');
foreach ($files as $filename) {
if (!preg_match('@-([a-zA-Z0-9\-_]{11})\.mp3$@ismU', $filename, $m)) continue;
$yt_id = $m[1];
 
if (!yt_check_video_id($yt_id)) continue; // just to be sure
 
$orig_ts = filemtime($filename);
system('id3v2 -c '.escapeshellarg($yt_id).' '.escapeshellarg($filename), $ec);
touch($filename, $orig_ts);
if ($ec != 0) {
fwrite(STDERR, "Cannot set ID tag for file $filename\n");
$allok = false;
continue;
}
 
$target_filename = str_replace("-$yt_id.mp3", '.mp3', $filename);
if (!intelligent_rename($filename, $target_filename)) {
fwrite(STDERR, "Cannot move file $filename to $target_filename\n");
$allok = false;
continue;
}
}
return $allok;
}
 
function curl_to_cid($channel_url) {
if (preg_match("@https{0,1}://(www\\.|)youtube\\.com/user/(.*)(&|\\?)@ismU", $channel_url.'&', $m)) {
$username = $m[2];
$channel_id = yt_get_channel_id($username);
return $channel_id;
} else if (preg_match("@https{0,1}://(www\\.|)youtube\\.com/channel/(.*)(&|\\?)@ismU", $channel_url.'&', $m)) {
$channel_id = $m[2];
return $channel_id;
} else {
return false;
}
}
 
function in_alreadydownloaded_file($type, $video_id) {
$lines = file(_getAlreadyDownloaded());
foreach ($lines as $line) {
if (trim($line) == rtrim($type,':').':'.$video_id) {
return true;
}
}
return false;
}
 
function addto_alreadydownloaded_file($type, $video_id) {
file_put_contents(_getAlreadyDownloaded(), rtrim($type,':').':'.$video_id."\n", FILE_APPEND);
}
 
function syntax_error($msg) {
fwrite(STDERR, "Syntax error: ".trim($msg)."\n");
fwrite(STDERR, "Please use argument '--help' to show the syntax rules.\n");
exit(2);
}
 
function _help() {
global $argv;
$out = '';
$own = file_get_contents($argv[0]);
$help = explode('// ----', $own, 2)[0];
$help = preg_match_all('@^//(.*)$@mU', $help, $m);
foreach ($m[1] as $line) {
$out .= substr($line,1)."\n";
}
return $out;
}
 
function help() {
echo _help();
exit(0);
}
 
function version() {
echo explode("\n\n", _help(), 2)[0]."\n";
exit(0);
}
 
function command_exists($command) {
// https://stackoverflow.com/questions/592620/check-if-a-program-exists-from-a-bash-script
 
$ec = -1;
system('command -v '.escapeshellarg($command).' > /dev/null', $ec);
return ($ec == 0);
}
 
function hf_type($type) {
if (strpos($type, ':') === false) return $type; // invalid type (missing ':')
list($av, $format) = explode(':', $type);
 
if ($av == 'a') $av = 'audio';
else if ($av == 'v') $av = 'video';
else return $type; // invalid type
 
return (!empty($format)) ? $format.'-'.$av : $av;
}
 
function expand_tilde($path) {
// Source: http://jonathonhill.net/2013-09-03/tilde-expansion-in-php/
 
if (function_exists('posix_getuid') && strpos($path, '~') !== false) {
$info = posix_getpwuid(posix_getuid());
$path = str_replace('~', $info['dir'], $path);
}
 
return $path;
}
 
function _getLastListname() {
global $listFilenameStack;
$listname = ''; // default
if (count($listFilenameStack) > 0) {
$listname = $listFilenameStack[count($listFilenameStack)-1];
$listname = pathinfo($listname, PATHINFO_FILENAME); // remove file extension, e.g. ".list"
}
return $listname;
}
 
function _getApiKey() {
global $apikey;
 
$out = $apikey;
if (empty($out)) {
$auto_api_key = AUTO_API_KEY;
$auto_api_key = expand_tilde($auto_api_key);
$auto_api_key = str_replace('[listname]', _getLastListname(), $auto_api_key);
 
if (file_exists($auto_api_key)) {
$out = trim(file_get_contents($auto_api_key));
} else {
syntax_error("Please specify a YouTube API key with argument '-A'.");
}
} else {
$out = str_replace('[listname]', _getLastListname(), $out);
$out = expand_tilde($out);
 
if (file_exists($out)) {
$out = trim(file_get_contents($out));
} else {
// Assume, $out is a key, not a file
}
}
 
if (!yt_check_apikey_syntax($out)) syntax_error("'$out' is not a valid API key, not an existing file containing an API key.\n");
 
return $out;
}
 
function _getResultCache() {
global $resultcache;
if (empty($resultcache)) return '';
 
$out = expand_tilde($resultcache);
 
$out = str_replace('[listname]', _getLastListname(), $out);
 
if (!file_exists($out)) {
@touch($out);
if (!file_exists($out)) {
fwrite(STDERR, "File '$out' cannot be created. Disable feature.\n");
return '';
}
}
 
return $out;
}
 
function _getAlreadyDownloaded() {
global $alreadyDownloaded;
if (empty($alreadyDownloaded)) return '';
 
$out = expand_tilde($alreadyDownloaded);
 
$out = str_replace('[listname]', _getLastListname(), $out);
 
if (!file_exists($out)) {
@touch($out);
if (!file_exists($out)) {
fwrite(STDERR, "File '$out' cannot be created. Disable feature.\n");
return '';
}
}
 
return $out;
}
 
function _getFailList() {
global $failList;
if (empty($failList)) return '';
 
$out = expand_tilde($failList);
 
$out = str_replace('[listname]', _getLastListname(), $out);
 
if (!file_exists($out)) {
@touch($out);
if (!file_exists($out)) {
fwrite(STDERR, "File '$out' cannot be created. Disable feature.\n");
return '';
}
}
 
return $out;
}
 
function _getOutputDir() {
global $outputDir, $allow_creation_outputdir;
if (empty($outputDir)) return '.';
 
$out = expand_tilde($outputDir);
 
$out = str_replace('[listname]', _getLastListname(), $out);
 
if ($allow_creation_outputdir) {
if (!is_dir($out)) {
mkdir($out, true);
if (!is_dir($out)) {
fwrite(STDERR, "Output directory '$out' does not exist.\n");
exit(1);
}
}
} else {
if (!is_dir($out)) {
fwrite(STDERR, "Output directory '$out' does not exist.\n");
exit(1);
}
}
 
return $out;
}
 
function parse_quoting($str) {
if ((substr($str,0,1) == '"') && (substr($str,-1,1) == '"')) {
$str = substr($str,1,strlen($str)-2);
 
$escape = false;
$out = '';
for ($i=0; $i<strlen($str); $i++) {
$char = $str[$i];
 
if ($char == '\\') {
if ($escape) {
$out .= $char;
$escape = false;
} else {
$escape = true;
}
} else {
$out .= $char;
}
 
}
$str = $out;
 
}
 
return $str;
}
 
function ytdwn_register_fail($type, $video_id, $code) {
// Note: Error code $code ist currently not used
 
$file = _getFailList();
$cont = file_get_contents($file);
if (preg_match("@Video ID ".preg_quote($video_id,'@')." failed (\d+) time\(s\) with type ".preg_quote($type,'@')."@ismU", $cont, $m)) {
$cont = preg_replace("@Video ID ".preg_quote($video_id,'@')." failed (\d+) time\(s\) with type ".preg_quote($type,'@')."@ismU",
"Video ID $video_id failed ".($m[1]+1)." time(s) with type $type", $cont);
file_put_contents($file, $cont);
} else {
file_put_contents($file, "Video ID $video_id failed 1 time(s) with type $type\n", FILE_APPEND);
}
}
 
function ytdwn_fail_counter($type, $video_id) {
$file = _getFailList();
$cont = file_get_contents($file);
if (preg_match("@Video ID ".preg_quote($video_id,'@')." failed (\d+) time\(s\) with type ".preg_quote($type,'@')."@ismU", $cont, $m)) {
return $m[1];
} else {
return 0;
}
}
 
function intelligent_rename($src, $dest) {
$pos = strrpos($dest, '.');
$ext = substr($dest, $pos);
$namewoext = substr($dest, 0, $pos);
$failcnt = 1;
$dest_neu = $dest;
while (file_exists($dest_neu)) {
$failcnt++;
$dest_neu = "$namewoext ($failcnt)$ext";
}
return rename($src, $dest_neu);
}
Property changes:
Added: svn:executable
+*
\ No newline at end of property
/trunk/.
Property changes:
Added: svn:ignore
+marschall
+ffmpeg
+youtube-dl
+upload_to_vts