App-DrivePlayer
view release on metacpan or search on metacpan
lib/App/DrivePlayer/MetadataFetcher.pm view on Meta::CPAN
package App::DrivePlayer::MetadataFetcher;
use App::DrivePlayer::Setup;
use File::Temp qw( tempfile );
use HTTP::Tiny;
use JSON::PP qw( decode_json );
use URI::Escape qw( uri_escape_utf8 );
use Time::HiRes qw( sleep time usleep );
my $log = do { eval { require Log::Log4perl; Log::Log4perl->get_logger(__PACKAGE__) } };
Readonly my $USER_AGENT => 'DrivePlayer/1.0 (https://github.com/mvsjes2/drive_player)';
Readonly my $ITUNES_BASE => 'https://itunes.apple.com/search';
Readonly my $MB_BASE => 'https://musicbrainz.org/ws/2';
Readonly my $AID_BASE => 'https://api.acoustid.org/v2/lookup';
Readonly my $DRIVE_URL => 'https://www.googleapis.com/drive/v3/files/%s?alt=media';
Readonly my $MB_MIN_GAP => 1.1;
lib/App/DrivePlayer/MetadataFetcher.pm view on Meta::CPAN
}
# ------------------------------------------------------------------
# iTunes
# ------------------------------------------------------------------
sub _fetch_itunes {
my ($self, $title, $artist, $album) = @_;
my $term = join(' ', grep { length } $artist, $title);
my $url = $ITUNES_BASE . '?term=' . uri_escape_utf8($term)
. '&entity=song&media=music&limit=5';
my $data = $self->_get_plain($url) or return;
my $results = $data->{results} or return;
return unless @$results;
my $best = _best_itunes_match($results, $title, $artist, $album);
return unless $best;
my %meta;
lib/App/DrivePlayer/MetadataFetcher.pm view on Meta::CPAN
my @attempts;
push @attempts, 'recording:' . _mb_q($title) . '~ AND artist:' . _mb_q($artist)
. '~ AND release:' . _mb_q($album) . '~'
if $artist && $album;
push @attempts, 'recording:' . _mb_q($title) . '~ AND artist:' . _mb_q($artist) . '~'
if $artist;
push @attempts, 'recording:' . _mb_q($title) . '~';
for my $query (@attempts) {
my $url = "$MB_BASE/recording?query=" . uri_escape_utf8($query)
. '&fmt=json&limit=5&inc=releases+artist-credits+tags+genres';
my $data = $self->_get_mb($url) or next;
my $recs = $data->{recordings} or next;
next unless @$recs;
my $meta = $self->_parse_mb_with_release($recs->[0]);
return $meta if $meta && %$meta;
}
return;
}
lib/App/DrivePlayer/MetadataFetcher.pm view on Meta::CPAN
sub _fpcalc_available { fpcalc_available() }
sub _download_partial {
my ($self, $drive_id, $max_bytes) = @_;
my $token = $self->token_fn->();
unless ($token) {
$log->warn("Fingerprint: no bearer token available") if $log;
return;
}
my $url = sprintf $DRIVE_URL, uri_escape_utf8($drive_id);
my $max = ($max_bytes // ($DOWNLOAD_MB * 1024 * 1024)) - 1;
my $ua = HTTP::Tiny->new(agent => $USER_AGENT, timeout => 30);
my ($fh, $tmpfile) = tempfile(SUFFIX => '.audio', UNLINK => 0);
binmode $fh;
my $res = $ua->request('GET', $url, {
headers => {
Authorization => $token,
Range => "bytes=0-$max",
},
lib/App/DrivePlayer/MetadataFetcher.pm view on Meta::CPAN
my $json = qx($fpcalc -json -length 120 \Q$tmpfile\E 2>/dev/null);
return unless $json;
my $data = eval { decode_json($json) } or return;
return unless $data->{fingerprint} && $data->{duration};
return { fingerprint => $data->{fingerprint}, duration => int($data->{duration}) };
}
sub _query_acoustid {
my ($self, $fp) = @_;
my $url = $AID_BASE
. '?client=' . uri_escape_utf8($self->acoustid_key)
. '&meta=recordings+compress'
. '&duration=' . $fp->{duration}
. '&fingerprint=' . uri_escape_utf8($fp->{fingerprint});
my $data = $self->_get_plain($url) or return;
my $results = $data->{results} or return;
return unless @$results;
# Pick the result with the highest score
my ($best) = sort { $b->{score} <=> $a->{score} } @$results;
return unless $best->{score} && $best->{score} > 0.5;
my $recordings = $best->{recordings} or return;
( run in 2.168 seconds using v1.01-cache-2.11-cpan-2398b32b56e )