Whitespace -> tab. Usage in readme now corresponds with script. Proxy support (forgot to mention in previous commit).
This commit is contained in:
17
README.md
17
README.md
@@ -8,16 +8,15 @@ Origin of the script is the following article: http://kaimi.ru/2013/11/yandex-mu
|
|||||||
|
|
||||||
```bat
|
```bat
|
||||||
ya.pl [-adkpt] [long options...]
|
ya.pl [-adkpt] [long options...]
|
||||||
-p --playlist playlist id to download
|
-p --playlist playlist id to download
|
||||||
-k --kind playlist kind (eg. ya-playlist, music-blog,
|
-k --kind playlist kind (eg. ya-playlist, music-blog, music-partners, etc.)
|
||||||
music-partners, etc.)
|
-a --album album to download
|
||||||
-a --album album to download
|
-t --track track to download (album id must be specified)
|
||||||
-t --track track to download (album id must be specified)
|
-d --dir download path (current direcotry will be used by default)
|
||||||
-d --dir download path (current direcotry will be used by
|
--proxy HTTP-proxy (format: 1.2.3.4:8888)
|
||||||
default)
|
|
||||||
|
|
||||||
--debug print debug info during work
|
--debug print debug info during work
|
||||||
--help print usage
|
--help print usage
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
ya.pl -p 123 -k ya-playlist
|
ya.pl -p 123 -k ya-playlist
|
||||||
|
|||||||
610
src/ya.pl
610
src/ya.pl
@@ -8,42 +8,42 @@ use YaHash;
|
|||||||
use constant IS_WIN => $^O eq 'MSWin32';
|
use constant IS_WIN => $^O eq 'MSWin32';
|
||||||
use constant
|
use constant
|
||||||
{
|
{
|
||||||
NL => IS_WIN ? "\015\012" : "\012",
|
NL => IS_WIN ? "\015\012" : "\012",
|
||||||
TARGET_ENC => IS_WIN ? 'cp1251' : 'utf8',
|
TARGET_ENC => IS_WIN ? 'cp1251' : 'utf8',
|
||||||
TIMEOUT => 5,
|
TIMEOUT => 5,
|
||||||
AGENT => 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0',
|
AGENT => 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0',
|
||||||
YANDEX_BASE => 'http://music.yandex.ru',
|
YANDEX_BASE => 'http://music.yandex.ru',
|
||||||
MUSIC_INFO_REGEX => qr/var\s+Mu\s+=\s+(.+?);\s+<\/script>/is,
|
MUSIC_INFO_REGEX => qr/var\s+Mu\s+=\s+(.+?);\s+<\/script>/is,
|
||||||
DOWNLOAD_INFO_MASK => '/api/v1.5/handlers/api-jsonp.jsx?requestId=2&nc=%d&action=getTrackSrc&p=download-info/%s/2.mp3',
|
DOWNLOAD_INFO_MASK => '/api/v1.5/handlers/api-jsonp.jsx?requestId=2&nc=%d&action=getTrackSrc&p=download-info/%s/2.mp3',
|
||||||
DOWNLOAD_PATH_MASK => 'http://%s/get-mp3/%s/%s?track-id=%s&from=service-10-track&similarities-experiment=default',
|
DOWNLOAD_PATH_MASK => 'http://%s/get-mp3/%s/%s?track-id=%s&from=service-10-track&similarities-experiment=default',
|
||||||
PLAYLIST_INFO_MASK => '/users/%s/playlists/%d',
|
PLAYLIST_INFO_MASK => '/users/%s/playlists/%d',
|
||||||
PLAYLIST_REQ_PART => '{"userFeed":"old","similarities":"default","genreRadio":"new-ichwill-matrixnet6","recommendedArtists":"ichwill_similar_artists","recommendedTracks":"recommended_tracks_by_artist_from_history","recommendedAlbumsOfFavoriteGenre":"recent","recommendedSimilarArtists":"default","recommendedArtistsWithArtistsFromHistory":"force_recent","adv":"a","loserArtistsWithArtists":"off","ny2015":"no"}',
|
PLAYLIST_REQ_PART => '{"userFeed":"old","similarities":"default","genreRadio":"new-ichwill-matrixnet6","recommendedArtists":"ichwill_similar_artists","recommendedTracks":"recommended_tracks_by_artist_from_history","recommendedAlbumsOfFavoriteGenre":"recent","recommendedSimilarArtists":"default","recommendedArtistsWithArtistsFromHistory":"force_recent","adv":"a","loserArtistsWithArtists":"off","ny2015":"no"}',
|
||||||
PLAYLIST_FULL_INFO => '/handlers/track-entries.jsx',
|
PLAYLIST_FULL_INFO => '/handlers/track-entries.jsx',
|
||||||
ALBUM_INFO_MASK => '/album/%d',
|
ALBUM_INFO_MASK => '/album/%d',
|
||||||
FILE_SAVE_EXT => '.mp3',
|
FILE_SAVE_EXT => '.mp3',
|
||||||
ARTIST_TITLE_DELIM => ' - '
|
ARTIST_TITLE_DELIM => ' - '
|
||||||
};
|
};
|
||||||
use constant
|
use constant
|
||||||
{
|
{
|
||||||
DEBUG => 'DEBUG',
|
DEBUG => 'DEBUG',
|
||||||
ERROR => 'ERROR',
|
ERROR => 'ERROR',
|
||||||
INFO => 'INFO',
|
INFO => 'INFO',
|
||||||
OK => 'OK'
|
OK => 'OK'
|
||||||
};
|
};
|
||||||
|
|
||||||
my %log_colors =
|
my %log_colors =
|
||||||
(
|
(
|
||||||
&DEBUG => 'red on_white',
|
&DEBUG => 'red on_white',
|
||||||
&ERROR => 'red',
|
&ERROR => 'red',
|
||||||
&INFO => 'blue on_white',
|
&INFO => 'blue on_white',
|
||||||
&OK => 'green on_white'
|
&OK => 'green on_white'
|
||||||
);
|
);
|
||||||
|
|
||||||
my %req_modules =
|
my %req_modules =
|
||||||
(
|
(
|
||||||
NIX => [],
|
NIX => [],
|
||||||
WIN => [ qw/Win32::Console::ANSI/ ],
|
WIN => [ qw/Win32::Console::ANSI/ ],
|
||||||
ALL => [ qw/JSON::PP Getopt::Long::Descriptive Term::ANSIColor LWP::UserAgent HTTP::Cookies HTML::Entities/ ]
|
ALL => [ qw/JSON::PP Getopt::Long::Descriptive Term::ANSIColor LWP::UserAgent HTTP::Cookies HTML::Entities/ ]
|
||||||
);
|
);
|
||||||
|
|
||||||
$\ = NL;
|
$\ = NL;
|
||||||
@@ -51,50 +51,50 @@ $\ = NL;
|
|||||||
my @missing_modules;
|
my @missing_modules;
|
||||||
for(@{$req_modules{ALL}}, IS_WIN ? @{$req_modules{WIN}} : @{$req_modules{NIX}})
|
for(@{$req_modules{ALL}}, IS_WIN ? @{$req_modules{WIN}} : @{$req_modules{NIX}})
|
||||||
{
|
{
|
||||||
eval "require $_";
|
eval "require $_";
|
||||||
if($@)
|
if($@)
|
||||||
{
|
{
|
||||||
($_) = $@ =~ /locate (.+?)(?:\.pm)? in \@INC/;
|
($_) = $@ =~ /locate (.+?)(?:\.pm)? in \@INC/;
|
||||||
$_ =~ s/\//::/g;
|
$_ =~ s/\//::/g;
|
||||||
push @missing_modules, $_;
|
push @missing_modules, $_;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(@missing_modules)
|
if(@missing_modules)
|
||||||
{
|
{
|
||||||
print 'Please, install this modules: '.join ', ', @missing_modules;
|
print 'Please, install this modules: '.join ', ', @missing_modules;
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
my ($opt, $usage) = Getopt::Long::Descriptive::describe_options
|
my ($opt, $usage) = Getopt::Long::Descriptive::describe_options
|
||||||
(
|
(
|
||||||
basename(__FILE__).' %o',
|
basename(__FILE__).' %o',
|
||||||
['playlist|p:i', 'playlist id to download'],
|
['playlist|p:i', 'playlist id to download'],
|
||||||
['kind|k:s', 'playlist kind (eg. ya-playlist, music-blog, music-partners, etc.)'],
|
['kind|k:s', 'playlist kind (eg. ya-playlist, music-blog, music-partners, etc.)'],
|
||||||
['album|a:i', 'album to download'],
|
['album|a:i', 'album to download'],
|
||||||
['track|t:i', 'track to download (album id must be specified)'],
|
['track|t:i', 'track to download (album id must be specified)'],
|
||||||
['dir|d:s', 'download path (current direcotry will be used by default)', {default => '.'}],
|
['dir|d:s', 'download path (current direcotry will be used by default)', {default => '.'}],
|
||||||
['proxy=s', 'HTTP-proxy (format: 1.2.3.4:8888)'],
|
['proxy=s', 'HTTP-proxy (format: 1.2.3.4:8888)'],
|
||||||
[],
|
[],
|
||||||
['debug', 'print debug info during work'],
|
['debug', 'print debug info during work'],
|
||||||
['help', 'print usage'],
|
['help', 'print usage'],
|
||||||
[],
|
[],
|
||||||
['Example: '],
|
['Example: '],
|
||||||
["\t".basename(__FILE__).' -p 123 -k ya-playlist'],
|
["\t".basename(__FILE__).' -p 123 -k ya-playlist'],
|
||||||
["\t".basename(__FILE__).' -a 123'],
|
["\t".basename(__FILE__).' -a 123'],
|
||||||
["\t".basename(__FILE__).' -a 123 -t 321']
|
["\t".basename(__FILE__).' -a 123 -t 321']
|
||||||
);
|
);
|
||||||
|
|
||||||
if( $opt->help || ( !($opt->track && $opt->album) && !$opt->album && !($opt->playlist && $opt->kind) ) )
|
if( $opt->help || ( !($opt->track && $opt->album) && !$opt->album && !($opt->playlist && $opt->kind) ) )
|
||||||
{
|
{
|
||||||
print $usage->text;
|
print $usage->text;
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if($opt->dir && !-d $opt->dir)
|
if($opt->dir && !-d $opt->dir)
|
||||||
{
|
{
|
||||||
info(ERROR, 'Please, specify an existing directory');
|
info(ERROR, 'Please, specify an existing directory');
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
my ($whole_file, $total_size);
|
my ($whole_file, $total_size);
|
||||||
@@ -109,276 +109,276 @@ if($opt->proxy)
|
|||||||
|
|
||||||
if($opt->album || ($opt->playlist && $opt->kind))
|
if($opt->album || ($opt->playlist && $opt->kind))
|
||||||
{
|
{
|
||||||
my @track_list_info;
|
my @track_list_info;
|
||||||
|
|
||||||
if($opt->album)
|
if($opt->album)
|
||||||
{
|
{
|
||||||
info(INFO, 'Fetching album info: '.$opt->album);
|
info(INFO, 'Fetching album info: '.$opt->album);
|
||||||
|
|
||||||
@track_list_info = get_album_tracks_info($opt->album);
|
@track_list_info = get_album_tracks_info($opt->album);
|
||||||
|
|
||||||
if($opt->track)
|
if($opt->track)
|
||||||
{
|
{
|
||||||
info(INFO, 'Filtering single track: '.$opt->track.' ['.$opt->album.']');
|
info(INFO, 'Filtering single track: '.$opt->track.' ['.$opt->album.']');
|
||||||
@track_list_info = grep
|
@track_list_info = grep
|
||||||
(
|
(
|
||||||
(split(/\./, $_->{dir}))[1] eq $opt->track
|
(split(/\./, $_->{dir}))[1] eq $opt->track
|
||||||
,
|
,
|
||||||
@track_list_info
|
@track_list_info
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
info(INFO, 'Fetching playlist info: '.$opt->playlist.' ['.$opt->kind.']');
|
info(INFO, 'Fetching playlist info: '.$opt->playlist.' ['.$opt->kind.']');
|
||||||
|
|
||||||
@track_list_info = get_playlist_tracks_info($opt->playlist);
|
@track_list_info = get_playlist_tracks_info($opt->playlist);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if(!@track_list_info)
|
if(!@track_list_info)
|
||||||
{
|
{
|
||||||
info(ERROR, 'Can\'t get track list info');
|
info(ERROR, 'Can\'t get track list info');
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
for my $track_info_ref(@track_list_info)
|
for my $track_info_ref(@track_list_info)
|
||||||
{
|
{
|
||||||
if(!$track_info_ref->{title})
|
if(!$track_info_ref->{title})
|
||||||
{
|
{
|
||||||
info(ERROR, 'Track with non-existent title. Skipping...');
|
info(ERROR, 'Track with non-existent title. Skipping...');
|
||||||
next;
|
next;
|
||||||
}
|
}
|
||||||
if(!$track_info_ref->{dir})
|
if(!$track_info_ref->{dir})
|
||||||
{
|
{
|
||||||
info(ERROR, 'Track with non-existent path (deleted?). Skipping...');
|
info(ERROR, 'Track with non-existent path (deleted?). Skipping...');
|
||||||
next;
|
next;
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch_track($track_info_ref);
|
fetch_track($track_info_ref);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub fetch_track
|
sub fetch_track
|
||||||
{
|
{
|
||||||
my $track_info_ref = shift;
|
my $track_info_ref = shift;
|
||||||
|
|
||||||
fix_encoding(\$track_info_ref->{title});
|
fix_encoding(\$track_info_ref->{title});
|
||||||
$track_info_ref->{title} =~ s/\s+$//;
|
$track_info_ref->{title} =~ s/\s+$//;
|
||||||
$track_info_ref->{title} =~ s/[\\\/:"*?<>|]+/-/g;
|
$track_info_ref->{title} =~ s/[\\\/:"*?<>|]+/-/g;
|
||||||
|
|
||||||
info(INFO, 'Trying to fetch track: '.$track_info_ref->{title});
|
info(INFO, 'Trying to fetch track: '.$track_info_ref->{title});
|
||||||
|
|
||||||
my $track_url = get_track_url($track_info_ref->{dir});
|
my $track_url = get_track_url($track_info_ref->{dir});
|
||||||
if(!$track_url)
|
if(!$track_url)
|
||||||
{
|
{
|
||||||
info(ERROR, 'Can\'t get track url');
|
info(ERROR, 'Can\'t get track url');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
my $file_path = download_track($track_url, $track_info_ref->{title});
|
my $file_path = download_track($track_url, $track_info_ref->{title});
|
||||||
if(!$file_path)
|
if(!$file_path)
|
||||||
{
|
{
|
||||||
info(ERROR, 'Failed to download track');
|
info(ERROR, 'Failed to download track');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
info(OK, 'Saved track at '.$file_path);
|
info(OK, 'Saved track at '.$file_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub download_track
|
sub download_track
|
||||||
{
|
{
|
||||||
my ($url, $title) = @_;
|
my ($url, $title) = @_;
|
||||||
|
|
||||||
my $request = $ua->head($url);
|
my $request = $ua->head($url);
|
||||||
if(!$request->is_success)
|
if(!$request->is_success)
|
||||||
{
|
{
|
||||||
info(DEBUG, 'HEAD request failed');
|
info(DEBUG, 'HEAD request failed');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$whole_file = '';
|
$whole_file = '';
|
||||||
$total_size = $request->headers->content_length;
|
$total_size = $request->headers->content_length;
|
||||||
info(DEBUG, 'File size from header: '.$total_size);
|
info(DEBUG, 'File size from header: '.$total_size);
|
||||||
|
|
||||||
$request = $ua->get($url, ':content_cb' => \&progress);
|
$request = $ua->get($url, ':content_cb' => \&progress);
|
||||||
if(!$request->is_success)
|
if(!$request->is_success)
|
||||||
{
|
{
|
||||||
info(DEBUG, 'GET request failed in '.(caller(0))[3]);
|
info(DEBUG, 'GET request failed in '.(caller(0))[3]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
my $file_path = $opt->dir.'/'.$title.FILE_SAVE_EXT;
|
my $file_path = $opt->dir.'/'.$title.FILE_SAVE_EXT;
|
||||||
if(open(F, '>', $file_path))
|
if(open(F, '>', $file_path))
|
||||||
{
|
{
|
||||||
local $\ = undef;
|
local $\ = undef;
|
||||||
|
|
||||||
binmode F;
|
binmode F;
|
||||||
print F $whole_file;
|
print F $whole_file;
|
||||||
close F;
|
close F;
|
||||||
|
|
||||||
my $disk_data_size = -s $file_path;
|
my $disk_data_size = -s $file_path;
|
||||||
|
|
||||||
if($total_size && $disk_data_size != $total_size)
|
if($total_size && $disk_data_size != $total_size)
|
||||||
{
|
{
|
||||||
info(DEBUG, 'Actual file size differs from expected ('.$disk_data_size.'/'.$total_size.')');
|
info(DEBUG, 'Actual file size differs from expected ('.$disk_data_size.'/'.$total_size.')');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $file_path;
|
return $file_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
info(DEBUG, 'Failed to open file '.$file_path);
|
info(DEBUG, 'Failed to open file '.$file_path);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub get_track_url
|
sub get_track_url
|
||||||
{
|
{
|
||||||
my $storage_dir = shift;
|
my $storage_dir = shift;
|
||||||
|
|
||||||
my $request = $ua->get(YANDEX_BASE.sprintf(DOWNLOAD_INFO_MASK, time, $storage_dir));
|
my $request = $ua->get(YANDEX_BASE.sprintf(DOWNLOAD_INFO_MASK, time, $storage_dir));
|
||||||
if(!$request->is_success)
|
if(!$request->is_success)
|
||||||
{
|
{
|
||||||
info(DEBUG, 'Request failed');
|
info(DEBUG, 'Request failed');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
my ($json_data) = $request->content;
|
my ($json_data) = $request->content;
|
||||||
if(!$json_data)
|
if(!$json_data)
|
||||||
{
|
{
|
||||||
info(DEBUG, 'Can\'t parse JSON blob');
|
info(DEBUG, 'Can\'t parse JSON blob');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
my $json = create_json($json_data);
|
my $json = create_json($json_data);
|
||||||
if(!$json)
|
if(!$json)
|
||||||
{
|
{
|
||||||
info(DEBUG, 'Can\'t create json from data');
|
info(DEBUG, 'Can\'t create json from data');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
my %fields =
|
my %fields =
|
||||||
(
|
(
|
||||||
host => $json->{host},
|
host => $json->{host},
|
||||||
path => $json->{path},
|
path => $json->{path},
|
||||||
ts => $json->{ts},
|
ts => $json->{ts},
|
||||||
region => $json->{region},
|
region => $json->{region},
|
||||||
s => $json->{s}
|
s => $json->{s}
|
||||||
);
|
);
|
||||||
|
|
||||||
my $hash = hash(substr($fields{path}, 1) . $fields{s});
|
my $hash = hash(substr($fields{path}, 1) . $fields{s});
|
||||||
|
|
||||||
my $url = sprintf(DOWNLOAD_PATH_MASK, $fields{host}, $hash, $fields{ts}.$fields{path}, (split /\./, $storage_dir)[1]);
|
my $url = sprintf(DOWNLOAD_PATH_MASK, $fields{host}, $hash, $fields{ts}.$fields{path}, (split /\./, $storage_dir)[1]);
|
||||||
|
|
||||||
info(DEBUG, 'Track url: '.$url);
|
info(DEBUG, 'Track url: '.$url);
|
||||||
|
|
||||||
return $url;
|
return $url;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub get_album_tracks_info
|
sub get_album_tracks_info
|
||||||
{
|
{
|
||||||
my $album_id = shift;
|
my $album_id = shift;
|
||||||
|
|
||||||
my $request = $ua->get(YANDEX_BASE.sprintf(ALBUM_INFO_MASK, $album_id));
|
my $request = $ua->get(YANDEX_BASE.sprintf(ALBUM_INFO_MASK, $album_id));
|
||||||
if(!$request->is_success)
|
if(!$request->is_success)
|
||||||
{
|
{
|
||||||
info(DEBUG, 'Request failed');
|
info(DEBUG, 'Request failed');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
my ($json_data) = ($request->as_string =~ MUSIC_INFO_REGEX);
|
my ($json_data) = ($request->as_string =~ MUSIC_INFO_REGEX);
|
||||||
if(!$json_data)
|
if(!$json_data)
|
||||||
{
|
{
|
||||||
info(DEBUG, 'Can\'t parse JSON blob');
|
info(DEBUG, 'Can\'t parse JSON blob');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
my $json = create_json($json_data);
|
my $json = create_json($json_data);
|
||||||
if(!$json)
|
if(!$json)
|
||||||
{
|
{
|
||||||
info(DEBUG, 'Can\'t create json from data');
|
info(DEBUG, 'Can\'t create json from data');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
my $title = $json->{pageData}->{title};
|
my $title = $json->{pageData}->{title};
|
||||||
if(!$title)
|
if(!$title)
|
||||||
{
|
{
|
||||||
info(DEBUG, 'Can\'t get album title');
|
info(DEBUG, 'Can\'t get album title');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fix_encoding(\$title);
|
fix_encoding(\$title);
|
||||||
|
|
||||||
info(INFO, 'Album title: '.$title);
|
info(INFO, 'Album title: '.$title);
|
||||||
info(INFO, 'Tracks total: '. $json->{pageData}->{trackCount});
|
info(INFO, 'Tracks total: '. $json->{pageData}->{trackCount});
|
||||||
|
|
||||||
return map
|
return map
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
dir => $_->{storageDir},
|
dir => $_->{storageDir},
|
||||||
title=> $_->{artists}->[0]->{name} . ARTIST_TITLE_DELIM . $_->{title}
|
title=> $_->{artists}->[0]->{name} . ARTIST_TITLE_DELIM . $_->{title}
|
||||||
}
|
}
|
||||||
} @{ $json->{pageData}->{volumes}->[0] };
|
} @{ $json->{pageData}->{volumes}->[0] };
|
||||||
}
|
}
|
||||||
|
|
||||||
sub get_playlist_tracks_info
|
sub get_playlist_tracks_info
|
||||||
{
|
{
|
||||||
my $playlist_id = shift;
|
my $playlist_id = shift;
|
||||||
|
|
||||||
my $request = $ua->get(YANDEX_BASE.sprintf(PLAYLIST_INFO_MASK, $opt->kind, $playlist_id));
|
my $request = $ua->get(YANDEX_BASE.sprintf(PLAYLIST_INFO_MASK, $opt->kind, $playlist_id));
|
||||||
if(!$request->is_success)
|
if(!$request->is_success)
|
||||||
{
|
{
|
||||||
info(DEBUG, 'Request failed');
|
info(DEBUG, 'Request failed');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
my ($json_data) = ($request->as_string =~ MUSIC_INFO_REGEX);
|
my ($json_data) = ($request->as_string =~ MUSIC_INFO_REGEX);
|
||||||
if(!$json_data)
|
if(!$json_data)
|
||||||
{
|
{
|
||||||
info(DEBUG, 'Can\'t parse JSON blob');
|
info(DEBUG, 'Can\'t parse JSON blob');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
my $json = create_json($json_data);
|
my $json = create_json($json_data);
|
||||||
if(!$json)
|
if(!$json)
|
||||||
{
|
{
|
||||||
info(DEBUG, 'Can\'t create json from data');
|
info(DEBUG, 'Can\'t create json from data');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
my $title = $json->{pageData}->{playlist}->{title};
|
my $title = $json->{pageData}->{playlist}->{title};
|
||||||
if(!$title)
|
if(!$title)
|
||||||
{
|
{
|
||||||
info(DEBUG, 'Can\'t get playlist title');
|
info(DEBUG, 'Can\'t get playlist title');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fix_encoding(\$title);
|
fix_encoding(\$title);
|
||||||
|
|
||||||
info(INFO, 'Playlist title: '.$title);
|
info(INFO, 'Playlist title: '.$title);
|
||||||
info(INFO, 'Tracks total: '. $json->{pageData}->{playlist}->{trackCount});
|
info(INFO, 'Tracks total: '. $json->{pageData}->{playlist}->{trackCount});
|
||||||
|
|
||||||
my @tracks_info;
|
my @tracks_info;
|
||||||
|
|
||||||
if($json->{pageData}->{playlist}->{trackIds})
|
if($json->{pageData}->{playlist}->{trackIds})
|
||||||
{
|
{
|
||||||
my @playlist_chunks;
|
my @playlist_chunks;
|
||||||
my $tracks_ref = $json->{pageData}->{playlist}->{trackIds};
|
my $tracks_ref = $json->{pageData}->{playlist}->{trackIds};
|
||||||
my $sign = $json->{authData}->{user}->{sign};
|
my $sign = $json->{authData}->{user}->{sign};
|
||||||
|
|
||||||
push @playlist_chunks, [splice @{$tracks_ref}, 0, 150] while @{$tracks_ref};
|
push @playlist_chunks, [splice @{$tracks_ref}, 0, 150] while @{$tracks_ref};
|
||||||
|
|
||||||
for my $chunk(@playlist_chunks)
|
for my $chunk(@playlist_chunks)
|
||||||
{
|
{
|
||||||
$request = $ua->post
|
$request = $ua->post
|
||||||
(
|
(
|
||||||
YANDEX_BASE.PLAYLIST_FULL_INFO,
|
YANDEX_BASE.PLAYLIST_FULL_INFO,
|
||||||
{
|
{
|
||||||
strict => 'true',
|
strict => 'true',
|
||||||
sign => $sign,
|
sign => $sign,
|
||||||
lang => 'ru',
|
lang => 'ru',
|
||||||
experiments => PLAYLIST_REQ_PART,
|
experiments => PLAYLIST_REQ_PART,
|
||||||
entries => join ',', @{$chunk}
|
entries => join ',', @{$chunk}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if(!$request->is_success)
|
if(!$request->is_success)
|
||||||
@@ -388,11 +388,11 @@ sub get_playlist_tracks_info
|
|||||||
}
|
}
|
||||||
|
|
||||||
$json = create_json($request->content);
|
$json = create_json($request->content);
|
||||||
if(!$json)
|
if(!$json)
|
||||||
{
|
{
|
||||||
info(DEBUG, 'Can\'t create json from data');
|
info(DEBUG, 'Can\'t create json from data');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
push @tracks_info,
|
push @tracks_info,
|
||||||
map
|
map
|
||||||
@@ -403,16 +403,16 @@ sub get_playlist_tracks_info
|
|||||||
}
|
}
|
||||||
} @{ $json };
|
} @{ $json };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@tracks_info = map
|
@tracks_info = map
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
dir => $_->{storageDir},
|
dir => $_->{storageDir},
|
||||||
title=> $_->{artists}->[0]->{name} . ARTIST_TITLE_DELIM . $_->{title}
|
title=> $_->{artists}->[0]->{name} . ARTIST_TITLE_DELIM . $_->{title}
|
||||||
}
|
}
|
||||||
} @{ $json->{pageData}->{playlist}->{tracks} };
|
} @{ $json->{pageData}->{playlist}->{tracks} };
|
||||||
}
|
}
|
||||||
|
|
||||||
return @tracks_info;
|
return @tracks_info;
|
||||||
@@ -420,63 +420,63 @@ sub get_playlist_tracks_info
|
|||||||
|
|
||||||
sub create_json
|
sub create_json
|
||||||
{
|
{
|
||||||
my $json_data = shift;
|
my $json_data = shift;
|
||||||
|
|
||||||
HTML::Entities::decode_entities($json_data);
|
HTML::Entities::decode_entities($json_data);
|
||||||
|
|
||||||
my $json;
|
my $json;
|
||||||
eval
|
eval
|
||||||
{
|
{
|
||||||
$json = $json_decoder->decode($json_data);
|
$json = $json_decoder->decode($json_data);
|
||||||
};
|
};
|
||||||
|
|
||||||
if($@)
|
if($@)
|
||||||
{
|
{
|
||||||
info(DEBUG, 'Error decoding json '.$@);
|
info(DEBUG, 'Error decoding json '.$@);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $json;
|
return $json;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub fix_encoding
|
sub fix_encoding
|
||||||
{
|
{
|
||||||
my $ref = shift;
|
my $ref = shift;
|
||||||
from_to($$ref, 'unicode', TARGET_ENC);
|
from_to($$ref, 'unicode', TARGET_ENC);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub info
|
sub info
|
||||||
{
|
{
|
||||||
my ($type, $msg) = @_;
|
my ($type, $msg) = @_;
|
||||||
|
|
||||||
if($type eq DEBUG)
|
if($type eq DEBUG)
|
||||||
{
|
{
|
||||||
return if !$opt->debug;
|
return if !$opt->debug;
|
||||||
# Func, line, msg
|
# Func, line, msg
|
||||||
$msg = (caller(1))[3] . "(" . (caller(0))[2] . "): " . $msg;
|
$msg = (caller(1))[3] . "(" . (caller(0))[2] . "): " . $msg;
|
||||||
}
|
}
|
||||||
# Actual terminal width detection?
|
# Actual terminal width detection?
|
||||||
$msg = Term::ANSIColor::colored('['.$type.']', $log_colors{$type}) . ' ' . $msg;
|
$msg = Term::ANSIColor::colored('['.$type.']', $log_colors{$type}) . ' ' . $msg;
|
||||||
$msg .= ' ' x (80 - length($msg) - length($\));
|
$msg .= ' ' x (80 - length($msg) - length($\));
|
||||||
|
|
||||||
print $msg;
|
print $msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub progress
|
sub progress
|
||||||
{
|
{
|
||||||
my ($data, undef, undef) = @_;
|
my ($data, undef, undef) = @_;
|
||||||
|
|
||||||
$whole_file .= $data;
|
$whole_file .= $data;
|
||||||
print progress_bar(length($whole_file), $total_size);
|
print progress_bar(length($whole_file), $total_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub progress_bar
|
sub progress_bar
|
||||||
{
|
{
|
||||||
my ($got, $total, $width, $char) = @_;
|
my ($got, $total, $width, $char) = @_;
|
||||||
|
|
||||||
$width ||= 25; $char ||= '=';
|
$width ||= 25; $char ||= '=';
|
||||||
my $num_width = length $total;
|
my $num_width = length $total;
|
||||||
sprintf "|%-${width}s| Got %${num_width}s bytes of %s (%.2f%%)\r",
|
sprintf "|%-${width}s| Got %${num_width}s bytes of %s (%.2f%%)\r",
|
||||||
$char x (($width-1) * $got / $total). '>',
|
$char x (($width-1) * $got / $total). '>',
|
||||||
$got, $total, 100 * $got / +$total;
|
$got, $total, 100 * $got / +$total;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user