App-MHFS
view release on metacpan or search on metacpan
lib/MHFS/Plugin/MusicLibrary.pm view on Meta::CPAN
SendLibrary($self, $request);
});
return 1;
}
# deduce the format if not provided
my $fmt = $request->{'qs'}{'fmt'};
if(! $fmt) {
$fmt = 'worklet';
my $fallback = 'musicinc';
if($request->{'header'}{'User-Agent'} =~ /Chrome\/([^\.]+)/) {
my $ver = $1;
# SharedArrayBuffer support with spectre/meltdown fixes was added in 68
# AudioWorklet on linux had awful glitching until somewhere in 92 https://bugs.chromium.org/p/chromium/issues/detail?id=825823
if($ver < 93) {
if(($ver < 68) || ($request->{'header'}{'User-Agent'} =~ /Linux/)) {
$fmt = $fallback;
}
}
}
elsif($request->{'header'}{'User-Agent'} =~ /Firefox\/([^\.]+)/) {
my $ver = $1;
# SharedArrayBuffer support with spectre/meltdown fixes was added in 79
if($ver < 79) {
$fmt = $fallback;
}
}
else {
# Hope for the best, assume worklet works
}
# leave this here for now to not break the segment based players
if($request->{'qs'}{'segments'}) {
$fmt = $fallback;
}
}
# route
my $qs = defined($request->{'qs'}{'ptrack'}) ? {'ptrack' => $request->{'qs'}{'ptrack'}} : undef;
if($fmt eq 'worklet') {
return $request->SendRedirect(307, 'static/music_worklet_inprogress/', $qs);
}
elsif($fmt eq 'musicdbjson') {
return $request->SendBytes('application/json', $self->{'musicdbjson'});
}
elsif($fmt eq 'musicdbhtml') {
return $request->SendBytes("text/html; charset=utf-8", $self->{'musicdbhtml'});
}
elsif($fmt eq 'gapless') {
$qs->{fmt} = 'musicinc';
return $request->SendRedirect(301, "music", $qs);
}
elsif($fmt eq 'musicinc') {
return $request->SendRedirect(307, 'static/music_inc/', $qs);
}
elsif($fmt eq 'legacy') {
say __PACKAGE__.": legacy";
return $request->SendBytes("text/html; charset=utf-8", $self->{'html'});
}
else {
return $request->Send404;
}
}
my $SEGMENT_DURATION = 5;
my %TRACKDURATION;
my %TRACKINFO;
sub SendTrack {
my ($request, $tosend) = @_;
if(defined $request->{'qs'}{'part'}) {
if(! HAS_MHFS_XS) {
say __PACKAGE__.": route not available without XS";
$request->Send503();
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;
lib/MHFS/Plugin/MusicLibrary.pm view on Meta::CPAN
my ($self, $msource, $name) = @_;
my @namearr = split('/', $name);
my $finalstring = $self->{'settings'}{'SOURCES'}{$msource->[0]}{'folder'};
my $lib = $msource->[1];
FindInLibrary_Outer: foreach my $component (@namearr) {
foreach my $libcomponent (@{$lib->[2]}) {
if($libcomponent->[3] eq $component) {
$finalstring .= "/".$libcomponent->[0];
$lib = $libcomponent;
next FindInLibrary_Outer;
}
}
return undef;
}
return {
'node' => $lib,
'path' => $finalstring
};
}
# Define source types here
my %sendFiles = (
'local' => sub {
my ($request, $file, $node, $source, $nameloc) = @_;
return undef if(! -e $file);
if( ! -d $file) {
$request->{'localtrack'} = { 'nameloc' => $nameloc, 'basename' => $node->[0]};
SendLocalTrack($request, $file);
}
else {
$request->SendAsTar($file);
}
return 1;
},
'mhfs' => sub {
my ($request, $file, $node, $source) = @_;
return $request->Proxy($source, $node);
},
'ssh' => sub {
my ($request, $file, $node, $source) = @_;
return $request->SendFromSSH($source, $file, $node);
},
);
sub SendFromLibrary {
my ($self, $request) = @_;
my $utf8name = decode('UTF-8', $request->{'qs'}{'name'});
foreach my $msource (@{$self->{'sources'}}) {
my $node = $self->FindInLibrary($msource, $utf8name);
next if ! $node;
my $nameloc;
if($utf8name =~ /(.+\/).+$/) {
$nameloc = $1;
}
my $source = $self->{'settings'}{'SOURCES'}{$msource->[0]};
if($sendFiles{$source->{'type'}}->($request, $node->{'path'}, $node->{'node'}, $source, $nameloc)) {
return 1;
}
}
say "SendFromLibrary: did not find in library, 404ing";
say "name: " . $request->{'qs'}{'name'};
$request->Send404;
}
sub SendResources {
my ($self, $request) = @_;
if(! HAS_MHFS_XS) {
say __PACKAGE__.": route not available without XS";
$request->Send503();
return;
}
my $utf8name = decode('UTF-8', $request->{'qs'}{'name'});
foreach my $msource (@{$self->{'sources'}}) {
my $node = $self->FindInLibrary($msource, $utf8name);
next if ! $node;
my $comments = MHFS::XS::get_vorbis_comments($node->{'path'});
my $commenthash = {};
foreach my $comment (@{$comments}) {
$comment = decode('UTF-8', $comment);
my ($key, $value) = split('=', $comment);
$commenthash->{$key} = $value;
}
$request->SendAsJSON($commenthash);
return 1;
}
say "SendFromLibrary: did not find in library, 404ing";
say "name: " . $request->{'qs'}{'name'};
$request->Send404;
}
sub SendArt {
my ($self, $request) = @_;
my $utf8name = decode('UTF-8', $request->{'qs'}{'name'});
foreach my $msource (@{$self->{'sources'}}) {
my $node = $self->FindInLibrary($msource, $utf8name);
next if ! $node;
my $dname = $node->{'path'};
my $dh;
if(! opendir($dh, $dname)) {
$dname = dirname($node->{'path'});
if(! opendir($dh, $dname)) {
$request->Send404;
return 1;
}
}
# scan dir for art
my @files;
while(my $fname = readdir($dh)) {
my $last = lc(substr($fname, -4));
push @files, $fname if(($last eq '.png') || ($last eq '.jpg') || ($last eq 'jpeg'));
}
closedir($dh);
if( ! @files) {
$request->Send404;
return 1;
}
my $tosend = "$dname/" . $files[0];
foreach my $file (@files) {
foreach my $expname ('cover', 'front', 'album') {
if(substr($file, 0, length($expname)) eq $expname) {
$tosend = "$dname/$file";
last;
}
}
}
say "tosend $tosend";
$request->SendLocalFile($tosend);
return 1;
}
}
sub UpdateLibrariesAsync {
my ($self, $evp, $onUpdateEnd) = @_;
MHFS::Process->new_output_child($evp, sub {
# done in child
my ($datachannel) = @_;
# save references to before
my @potentialupdates = ('html', 'musicdbhtml', 'musicdbjson');
my %before;
foreach my $pupdate (@potentialupdates) {
$before{$pupdate} = $self->{$pupdate};
}
# build the new libraries
$self->BuildLibraries();
# determine what needs to be updated
my @updates = (['sources', $self->{'sources'}]);
foreach my $pupdate(@potentialupdates) {
if($before{$pupdate} ne $self->{$pupdate}) {
push @updates, [$pupdate, $self->{$pupdate}];
}
}
# serialize and output
my $pipedata = freeze(\@updates);
print $datachannel $pipedata;
exit 0;
}, sub {
my ($out, $err) = @_;
say "BEGIN_FROM_CHILD---------";
print $err;
say "END_FROM_CHILD-----------";
my $unthawed;
{
local $@;
unless (eval {
$unthawed = thaw($out);
return 1;
}) {
warn("thaw threw exception");
}
}
( run in 1.496 second using v1.01-cache-2.11-cpan-39bf76dae61 )