App-DrivePlayer
view release on metacpan or search on metacpan
lib/App/DrivePlayer/Scanner.pm view on Meta::CPAN
$self->on_track_found->(\%track) if $self->has_on_track_found;
}
# Parse filename into (title, artist, album, track_number, year).
# Handles: "NN - Artist - Title", "Artist - Title", "NN - Title", "Title"
sub _parse_filename {
my ($filename) = @_;
(my $base = $filename) =~ s/\.[^.]+$//;
my ($title, $artist, $album, $track_num, $year);
# "YYYY-..." prefix: capture the year and strip it before running the
# track-number patterns below (otherwise 2001 would match \d+ as track#).
if ($base =~ s{
^
( (?: 19 | 20) \d{2} ) # 4-digit year, 19xx or 20xx
-
}{}x) {
$year = $1;
}
if ($base =~ /^(\d+)[\s.\-]+(.+?)\s+[-â]\s+(.+)$/) {
($track_num, $artist, $title) = ($1 + 0, $2, $3);
} elsif ($base =~ /^(\d+)[\s.\-]+(.+)$/) {
($track_num, $title) = ($1 + 0, $2);
} elsif ($base =~ /^(.+?)\s+[-â]\s+(.+)$/) {
($artist, $title) = ($1, $2);
} else {
$title = $base;
}
if (!$year && $title && $title =~ s{
\s*
[ \( \[ ] # opening paren or bracket
( (?: 19 | 20) \d{2} ) # 4-digit year
[ \) \] ] # closing paren or bracket
\s*
$
}{}x) {
$year = $1;
}
return ($title // $base, $artist, $album, $track_num, $year);
}
1;
__END__
=head1 NAME
App::DrivePlayer::Scanner - Recursively scan a Google Drive folder and store tracks
=head1 SYNOPSIS
use App::DrivePlayer::Scanner;
my $scanner = App::DrivePlayer::Scanner->new(
drive => $drive_api, # Google::RestApi::DriveApi3
db => $db, # App::DrivePlayer::DB
on_progress => sub { say $_[0] },
on_track_found => sub { my $track = shift; ... },
);
$scanner->scan_folder($root_folder_id, 'My Music');
# From within an on_progress callback:
$scanner->stop;
=head1 DESCRIPTION
Walks a Google Drive folder hierarchy depth-first, recording every audio
file it finds into the DrivePlayer database. Non-audio files and Google
Docs are silently ignored.
Metadata (title, artist, album, track number, year) is extracted from the
filename using common naming conventions, and supplemented by inferring
artist and album from the folder path when the filename alone is ambiguous.
Supported filename patterns:
NN - Artist - Title.ext
Artist - Title.ext (en-dash also accepted)
NN - Title.ext
Title.ext
Year is extracted from a trailing C<(YYYY)> or C<[YYYY]> suffix.
A rescan of an existing folder replaces all previous data for that folder.
=head1 ATTRIBUTES
=head2 drive
is: ro, required: 1
A L<Google::RestApi::DriveApi3> instance (or any object with a C<list>
method matching that interface).
=head2 db
is: ro, required: 1
A L<App::DrivePlayer::DB> instance used to persist scan results.
=head2 on_progress
is: ro, isa: CodeRef, optional
Called with a single string message as each folder is entered or when a
Drive API error occurs. Calling L</stop> from within this callback will
prevent the current folder's Drive listing from being fetched.
=head2 on_track_found
is: ro, isa: CodeRef, optional
Called with a track hashref each time an audio file is successfully stored.
=head1 METHODS
( run in 1.387 second using v1.01-cache-2.11-cpan-d7a12ab2c7f )