#!/usr/bin/perl -w use strict; # extract one or more tracks from a FLAC file using its embedded cuesheet # save those tracks as wav or mp3 files # can also run them through a sox filter # TODO: separate sox filter for each track! use Getopt::Long; GetOptions( 'D=s' => \my %TRACKS, 't=s' => \my $TYPE, 'x=s' => \my $SOX_FILTER, ); my $FLAC_FILE = shift or die "Need a flac file to decode"; # default to mp3 $TYPE ||= 'mp3'; # for getting track metadata from MusicBrainz my %info; if ($TYPE eq 'mp3') { require Audio::FLAC::Header; require LWP; require XML::XPath; require XML::XPath::XMLParser; my $flac = Audio::FLAC::Header->new($FLAC_FILE) or warn "Can't read FLAC header from $FLAC_FILE\n"; my $discid = $flac->tags('MBZ_DISCID') or warn "No MBZ_DISCID tag in $FLAC_FILE\n" if $flac; #TODO: calculate TOC and DISCID from cuesheet if there is no MBZ_DISCID tag present #TODO: use the functions in mbz instead of repeating them here) %info = get_musicbrainz_info($discid); } while (my ($tracknum, $title) = each %TRACKS) { if ($tracknum !~ /^\d+$/) { warn "Don't know what to do with track number '$tracknum'"; next; } my $start = $tracknum . '.1'; my $end = $tracknum + 1 . '.1'; my $cmd = qq{flac -d --cue $start-$end $FLAC_FILE -o - }; if ($SOX_FILTER) { $cmd .= qq{| sox -t wav - -t wav - $SOX_FILTER }; } $title = quotemeta($title); if ($TYPE eq 'mp3') { # bitrate of 192 $cmd .= qq{| lame -b 192}; # if there is track info, add it as ID3 tags if (%info) { my $track = $info{TRACKS}[$tracknum]; $cmd .= sprintf q{ --tt %s --ta %s --tl %s --tn %d}, quote($$track{TITLE}), quote($$track{ARTIST}), quote($info{ALBUM}), $tracknum; } $cmd .= qq{ - $title.mp3}; } elsif ($TYPE eq 'wav') { $cmd .= qq{> $title.wav}; } else { die "Unknown type: $TYPE\n"; } #die $cmd; system $cmd; print "\n" if $SOX_FILTER; } sub quote { my ($string) = @_; $string =~ s/"/\\"/g; return qq{"$string"}; } # make the output terminal handle UTF-8 characters #binmode STDOUT, ':utf8'; #my $info = get_musicbrainz_info($discid); #for my $key (sort keys %{ $info }) { # print "$key=$$info{$key}\n"; #} sub lookup_release { my ($discid) = @_; my $ua = LWP::UserAgent->new; my $uri = URI->new('http://musicbrainz.org/ws/1/release/'); $uri->query_form(type => 'xml', discid => $discid); my $res = $ua->get($uri); return $res->decoded_content; } sub get_musicbrainz_info { my ($discid) = @_; my %info; $info{MBZ_DISCID} = $discid; my $xpath = XML::XPath->new(); $xpath->set_xml(lookup_release($discid)); # TODO: check for more than 1 release? $info{MB_RELEASE_ID} = $xpath->findvalue('//release/@id'); $info{ALBUM} = $xpath->findvalue('//release/title'); $info{ARTIST} = $xpath->findvalue('//release/artist/name'); $info{TRACKS} = []; # TODO: get release date my $tracknum = 1; for my $track_node ($xpath->findnodes('//track-list/track')) { $info{TRACKS}[$tracknum]{MB_TRACKID} = $xpath->findvalue('@id', $track_node); $info{TRACKS}[$tracknum]{TITLE} = $xpath->findvalue('title', $track_node); $info{TRACKS}[$tracknum]{ARTIST} = $xpath->findvalue('artist/name', $track_node) || $info{ARTIST}; $tracknum++; } return %info; }