0

- Added adding lyrics (fixes #38)

- Album info endpoint switched
- Default network timeout increased
- Mobile and web user agent string updated
- Fixed adding empty authentication headers in web requests
- Version bump
This commit is contained in:
Kaimi
2022-10-11 08:50:40 +03:00
parent 85c3a40a8b
commit 808443cb32

122
src/ya.pl
View File

@@ -12,9 +12,9 @@ use constant IS_WIN => $^O eq 'MSWin32';
use constant use constant
{ {
NL => IS_WIN ? "\015\012" : "\012", NL => IS_WIN ? "\015\012" : "\012",
TIMEOUT => 5, TIMEOUT => 10,
AGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36', AGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36',
MOBILE_AGENT => 'Dalvik/10.1.0 (Linux; U; Android 10.0; Google Pixel 4 - 10.0.0 - API 29 - 768x1280 Build/LRX29M)', MOBILE_AGENT => 'Mozilla/5.0 (Linux; Android 13; Pixel 7 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Mobile Safari/537.36',
YANDEX_BASE => 'https://music.yandex.ru', YANDEX_BASE => 'https://music.yandex.ru',
MOBILE_YANDEX_BASE => 'https://api.music.yandex.net', MOBILE_YANDEX_BASE => 'https://api.music.yandex.net',
MD5_SALT => 'XGRlBW9FXlekgbPrRHuSiA', MD5_SALT => 'XGRlBW9FXlekgbPrRHuSiA',
@@ -25,8 +25,9 @@ use constant
MOBILE_PLAYLIST_INFO_MASK => '/users/%s/playlists/%d', MOBILE_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 => '/handlers/album.jsx?album=%d&lang=ru&external-domain=music.yandex.ru&overembed=false&ncrnd=%d', ALBUM_INFO_MASK => '/api/v2.1/handlers/album/%d?external-domain=music.yandex.ru&overembed=no&__t=%d',
MOBILE_ALBUM_INFO_MASK => '/albums/%d/with-tracks', MOBILE_ALBUM_INFO_MASK => '/albums/%d/with-tracks',
LYRICS_MASK => '/handlers/track.jsx?track=%d:%d&lang=ru&external-domain=music.yandex.ru&overembed=false&ncrnd=%d',
FILE_NAME_PATTERN => '#artist - #title', FILE_NAME_PATTERN => '#artist - #title',
DEFAULT_PERMISSIONS => 755, DEFAULT_PERMISSIONS => 755,
# For more details refer to 'create_track_entry' function # For more details refer to 'create_track_entry' function
@@ -53,7 +54,7 @@ use constant
HQ_BITRATE => '320', HQ_BITRATE => '320',
DEFAULT_CODEC => 'mp3', DEFAULT_CODEC => 'mp3',
PODCAST_TYPE => 'podcast', PODCAST_TYPE => 'podcast',
VERSION => '1.4', VERSION => '1.5',
COPYRIGHT => '© 2013-2022 by Kaimi (https://kaimi.io)', COPYRIGHT => '© 2013-2022 by Kaimi (https://kaimi.io)',
}; };
use constant use constant
@@ -267,39 +268,20 @@ if($opt{dir} && !-d $opt{dir})
exit(1); exit(1);
} }
MP3::Tag->config('id3v23_unsync', 0);
# Fix for "Writing of ID3v2.4 is not fully supported (prohibited now via `write_v24')" # Fix for "Writing of ID3v2.4 is not fully supported (prohibited now via `write_v24')"
MP3::Tag->config(write_v24 => 1); MP3::Tag->config
# Fix auth token and cookie format if required (
my $auth_token = ''; id3v23_unsync => 0,
if($opt{mobile} && $opt{auth}) write_v24 => 1,
{ decode_encoding_v2 => 'UTF-8'
if($opt{auth} !~ /${\(AUTH_TOKEN_PREFIX)}/i) );
{
$auth_token = AUTH_TOKEN_PREFIX;
}
$auth_token .= $opt{auth};
}
my $cookie = '';
if(!$opt{mobile} && $opt{cookie})
{
if($opt{cookie} !~ /${\(COOKIE_PREFIX)}/i)
{
$cookie = COOKIE_PREFIX;
}
$cookie .= $opt{cookie};
}
my ($whole_file, $total_size);
my $ua = LWP::UserAgent->new my $ua = LWP::UserAgent->new
( (
agent => $opt{mobile} ? MOBILE_AGENT : AGENT, agent => $opt{mobile} ? MOBILE_AGENT : AGENT,
default_headers => HTTP::Headers->new default_headers => HTTP::Headers->new
( (
Authorization => $auth_token, X_Retpath_Y => 1
X_Retpath_Y => 1,
Cookie => $cookie
), ),
cookie_jar => HTTP::Cookies->new cookie_jar => HTTP::Cookies->new
( (
@@ -310,8 +292,37 @@ my $ua = LWP::UserAgent->new
{ {
verify_hostname => $opt{debug} ? 0 : 1, verify_hostname => $opt{debug} ? 0 : 1,
SSL_verify_mode => $opt{debug} ? IO::Socket::SSL->SSL_VERIFY_NONE : IO::Socket::SSL->SSL_VERIFY_PEER, SSL_verify_mode => $opt{debug} ? IO::Socket::SSL->SSL_VERIFY_NONE : IO::Socket::SSL->SSL_VERIFY_PEER,
} },
send_te => 0
); );
# Fix auth token and cookie format if required
my $auth_token = '';
if($opt{mobile} && $opt{auth})
{
if($opt{auth} !~ /${\(AUTH_TOKEN_PREFIX)}/i)
{
$auth_token = AUTH_TOKEN_PREFIX;
}
$auth_token .= $opt{auth};
$ua->default_header(Authorization => $auth_token);
}
my $cookie = '';
if(!$opt{mobile} && $opt{cookie})
{
if($opt{cookie} !~ /${\(COOKIE_PREFIX)}/i)
{
$cookie = COOKIE_PREFIX;
}
$cookie .= $opt{cookie};
$ua->default_header(Cookie => $cookie);
}
my ($whole_file, $total_size);
my $json_decoder = JSON::PP->new->utf8->pretty->allow_nonref->allow_singlequote; my $json_decoder = JSON::PP->new->utf8->pretty->allow_nonref->allow_singlequote;
my @exclude = (); my @exclude = ();
my @include = (); my @include = ();
@@ -500,6 +511,7 @@ sub fetch_track
info(OK, 'Temporary saved track at '.$file_path); info(OK, 'Temporary saved track at '.$file_path);
fetch_album_cover($track_info_ref->{mp3tags}); fetch_album_cover($track_info_ref->{mp3tags});
fetch_track_lyrics($track_info_ref);
if(write_mp3_tags($file_path, $track_info_ref->{mp3tags})) if(write_mp3_tags($file_path, $track_info_ref->{mp3tags}))
{ {
@@ -940,6 +952,7 @@ sub create_track_entry
# APIC - album picture; TYER - year; # APIC - album picture; TYER - year;
# TIT2 - song title; TPE1 - song artist; # TIT2 - song title; TPE1 - song artist;
# TCON - track genre; TRCK - track number # TCON - track genre; TRCK - track number
# USLT - unsychronised lyrics
my %mp3_tags = (); my %mp3_tags = ();
# Special case for podcasts # Special case for podcasts
if($track_info->{albums}->[0]->{metaType} eq PODCAST_TYPE) if($track_info->{albums}->[0]->{metaType} eq PODCAST_TYPE)
@@ -1050,7 +1063,7 @@ sub fetch_album_cover
my $cover_url = $mp3tags->{APIC}; my $cover_url = $mp3tags->{APIC};
if(!$cover_url) if(!$cover_url)
{ {
info(DEBUG, 'Empty cover url'); info(DEBUG, 'Empty cover URL');
return; return;
} }
@@ -1058,7 +1071,7 @@ sub fetch_album_cover
$cover_url =~ s/%%/${\(COVER_RESOLUTION)}/; $cover_url =~ s/%%/${\(COVER_RESOLUTION)}/;
$cover_url = 'https://' . $cover_url; $cover_url = 'https://' . $cover_url;
info(DEBUG, 'Cover url: ' . $cover_url); info(DEBUG, 'Cover URL: ' . $cover_url);
my $request = $ua->get($cover_url); my $request = $ua->get($cover_url);
if(!$request->is_success) if(!$request->is_success)
@@ -1072,6 +1085,47 @@ sub fetch_album_cover
$mp3tags->{APIC} = [chr(0x0), 'image/jpg', chr(0x0), 'Cover (front)', $request->content]; $mp3tags->{APIC} = [chr(0x0), 'image/jpg', chr(0x0), 'Cover (front)', $request->content];
} }
sub fetch_track_lyrics
{
my $track_info_ref = shift;
my $mp3tags = $track_info_ref->{mp3tags};
my $lyrics_url = YANDEX_BASE.sprintf(LYRICS_MASK, $track_info_ref->{track_id}, $track_info_ref->{album_id}, time);
info(DEBUG, 'Lyrics URL: ' . $lyrics_url);
my $request = $ua->get($lyrics_url);
if(!$request->is_success)
{
info(DEBUG, 'Request failed');
log_response($request);
return;
}
my ($json_data) = $request->content;
if(!$json_data)
{
info(DEBUG, 'Can\'t parse JSON blob');
log_response($request);
return;
}
my $json = create_json($json_data);
if(!$json)
{
info(DEBUG, 'Can\'t create json from data');
log_response($request);
return;
}
if($json->{lyricsAvailable})
{
my $lyrics = $json->{lyric}->[0]->{fullLyrics};
# Encoding flag explanation: $03 UTF-8 [UTF-8]
$mp3tags->{USLT} = [3, 'eng', undef, $lyrics];
}
}
sub rename_track sub rename_track
{ {
my ($src_path, $dst_path) = @_; my ($src_path, $dst_path) = @_;