/usr/share/gmusicbrowser/flacheader.pm is in gmusicbrowser 1.1.9-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 | # Copyright (C) 2005-2009 Quentin Sculo <squentin@free.fr>
#
# This file is part of Gmusicbrowser.
# Gmusicbrowser is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3, as
# published by the Free Software Foundation
#http://flac.sourceforge.net/format.html
package Tag::Flac;
use strict;
use warnings;
use Encode qw(decode encode);
use MIME::Base64;
our @ISA=('Tag::OGG');
use constant
{ STREAMINFO => 0,
PADDING => 1,
APPLICATION => 2,
SEEKTABLE => 3,
VORBIS_COMMENT=> 4,
CUESHEET => 5,
PICTURE => 6,
};
sub new
{ my ($class,$file)=@_;
my $self=bless {}, $class;
local $_;
# check that the file exists
unless (-e $file)
{ warn "File '$file' does not exist.\n";
return undef;
}
$self->{filename} = $file;
$self->{startaudio}=0; #start of flac stream (in case an id3v2 tag is at the beginning of the file)
my $fh=$self->_open or return undef;
my $buffer;
{ last unless read($fh,$buffer,4)==4;
if ($buffer=~m/^ID3/)
{ my $tag=Tag::ID3v2->new_from_file($self);
$self->{startaudio}+=$tag->{size};
redo;
}
}
unless ($buffer && $buffer eq 'fLaC')
{ warn "flac: Not a flac header\n"; $self->_close; return undef; }
my $last;
my @pictures;
while ( !$last && read($fh,$buffer,4)==4 )
{ $buffer=unpack 'N',$buffer;
my $size=$buffer & 0xffffff;
my $pos=tell $fh;
my $type=($buffer >> 24) & 0x7f;
$last=$buffer >>31;
unless (read($fh,$buffer,$size)==$size)
{ warn "flac: Premature end of file\n"; $self->_close; return undef; }
if ($type==STREAMINFO) {$self->{info}=_ReadInfo(\$buffer);}
elsif ($type==VORBIS_COMMENT) {$self->{comments}=_UnpackComments($self,\$buffer); $self->{comment_offset}=$pos-4}
elsif ($type==PICTURE) {my $pic=_ReadPicture(\$buffer); push @pictures,$pic if $pic;}
}
my $audiosize=(stat $fh)[7]-tell($fh);
$self->_close;
unless ($self->{info})
{ warn "error, can't read file or not a valid flac file\n";
return undef;
}
$self->{info}{bitrate}= $self->{info}{seconds} ? $audiosize*8/$self->{info}{seconds} : 0;
unless ($self->{comments})
{ $self->{vorbis_string}='gmusicbrowser'; #FIXME
$self->{CommentsOrder}=[];
$self->{comments}={};
}
for my $pic (@pictures)
{ push @{ $self->{comments}{metadata_block_picture} }, $pic;
push @{ $self->{CommentsOrder} }, 'metadata_block_picture',
}
return $self;
}
sub write_file
{ my $self=shift;
local $_;
my ($INfh,$OUTfh);
my $pictures='';
if (my $list=$self->{comments}{metadata_block_picture})
{ for my $pic (grep defined, @$list)
{ my $packet= _PackPicture($pic);
my $head=pack 'N', (PICTURE<<24)+length $$packet;
$pictures.= $head.$$packet;
}
@$list=(); #remove the pictures from vorbis comments
}
my $newcom_packref=_PackComments($self);
my $fh=$self->_open or return undef;
my $buffer; my $last; my $towrite='fLaC'; my $padding=0;
seek $fh,$self->{startaudio} ,0; #skip extra tags
return undef unless (read($fh,$buffer,4)==4 && $buffer eq 'fLaC');
while ( !$last && read($fh,$buffer,4)==4 )
{ $buffer=unpack 'N',$buffer;
my $size=$buffer & 0xffffff;
my $type=($buffer >> 24) & 0x7f;
$last=$buffer >>31;
if ($type!=VORBIS_COMMENT && $type!=PADDING && $type!=PICTURE)
{ $buffer&=0x7fffffff; #set Last-metadata-block flag to 0
$towrite.=pack 'N',$buffer;
unless (read($fh,$towrite,$size,length($towrite))==$size)
{ warn "flac: Premature end of file\n"; return undef; }
}
else {$padding+=$size+4; seek $fh,$size,1; }
}
$padding-= 4 + length($$newcom_packref) + length $pictures;
my $header=VORBIS_COMMENT;
my $inplace=($padding==0 || ($padding>3 && $padding<8192) );
if ($padding==0) {$header+=0x80;$padding='';}
else
{ $padding=$inplace? $padding-4 : 256;
$padding=pack "Nx$padding",((0x80+PADDING)<<24)+$padding;
}
$header=pack 'N',($header<<24)+length $$newcom_packref;
if ($inplace)
{ $self->_close;
$fh=$self->_openw or return undef;
seek $fh,$self->{startaudio} ,0;
print $fh $towrite.$pictures.$header.$$newcom_packref.$padding or warn $!;
$self->_close;
}
else
{ my $tmpfh=$self->_openw(1) or return undef;
if ($self->{startaudio})
{ seek $fh,0,0;
read($fh,$buffer,$self->{startaudio});
print $tmpfh $buffer or warn $!;
}
print $tmpfh $towrite.$pictures.$header.$$newcom_packref.$padding or warn $!;
while (read($fh,$buffer,1048576))
{ print $tmpfh $buffer or warn $!; }
$self->_close;
close $tmpfh;
warn "replacing old file with new file.\n";
unlink $self->{filename} && rename $self->{filename}.'.TEMP',$self->{filename};
}
%$self=(); #destroy the object to make sure it is not reused as many of its data are now invalid
return 1;
}
sub _close
{ my $self=shift;
close delete($self->{fileHandle});
}
sub _ReadInfo
{ my $packref=$_[0];
my @v=unpack 'nn CnCn nCCN',$$packref;
#A16 B16 C8 C16 D8 D16 E16 EEEEFFFG GGGGHHHH H32 I128
#A <16> The minimum block size (in samples) used in the stream
#B <16> The maximum block size (in samples) used in the stream. (Minimum blocksize == maximum blocksize) implies a fixed-blocksize stream.
#C <24> The minimum frame size (in bytes) used in the stream. May be 0 to imply the value is not known.
#D <24> The maximum frame size (in bytes) used in the stream. May be 0 to imply the value is not known.
#E <20> Sample rate in Hz. Though 20 bits are available, the maximum sample rate is limited by the structure of frame headers to 1048570Hz. Also, a value of 0 is invalid.
#F <3> (number of channels)-1. FLAC supports from 1 to 8 channels
#G <5> (bits per sample)-1. FLAC supports from 4 to 32 bits per sample. Currently the reference encoder and decoders only support up to 24 bits per sample
#H <36> Total samples in stream. 'Samples' means inter-channel sample, i.e. one second of 44.1Khz audio will have 44100 samples regardless of the number of channels. A value of zero here means the number of total samples is unknown.
#I <128> MD5 signature of the unencoded audio data
my %info;
$info{min_block_size}=$v[0];
$info{max_block_size}=$v[1];
$info{min_frame_size}=($v[2]<<16)+$v[3];
$info{max_frame_size}=($v[4]<<16)+$v[5];
$info{rate}=($v[6]<<4)+($v[7]>>4);
$info{channels}=1+( ($v[7] & 0b1110)>>1 );
$info{bit_per_sample}=1+( ($v[7] & 0b1)<<5 )+( $v[8] >>4 );
$info{seconds}=( $v[9]+($v[8] & 0b1111)*2**32 )/$info{rate};
return \%info;
}
sub _ReadPicture
{ my $packref=$_[0];
my $ret;
eval
{ my ($type,$mime,$desc,undef,undef,undef,undef,$data)
=unpack 'N N/a N/a NNNN N/a',$$packref;
$ret=[$mime,$type,$desc,$data];
};
if ($@) { warn "invalid picture block - skipped\n"; }
return $ret;
}
sub _PackPicture
{ my $pic=shift;
if (!ref $pic) { my $packet=decode_base64($pic); return \$packet; }
my ($mime,$type,$desc,$data)=@$pic;
my $packet= pack 'N N/a N/a NNNN N/a', ($type||0),$mime,$desc,0,0,0,0, $data;
return \$packet;
}
sub _UnpackComments
{ my ($self,$packref)=@_;
my ($vstring,@comlist)= eval { unpack 'V/a V/(V/a)',$$packref; };
if ($@) { warn "Comments corrupted\n"; return undef; }
$self->{vorbis_string}=$vstring;
my %comments;
for my $kv (@comlist)
{ unless ($kv=~m/^([^=]+)=(.*)$/s) { warn "comment invalid - skipped\n"; next; }
my $key=$1;
my $val=decode('utf-8', $2);
#warn "$key = $val\n";
push @{ $comments{lc$key} },$val;
push @{$self->{CommentsOrder}}, $key;
}
return \%comments;
}
sub _PackComments
{ my $self=$_[0];
my @comments;
my %count;
for my $key ( @{$self->{CommentsOrder}} )
{ my $nb=$count{lc$key}++ || 0;
my $val=$self->{comments}{lc$key}[$nb];
next unless defined $val;
$key=encode('ascii',$key);
$key=~tr/\x20-\x7D/?/c; $key=~tr/=/?/; #replace characters that are not allowed by '?'
push @comments,$key.'='.encode('utf8',$val);
}
my $packet=pack 'V/a* V (V/a*)*',$self->{vorbis_string},scalar @comments, @comments;
#$packet.="\x01"; #framing_flag #gstreamer doesn't like it and not needed anyway
return \$packet;
}
1;
|