source: flacrip/trunk/mbz2id3 @ 26

Last change on this file since 26 was 17, checked in by peter, 10 years ago

changed MBZ_DISCID to MUSICBRAINZ_DISCID, to match the standard MusicBrainz-Vorbis tag mappings

  • Property svn:executable set to *
File size: 6.6 KB
Line 
1#!/usr/bin/perl -w
2use strict;
3
4package Tracks;
5
6use WebService::MusicBrainz;
7use Text::Unidecode;
8use YAML;
9use String::Format;
10use Text::Sprintf::Named;
11
12my %CODES = (
13    a => 'ARTIST',
14    t => 'TITLE',
15    b => 'ALBUM',
16    n => 'TRACKNUM',
17    y => 'YEAR',
18);
19
20sub new {
21    my ($invocant, $discid) = @_;
22    die "Need a DiscID" unless $discid;
23
24    my $class = ref $invocant || $invocant;
25    my $self = {
26        discid => $discid,
27        tracks_info => [],
28        album_info => {},
29    };
30
31    return bless $self, $class;
32}
33
34sub get_tracks_info { $_[0]->{tracks_info} }
35sub get_album_info { $_[0]->{album_info} }
36sub get_track_count { scalar @{ $_[0]->{tracks_info} } }
37
38sub query_musicbrainz {
39    my $self = shift;
40
41    my $discid = $self->{discid};
42
43    # check for cached info
44    my $filename = "$discid.yml";
45    if (-r $filename) {
46        my $info = YAML::LoadFile($filename);
47        @{ $self }{qw{tracks_info album_info}} = @{ $info }{qw{tracks_info album_info}};
48        return $self;
49    }
50
51    my $ws_artists = WebService::MusicBrainz->new_artist;
52    my $ws_releases = WebService::MusicBrainz->new_release;
53    my $ws_tracks = WebService::MusicBrainz->new_track;
54
55    # search on the discid
56    my $response = $ws_releases->search({ DISCID => $discid });
57
58    # save this object, since WS::MBZ deletes it when you fetch it
59    # TODO: bug report to WS::MBZ?
60    my $release = $response->release;
61
62    # return undef if there is no matching release for this DiscID
63    #return unless defined $release;
64    die "No matches for $discid" unless defined $release;
65
66    # search again, using the MBID of the first release found
67    # TODO: deal with multiple releases found?
68    # include tracks and artist info
69    $response = $ws_releases->search({
70        MBID => $release->id,
71        INC => 'discs tracks artist release-events counts',
72    });
73
74    # get the fully filled out Release object (that represents the disc)
75    $release = $response->release;
76
77    # this is ID3v2:TDRL = Release Date
78    # (for now we just take the first date)
79    my $release_date = eval { @{ $release->release_event_list->events }[0]->date };
80    my $release_events = $release->release_event_list->events;
81    #warn map { $_->date } @{ $release_events };
82    $release_date = '' if $@;
83    my ($release_year) = ($release_date =~ /(\d{4})/);
84
85    $self->{album_info} = {
86        ARTIST => $release->artist->name,
87        ALBUM  => $release->title,
88        YEAR   => $release_year,
89    };       
90
91    # get full info on each of the tracks
92    my @tracks;
93    my $track_num = 1;
94    for my $track_id (map { $_->id } @{ $release->track_list->tracks }) {
95        my $response = $ws_tracks->search({
96            MBID => $track_id,
97            INC => 'artist track-rels',
98        });
99        my $track = $response->track;
100
101        # get the ID3v1 level stuff
102        # (will worry about the fancy v2 later)
103        push @tracks, {
104            TRACKNUM => $track_num,
105            TITLE    => $track->title,
106            ALBUM    => $release->title,
107            ARTIST   => $track->artist->name,
108            YEAR     => $release_year,
109        };
110
111        $track_num++;
112    }
113
114    $self->{tracks_info} = \@tracks;
115
116    $self->cache_info;
117
118    return $self;
119}
120
121sub cache_info {
122    my $self = shift;
123
124    #TODO: warn
125    # return without saving if the info is empty
126    return unless %{ $self->{album_info} } && @{ $self->{tracks_info} };
127
128    my $filename = $self->{discid} . '.yml';
129    YAML::DumpFile($filename, {
130        discid      => $self->{discid},
131        album_info  => $self->{album_info},
132        tracks_info => $self->{tracks_info},
133    });
134}
135   
136
137# utility to make filenames match [A-Za-z0-9_]+
138sub filename_escape {
139    my @strings = map { 
140        unidecode($_);  # unicode to ascii
141        $_ = lc $_;     # all lowercase
142       
143        # special substituations
144        s/&/ and /g;
145        s/(\w)'(\w)/$1$2/g;
146
147        s/[^A-Za-z0-9]/_/g;  # ascii alphanumerics only
148        s/_+/_/g;            # compress underscores
149        s/^_|_$//g;       
150
151        $_;  # return
152    } @_;
153    return wantarray ? @strings : $strings[-1];
154}
155
156sub get_mp3_filename {
157    my ($self, $args) = @_;
158
159    if (my $track_num = $args->{track}) {
160        my $track = $self->{tracks_info}[$track_num - 1];
161
162        #my $format = $args->{format} || '%02n-%a-%b-%t.mp3';
163        #my %codes = map { $_ => filename_escape($track->{$CODES{$_}}) } keys %CODES;
164        #return stringf($format, %codes);
165       
166        my $format = $args->{format} || '%(TRACKNUM)02d-%(ARTIST)s-%(ALBUM)s-%(TITLE)s.mp3';
167        my $formatter = Text::Sprintf::Named->new({fmt => $format});
168        return $formatter->format({
169            args => { map { $_ => filename_escape($track->{$_}) } keys %$track },
170        });
171
172        #return sprintf(
173        #    '%02d-%s-%s-%s.mp3',
174        #    $track->{TRACKNUM},
175        #    filename_escape(@{ $track }{qw{ARTIST ALBUM TITLE}}),
176        #);
177    }
178
179    return;
180}
181
182sub get_m3u_filename {
183    my ($self, $args) = @_;
184
185    my $album = $self->{album_info};
186
187    my $format = $args->{format} || '%(ARTIST)s-%(ALBUM)s.m3u';
188    my $formatter = Text::Sprintf::Named->new({fmt => $format});
189    return $formatter->format({
190        args => { map { $_ => filename_escape($album->{$_}) } keys %$album },
191    });
192
193    #return sprintf('%s-%s.m3u', filename_escape(@{ $self->{album_info} }{qw{ARTIST ALBUM}}));
194}
195
196package main;
197
198use YAML;
199
200my $flac_file = shift;
201
202my $discid = `metaflac --show-tag MUSICBRAINZ_DISCID $flac_file | cut -d = -f 2`;
203chomp $discid;
204
205my $tracks = Tracks->new($discid);
206my $info = $tracks->query_musicbrainz->get_tracks_info;
207
208print Dump($info);
209
210# split into individual mp3 tracks
211
212my $m3u_filename = $tracks->get_m3u_filename;
213print $m3u_filename, "\n";
214#open my $M3U, '>', $m3u_filename;
215#print $M3U "#EXTM3U\n";
216
217for my $i (1 .. $tracks->get_track_count) {
218    my $track = $info->[$i - 1];
219   
220    my $mp3_filename = $tracks->get_mp3_filename({ 
221        track => $i,
222        format => '%(ARTIST)s/%(ALBUM)s/%(TRACKNUM)02d-%(TITLE)s.mp3',
223    });
224
225    print '  ', $mp3_filename, "\n";
226    next;
227
228    # MP3 encoder
229    open my $LAME, '|-', 'lame',
230        '--tt', $track->{TITLE},
231        '--ta', $track->{ARTIST},
232        '--tl', $track->{ALBUM},
233        '--tn', $track->{TRACKNUM},
234        '--add-id3v2',           
235        '-', $mp3_filename;
236
237    my $start = $i;
238    my $end = $start + 1;
239   
240    # decode track
241    open my $FLAC, '-|', "flac -d --cue $start.1-$end.1 -o - $flac_file";
242
243    # pipe decoded FLAC audio to MP3 encoder
244    while (<$FLAC>) {
245        print $LAME $_;
246    }
247
248    close $LAME;
249    close $FLAC;
250
251    #print $M3U $mp3_filename, "\n";
252}
253
254#close $M3U;
Note: See TracBrowser for help on using the repository browser.