App-DrivePlayer

 view release on metacpan or  search on metacpan

lib/App/DrivePlayer/Scanner.pm  view on Meta::CPAN


    $self->_seen_folder_ids->{$drive_id} = 1;

    my $folder = $self->db->upsert_folder(
        drive_id        => $drive_id,
        name            => $folder_name,
        parent_drive_id => $args{parent_drive_id},
        path            => $path,
        scan_folder_id  => $scan_folder_id,
    );

    my @items = eval {
        $self->drive->list(
            filter => "'$drive_id' in parents and trashed=false",
            params => { fields => $DRIVE_FIELDS, pageSize => 1000 },
        );
    };
    if ($@) {
        $self->_progress("Error scanning $path: $@");
        return;
    }

    # Normalize Drive response bytes to decoded Unicode so SQLite doesn't
    # re-encode them as Latin-1 → UTF-8 (double-encoding).
    $_->{name} = _u8($_->{name}) for @items;

    my (@subfolders, @audio_files);
    for my $item (@items) {
        if ($item->{mimeType} eq $FOLDER_MIME) {
            push @subfolders, $item;
        } elsif ($item->{mimeType} =~ m{^audio/}i) {
            push @audio_files, $item;
        }
    }

    for my $file (@audio_files) {
        return if $self->_stop;
        $self->_store_track($file, $folder, $path);
    }

    for my $subfolder (@subfolders) {
        return if $self->_stop;
        $self->_scan_dir(
            folder_drive_id => $subfolder->{id},
            folder_name     => $subfolder->{name},
            parent_drive_id => $drive_id,
            path            => "$path/$subfolder->{name}",
            scan_folder_id  => $scan_folder_id,
        );
    }
}

sub _store_track {
    my ($self, $file, $folder, $folder_path) = @_;

    $self->_seen_track_ids->{$file->{id}} = 1;

    my ($title, $artist, $album, $track_num, $year) = _parse_filename($file->{name});

    # Infer artist/album from folder path depth: Root/Artist/Album/track
    my @parts = split m{/}, $folder_path;
    if (!$artist && @parts >= 3) {
        $artist = $parts[-2];
        $album  = $parts[-1];
    } elsif (!$album && @parts >= 2) {
        $album = $parts[-1];
    }

    # "YYYY-AlbumName" folder convention: strip the year prefix and use the
    # captured year as the track's year when one wasn't parsed from the file.
    if ($album && $album =~ s{
        ^
        ( (?: 19 | 20) \d{2} )   # 4-digit year, 19xx or 20xx
        -
    }{}x) {
        $year //= $1;
    }

    my $duration_ms;
    if (my $meta = $file->{videoMediaMetadata}) {
        $duration_ms = $meta->{durationMillis};
    }

    my %track = (
        drive_id      => $file->{id},
        title         => $title,
        artist        => $artist,
        album         => $album,
        track_number  => $track_num,
        year          => $year,
        duration_ms   => $duration_ms,
        size          => $file->{size},
        mime_type     => $file->{mimeType},
        modified_time => $file->{modifiedTime},
        folder_id     => $folder->{id},
        folder_path   => $folder_path,
    );

    $self->db->upsert_track(%track);
    $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+(.+)$/) {



( run in 1.487 second using v1.01-cache-2.11-cpan-71847e10f99 )