App-MHFS

 view release on metacpan or  search on metacpan

lib/MHFS/Plugin/MusicLibrary.pm  view on Meta::CPAN

    $info->{'NUMCHANNELS'}      = unpack('N', pack('B32', substr($x32 . substr($metaBinString, 100, 3), -32))) + 1;
    $info->{'BITSPERSAMPLE'}    = unpack('N', pack('B32', substr($x32 . substr($metaBinString, 103, 5), -32))) + 1;

    # Calculate total samples in two parts
    my $highBits = unpack('N', pack('B32', substr($x32 . substr($metaBinString, 108, 4), -32)));

    $info->{'TOTALSAMPLES'} = $highBits * 2 ** 32 +
            unpack('N', pack('B32', substr($x32 . substr($metaBinString, 112, 32), -32)));

    # Return the MD5 as a 32-character hexadecimal string
    $info->{'MD5CHECKSUM'} = unpack('H32',substr($buf, 18, 16));
    return $info;
}

sub GetTrackInfo {
    my ($file) = @_;
    open(my $fh, '<', $file) or die "open failed";
    my $buf = '';
    seek($fh, 8, 0) or die "seek failed";
    (read($fh, $buf, 34) == 34) or die "short read";
    my $info = parseStreamInfo($buf);
    $info->{'duration'} = $info->{'TOTALSAMPLES'}/$info->{'SAMPLERATE'};
    print Dumper($info);
    return $info;
}

sub SendLocalTrack {
    my ($request, $file) = @_;

    # fast path, just send the file
    my $justsendfile = (!defined($request->{'qs'}{'fmt'})) && (!defined($request->{'qs'}{'max_sample_rate'})) && (!defined($request->{'qs'}{'bitdepth'})) && (!defined($request->{'qs'}{'part'}));
    if($justsendfile) {
        SendTrack($request, $file);
        return;
    }

    my $evp = $request->{'client'}{'server'}{'evp'};
    my $tmpfileloc = $request->{'client'}{'server'}{'settings'}{'MUSIC_TMPDIR'} . '/';
    my $nameloc = $request->{'localtrack'}{'nameloc'};
    $tmpfileloc .= $nameloc if($nameloc);
    my $filebase = $request->{'localtrack'}{'basename'};

    # convert to lossy flac if necessary
    my $is_flac = lc(substr($file, -5)) eq '.flac';
    if(!$is_flac) {
        $filebase =~ s/\.[^.]+$/.lossy.flac/;
        $request->{'localtrack'}{'basename'} = $filebase;
        my $tlossy = $tmpfileloc . $filebase;
        if(-e $tlossy ) {
            $is_flac = 1;
            $file = $tlossy;

            if(defined LOCK_GET_LOCKDATA($tlossy)) {
                    # unlikely
                say "SendLocalTrack: lossy flac exists and is locked 503";
                $request->Send503;
                return;
            }
        }
        else {
            make_path($tmpfileloc, {chmod => 0755});
            my @cmd = ('ffmpeg', '-i', $file, '-c:a', 'flac', '-sample_fmt', 's16', $tlossy);
            my $buf;
            if(LOCK_WRITE($tlossy)) {
                $request->{'process'} = MHFS::Process->new(\@cmd, $evp, {
                'SIGCHLD' => sub {
                    UNLOCK_WRITE($tlossy);
                    SendLocalTrack($request,$tlossy);
                },
                'STDERR' => sub {
                    my ($terr) = @_;
                    read($terr, $buf, 4096);
                }});
            }
            else {
                # unlikely
                say "SendLocalTrack: lossy flac is locked 503";
                $request->Send503;
            }

            return;
        }
    }

    # everything should be flac now, grab the track info
    if(!defined($TRACKINFO{$file}))
    {
        $TRACKINFO{$file} = GetTrackInfo($file);
        $TRACKDURATION{$file} = $TRACKINFO{$file}{'duration'};
    }

    my $max_sample_rate = $request->{'qs'}{'max_sample_rate'} // 192000;
    my $bitdepth = $request->{'qs'}{'bitdepth'} // ($max_sample_rate > 48000 ? 24 : 16);

    # check to see if the raw file fullfills the requirements
    my $samplerate = $TRACKINFO{$file}{'SAMPLERATE'};
    my $inbitdepth = $TRACKINFO{$file}{'BITSPERSAMPLE'};
    say "input: samplerate $samplerate inbitdepth $inbitdepth";
    say "maxsamplerate $max_sample_rate bitdepth $bitdepth";
    if(($samplerate <= $max_sample_rate) && ($inbitdepth <= $bitdepth)) {
        say "samplerate is <= max_sample_rate, not resampling";
        SendTrack($request, $file);
        return;
    }

    # determine the acceptable samplerate, bitdepth combinations to send
    my %rates = (
        '48000' => [192000, 96000, 48000],
        '44100' => [176400, 88200, 44100]
    );
    my @acceptable_settings = ( [24, 192000], [24, 96000], [24, 48000], [24, 176400],  [24, 88200], [16, 48000], [16, 44100]);
    my @desired = ([$bitdepth, $max_sample_rate]);
    foreach my $setting (@acceptable_settings) {
        if(($setting->[0] <= $bitdepth) && ($setting->[1] <= $max_sample_rate)) {
            push @desired, $setting;
        }
    }

    # if we already transcoded/resampled, don't waste time doing it again
    foreach my $setting (@desired) {
        my $tmpfile = $tmpfileloc . $setting->[0] . '_' . $setting->[1] . '_' . $filebase;
        if(-e $tmpfile) {
            say "No need to resample $tmpfile exists";
            SendTrack($request, $tmpfile);
            return;
        }
    }
    make_path($tmpfileloc, {chmod => 0755});

    # resampling
    my $desiredrate;
    RATE_FACTOR: foreach my $key (keys %rates) {
        if(($samplerate % $key) == 0) {
            foreach my $rate (@{$rates{$key}}) {
                if(($rate <= $samplerate) && ($rate <= $max_sample_rate)) {
                    $desiredrate = $rate;
                    last RATE_FACTOR;
                }
            }
        }
    }
    $desiredrate //= $max_sample_rate;
    say "desired rate: $desiredrate";
    # build the command
    my $outfile = $tmpfileloc . $bitdepth . '_' . $desiredrate . '_' . $filebase;
    my @cmd = ('sox', $file, '-G', '-b', $bitdepth, $outfile, 'rate', '-v', '-L', $desiredrate, 'dither');
    say "cmd: " . join(' ', @cmd);

    if(LOCK_WRITE($outfile)) {
        $request->{'process'} = MHFS::Process->new(\@cmd, $evp, {
        'SIGCHLD' => sub {
            UNLOCK_WRITE($outfile);
            # BUG? files isn't necessarily flushed to disk on SIGCHLD. filesize can be wrong
            SendTrack($request, $outfile);
        },
        'STDERR' => sub {
            my ($terr) = @_;
            my $buf;
            read($terr, $buf, 4096);
        }});
    }
    else {
        # unlikely
        say "SendLocalTrack: sox is locked 503";
        $request->Send503;
    }
    return;
}


sub BuildLibraries {
    my ($self) = @_;
    my @wholeLibrary;

    $self->{'sources'} = [];

    foreach my $sid (@{$self->{'settings'}{'MEDIASOURCES'}{'music'}}) {
        my $source = $self->{'settings'}{'SOURCES'}{$sid};
        my $lib;
        if($source->{'type'} eq 'local') {
            say __PACKAGE__.": building music " . clock_gettime(CLOCK_MONOTONIC);
            $lib = BuildLibrary($source->{'folder'});
            say __PACKAGE__.": done building music " . clock_gettime(CLOCK_MONOTONIC);
        }
        elsif($source->{'type'} eq 'ssh') {
        }
        elsif($source->{'type'} eq 'mhfs') {
        }



( run in 0.600 second using v1.01-cache-2.11-cpan-39bf76dae61 )