App-MHFS

 view release on metacpan or  search on metacpan

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

            return;
        }

        if(! $TRACKDURATION{$tosend}) {
            say __PACKAGE__.": failed to get track duration";
            $request->Send503();
            return;
        }

        say "no proc, duration cached";
        my $pv = MHFS::XS::new($tosend);
        $request->{'outheaders'}{'X-MHFS-NUMSEGMENTS'} = ceil($TRACKDURATION{$tosend} / $SEGMENT_DURATION);
        $request->{'outheaders'}{'X-MHFS-TRACKDURATION'} = $TRACKDURATION{$tosend};
        $request->{'outheaders'}{'X-MHFS-MAXSEGDURATION'} = $SEGMENT_DURATION;
        my $samples_per_seg = $TRACKINFO{$tosend}{'SAMPLERATE'} * $SEGMENT_DURATION;
        my $spos = $samples_per_seg * ($request->{'qs'}{'part'} - 1);
        my $samples_left = $TRACKINFO{$tosend}{'TOTALSAMPLES'} - $spos;
        my $res = MHFS::XS::get_flac($pv, $spos, $samples_per_seg < $samples_left ? $samples_per_seg : $samples_left);
        $request->SendBytes('audio/flac', $res);
    }
    elsif(defined $request->{'qs'}{'fmt'} && ($request->{'qs'}{'fmt'}  eq 'wav')) {
        if(! HAS_MHFS_XS) {
            say __PACKAGE__.": route not available without XS";
            $request->Send503();
            return;
        }

        my $pv = MHFS::XS::new($tosend);
        my $outbuf = '';
        my $wavsize = (44+ $TRACKINFO{$tosend}{'TOTALSAMPLES'} * ($TRACKINFO{$tosend}{'BITSPERSAMPLE'}/8) * $TRACKINFO{$tosend}{'NUMCHANNELS'});
        my $startbyte = $request->{'header'}{'_RangeStart'} || 0;
        my $endbyte = $request->{'header'}{'_RangeEnd'} // $wavsize-1;
        say "start byte" . $startbyte;
        say "end byte " . $endbyte;
        say "MHFS::XS::wavvfs_read_range " . $startbyte . ' ' . $endbyte;
        my $maxsendsize;
        $maxsendsize = 1048576/2;
        say "maxsendsize $maxsendsize " . ' bytespersample ' . ($TRACKINFO{$tosend}{'BITSPERSAMPLE'}/8) . ' numchannels ' . $TRACKINFO{$tosend}{'NUMCHANNELS'};
        $request->SendCallback(sub{
            my ($fileitem) = @_;
            my $actual_endbyte = $startbyte + $maxsendsize - 1;
            if($actual_endbyte >= $endbyte) {
                $actual_endbyte = $endbyte;
                $fileitem->{'cb'} = undef;
                say "SendCallback last send";
            }
            my $actual_startbyte = $startbyte;
            $startbyte = $actual_endbyte+1;
            say "SendCallback wavvfs_read_range " . $actual_startbyte . ' ' . $actual_endbyte;
            return MHFS::XS::wavvfs_read_range($pv, $actual_startbyte, $actual_endbyte);
        }, {
            'mime' => 'audio/wav',
            'size' => $wavsize,
        });

    }
    else {
        if($request->{'qs'}{'action'} && ($request->{'qs'}{'action'} eq 'dl')) {
            $request->{'responseopt'}{'cd_file'} = 'attachment';
        }
        # Send the total pcm frame count for mp3
        elsif(lc(substr($tosend, -4)) eq '.mp3') {
            if(HAS_MHFS_XS) {
                if(! $TRACKINFO{$tosend}) {
                    $TRACKINFO{$tosend} = { 'TOTALSAMPLES' => MHFS::XS::get_totalPCMFrameCount($tosend) };
                    say "mp3 totalPCMFrames: " . $TRACKINFO{$tosend}{'TOTALSAMPLES'};
                }
                $request->{'outheaders'}{'X-MHFS-totalPCMFrameCount'} = $TRACKINFO{$tosend}{'TOTALSAMPLES'};
            }
        }
        $request->SendLocalFile($tosend);
    }
}

sub parseStreamInfo {
    # https://metacpan.org/source/DANIEL/Audio-FLAC-Header-2.4/Header.pm
    my ($buf) = @_;
    my $metaBinString = unpack('B144', $buf);

    my $x32 = 0 x 32;
    my $info = {};
    $info->{'MINIMUMBLOCKSIZE'} = unpack('N', pack('B32', substr($x32 . substr($metaBinString, 0, 16), -32)));
    $info->{'MAXIMUMBLOCKSIZE'} = unpack('N', pack('B32', substr($x32 . substr($metaBinString, 16, 16), -32)));
    $info->{'MINIMUMFRAMESIZE'} = unpack('N', pack('B32', substr($x32 . substr($metaBinString, 32, 24), -32)));
    $info->{'MAXIMUMFRAMESIZE'} = unpack('N', pack('B32', substr($x32 . substr($metaBinString, 56, 24), -32)));

    $info->{'SAMPLERATE'}       = unpack('N', pack('B32', substr($x32 . substr($metaBinString, 80, 20), -32)));
    $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;



( run in 0.811 second using v1.01-cache-2.11-cpan-e1769b4cff6 )