2 ############################################################################
3 # soepkiptng (c) copyright 2000 Eric Lammerts <eric@lammerts.org>.
5 ############################################################################
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License, version 2, as
8 # published by the Free Software Foundation.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # A copy of the GNU General Public License is available on the World Wide Web
16 # at `http://www.gnu.org/copyleft/gpl.html'. You can also obtain it by
17 # writing to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 # Boston, MA 02111-1307, USA.
19 ############################################################################
25 # find program directory
28 my $l = readlink or die "readlink $_: $!\n";
29 if($l =~ m
|^/|) { $_ = $l; } else { s|[^/]*$|/$l|; }
32 $progdir = abs_path
($1);
34 unshift @INC, "$progdir/lib";
36 require "$progdir/soepkiptng.lib";
37 $ENV{PATH
} = "$progdir/bin:$ENV{PATH}";
45 use vars qw
/$opt_r $opt_h $opt_q $opt_t $opt_l $opt_c $opt_f $opt_1/;
53 my $q = "SELECT filename,artist.name as artist,album.name as album" .
54 " FROM song,artist,album" .
55 " WHERE song.artist_id=artist.id AND song.album_id=album.id" .
56 " AND present AND filename LIKE ?";
57 my $sth = $dbh->prepare($q);
59 or die "can't do sql command: " . $dbh->errstr;
64 while($_ = $sth->fetchrow_hashref) {
65 (my $dir = $_->{filename
}) =~ s
|^(.*)/[^/]+$|\
1|;
66 next unless $dir eq $f; # skip files in subdirs
67 if($artist && $artist ne $_->{artist
}) {
68 warn "$f: No common artist name found.\n";
71 $artist = $_->{artist
};
72 if($album && $album ne $_->{album
}) {
73 warn "$f: No common album name found.\n";
77 push @files, $_->{filename
};
81 $newname .= string_to_filename
($artist) . "-" if $artist;
82 $newname .= string_to_filename
($album);
85 $newname = "$1/$newname";
88 } elsif(-e
$newname) {
89 warn "$f: $newname exists, skipping\n";
91 warn "renaming $f -> $newname\n";
92 if(rename $f, $newname) {
94 (my $fnew = $_) =~ s
|.*/|$newname/|;
96 $dbh->do("DELETE FROM song WHERE filename=?",
98 or die "can't do sql command: " . $dbh->errstr;
99 $dbh->do("UPDATE song SET filename=? WHERE filename=?",
101 or die "can't do sql command: " . $dbh->errstr;
104 warn "rename $f -> $newname: $!\n";
111 my ($id, $ti, $ar, $al, $tr, $len, $lyrics, $description, $language, $uuid);
115 my $full_filename = $f;
116 $full_filename =~ m
|/| or $full_filename = "./$f"; # prepend ./ if relative path
117 $full_filename =~ s|(.*)/|abs_path($1) . "/"|e; # get absolute pathname
119 my $filename_noext = $full_filename;
120 $filename_noext =~ s/\\/\\\\/g; # escape \
121 $filename_noext =~ s/_/\\_/g; # and _
122 $filename_noext =~ s/%/\\%/g; # and %
124 my $sth = $dbh->prepare(
125 "SELECT song
.id
,title
,artist
.name
,album
.name
,track
,length," .
126 "lyrics
,language
,description
,uuid FROM song
" .
127 "LEFT JOIN album ON album
.id
=song
.album_id
" .
128 "LEFT JOIN artist ON artist
.id
=song
.artist_id
" .
129 "LEFT JOIN lyrics ON lyrics
.id
=song
.id
" .
130 "WHERE present AND filename LIKE ?
");
134 $sth->execute($filename_noext)
135 or die "can
't do sql command: " . $dbh->errstr;
136 ($id, $ti, $ar, $al, $tr, $len, $lyrics, $language, $description, $uuid) =
137 $sth->fetchrow_array and last;
141 # replace extension by .% wildcard
142 $filename_noext =~ s|\.[^/.]+$|.%|;
144 # replace first pathname component by %/
145 $filename_noext =~ s|^%?/+[^/]+/+|%/| or do {
146 warn "$f: not found in dbase\n" unless $opt_q;
152 $partial and warn "$f: partial filename match on $filename_noext\n" unless $opt_q;
156 if($f =~ /\.mp[123]$/i) {
157 do_mp3($dbh, $f, $ar, $ti, $al, $tr, $len, $lyrics, $description, $language, $uuid);
158 } elsif($f =~ /\.flac$/i) {
159 do_flac($dbh, $f, $ar, $ti, $al, $tr, $len);
160 } elsif($f =~ /\.ogg$/i) {
161 do_ogg($dbh, $f, $ar, $ti, $al, $tr, $len);
163 warn "unknown file type: $f\n";
168 $f =~ /.*\.(.*?)$/ and $ext = lc($1);
170 $newname .= sprintf "%02d-", $tr if $tr;
171 $newname .= string_to_filename($ar) . "-" if $ar;
172 $newname .= string_to_filename($ti);
173 $newname =~ s/--+/-/;
174 if($opt_l && length("$newname.$ext") > $opt_l) {
175 $newname = substr($newname, 0, $opt_l - length(".$ext") - 1);
176 $newname =~ s/_?$/_/;
179 $full_filename =~ m|^(.*)/|;
180 $newname = "$1/$newname";
182 if($newname ne $full_filename) {
183 warn "renaming $full_filename -> $newname\n";
185 warn "$f: $newname exists, skipping\n";
187 if(rename $full_filename, $newname) {
188 $dbh->do("DELETE FROM song WHERE filename=?",
190 or die "can't
do sql command
: " . $dbh->errstr;
191 $dbh->do("UPDATE song SET filename
=? WHERE id
=?
",
192 undef, $newname, $id)
193 or die "can
't do sql command: " . $dbh->errstr;
195 warn "rename $full_filename -> $newname: $!\n";
203 sub do_mp3($$$$$$$$$) {
204 my ($dbh, $f, $ar, $ti, $al, $tr, $len, $lyrics, $lyrdesc, $lyrlang, $uuid) = @_;
208 open F, "+<$f" or do {
217 $buf =~ /^TAG/ or last;
218 warn "$f: removed ID3 tag\n";
220 truncate F, tell F or warn "truncate $f: $!\n"
227 $mp3 = MP3::Tag->new($f) or die "$f: $!\n";
228 $mp3->config("autoinfo", "ID3v2");
229 my ($p_ti, $p_tr, $p_ar, $p_al) = $mp3->autoinfo();
231 my ($p_ufidname, $p_uuid);
233 my $newtag = $mp3->{ID3v2};
234 if(defined($newtag)) {
235 $newtag->remove_frame('TIT2
');
236 $newtag->remove_frame('TPE1
');
237 $newtag->remove_frame('TALB
');
238 $newtag->remove_frame('TRCK
');
239 $p_len = $mp3->{ID3v2}->getFrame('TLEN
');
240 $newtag->remove_frame('TLEN
');
241 $p_uslt = $mp3->{ID3v2}->getFrame('USLT
');
242 $newtag->remove_frame('USLT
');
243 ($p_ufid) = $mp3->{ID3v2}->getFrame('UFID
');
244 if($p_ufid->{Text} eq "UUID") { $p_uuid = $p_ufid->{_Data}; }
245 $newtag->remove_frame('UFID
');
246 #warn Dumper($p_uslt);
248 $newtag = $mp3->newTag('ID3v2
');
250 if($opt_f || $p_ti ne $ti || $p_tr != $tr || $p_ar ne $ar || $p_al ne $al ||
251 $p_len != 1000 * $len || $p_uslt->{Description} ne $lyrdesc ||
252 ($lyrics && $p_uslt->{Language} ne $lyrlang) ||
253 $p_uslt->{Text} ne $lyrics || $p_uuid ne $uuid) {
255 $newtag->add_frame('TIT2
', $ti) if $ti;
256 $newtag->add_frame('TPE1
', $ar) if $ar;
257 $newtag->add_frame('TALB
', $al) if $al;
258 $newtag->add_frame('TRCK
', $tr) if $tr;
260 $newtag->add_frame('TLEN
', 1000 * $len);
263 $newtag->add_frame('USLT
', 0, $lyrlang, $lyrdesc, $lyrics);
266 $newtag->add_frame('UFID
', "UUID", $uuid);
268 warn "$f: updating ID3v2 tag\n";
273 $ti =~ s/^(.{29}).{2,}/$1_/;
274 $ar =~ s/^(.{29}).{2,}/$1_/;
275 $al =~ s/^(.{29}).{2,}/$1_/;
276 my $id3v1 = $mp3->{ID3v1};
277 if(defined($id3v1)) {
278 if($id3v1->song eq $ti &&
279 $id3v1->artist eq $ar &&
280 $id3v1->album eq $al &&
281 $id3v1->track == $tr) { $id3v1 = undef; }
283 $id3v1 = $mp3->newTag('ID3v1
');
285 if(defined($id3v1)) {
290 warn "$f: updating ID3 tag\n";
297 sub do_ogg($$$$$$$) {
298 my ($dbh, $f, $ar, $ti, $al, $tr, $len) = @_;
302 # read current tags & see what needs to be changed
304 if(open(FR, "-|") == 0) {
305 exec "vorbiscomment", "-ql", $f;
310 if((/^artist=(.*)/ && $1 ne $ar) ||
311 (/^title=(.*)/ && $1 ne $ti) ||
312 (/^album=(.*)/ && $1 ne $al) ||
313 (/^tracknumber=(.*)/ && $1 != $tr)) {
320 $needchange or return 1;
321 warn "$f: updating vorbis comments\n";
324 if(open(FR, "-|") == 0) {
325 exec "vorbiscomment", "-ql", $f;
328 if(open(FW, "|-") == 0) {
329 exec "vorbiscomment", "-qw", $f;
339 # copy other tags verbatim
341 next if /^(artist|title|album|tracknumber)=/;
349 sub do_flac
($$$$$$$) {
350 my ($dbh, $f, $ar, $ti, $al, $tr, $len) = @_;
353 # read current tags & see what needs to be changed
355 if(open(F
, "-|") == 0) {
356 exec "metaflac", "--export-vc-to=-", $f;
361 if($opt_f || (/^artist=(.*)/ && $1 ne $ar)) {
362 push @changeargs, "--remove-vc-field=artist", "--set-vc-field=artist=$ar";
363 } elsif($opt_f || (/^title=(.*)/ && $1 ne $ti)) {
364 push @changeargs, "--remove-vc-field=title", "--set-vc-field=title=$ti";
365 } elsif($opt_f || (/^album=(.*)/ && $1 ne $al)) {
366 push @changeargs, "--remove-vc-field=album", "--set-vc-field=album=$al";
367 } elsif($opt_f || (/^tracknumber=(.*)/ && $1 != $tr)) {
368 push @changeargs, "--remove-vc-field=tracknumber", "--set-vc-field=tracknumber=$tr";
375 warn "$f: updating vorbis comments\n";
376 if(!$opt_t) { system "metaflac", @changeargs, $f; }
382 getopts
('rfhqtl:c:1');
384 read_configfile
(\
%conf, $opt_c);
386 ($prog = $0) =~ s
|.*/||;
387 if($opt_h || !@ARGV) {
389 Usage: $prog [-hrqft] [-l len] mp3files...
393 -q : don't warn about partial/failed filename matches.
394 -f : force tag writing
395 -t : testmode, don't actually write any tags.
396 -l len : restrict length of filenames to 'len'
397 -1 : also write ID3 tag
399 $prog reads Artist/Title/Album/Track info from the SoepkipTNG
400 database and writes it to the specified files using ID3v2 (unless the files
401 already have ID3v2 tags containing the exact same info). All ID3v1 tags are
404 If the -r switch is used, the files are renamed to a standard format:
405 track-artist-title.mp3 (if there is a track number) or artist-title.mp3
406 (if there is no track number).
407 * track consists of two or more digits.
408 * artist may contain only alphanumeric characters and underscores.
409 * title may contain only alphanumeric characters, underscores and dashes.
417 my $dbh = DBI
->connect("DBI:$conf{'db_type'}:$conf{'db_name'}:$conf{'db_host'}",
418 $conf{'db_user'}, $conf{'db_pass'})
419 or die "can't connect to database";