2 ############################################################################
3 # soepkiptng (c) copyright 2000 Eric Lammerts <eric@lammerts.org>.
4 ############################################################################
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License, version 2, as
7 # published by the Free Software Foundation.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # A copy of the GNU General Public License is available on the World Wide Web
15 # at `http://www.gnu.org/copyleft/gpl.html'. You can also obtain it by
16 # writing to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 # Boston, MA 02111-1307, USA.
18 ############################################################################
22 use Cwd qw
'abs_path cwd';
24 # find program directory
27 my $l = readlink or die "readlink $_: $!\n";
28 if($l =~ m
|^/|) { $_ = $l; } else { s|[^/]*$|/$l|; }
31 $progdir = abs_path
($1);
33 unshift @INC, "$progdir/lib";
35 require "$progdir/soepkiptng.lib";
36 $ENV{PATH
} = "$progdir/bin:$ENV{PATH}";
44 use vars qw
/$opt_r $opt_h $opt_q $opt_t $opt_l $opt_c $opt_f $opt_1 $opt_s $opt_A $opt_R $opt_S/;
52 my $q = "SELECT filename,artist.name as artist,album.name as album, language FROM song" .
53 " LEFT JOIN artist ON artist.id=song.artist_id" .
54 " LEFT JOIN album ON album.id=song.album_id" .
55 " LEFT JOIN lyrics ON lyrics.id=song.id" .
56 " WHERE song.artist_id=artist.id AND song.album_id=album.id" .
57 " AND present AND filename LIKE ?";
58 my $sth = $dbh->prepare($q);
60 or die "can't do sql command: " . $dbh->errstr;
66 while($_ = $sth->fetchrow_hashref) {
67 (my $dir = $_->{filename
}) =~ s
|^(.*)/[^/]+$|\
1|;
68 next unless $dir eq $f; # skip files in subdirs
70 if($artist && $artist ne $_->{artist
}) {
71 warn "$f: No common artist name found.\n";
74 $artist = $_->{artist
};
76 if($album && $album ne $_->{album
}) {
77 warn "$f: No common album name found.\n";
80 $language{$_->{language
}} = 1;
82 push @files, $_->{filename
};
84 my ($language) = (keys %language == 1)?
keys %language : ();
87 $newname .= string_to_filename
($artist, $language);
88 $newname .= "-" if $artist && $album;
89 $newname .= string_to_filename
($album, $language);
92 $newname = cwd
. "/$newname";
95 $newname = "$1/$newname";
99 } elsif(-e
$newname) {
100 warn "$f: $newname exists, skipping\n";
102 warn "renaming $f -> $newname\n";
103 if(rename $f, $newname) {
105 (my $fnew = $_) =~ s
|.*/|$newname/|;
107 $dbh->do("DELETE FROM song WHERE filename=?",
109 or die "can't do sql command: " . $dbh->errstr;
110 $dbh->do("UPDATE song SET filename=? WHERE filename=?",
112 or die "can't do sql command: " . $dbh->errstr;
115 warn "rename $f -> $newname: $!\n";
122 my (@id, $ti, $ar, $al, $tr, $len, $lyrics, $description, $language, $uuid, $trimlength);
126 my $full_filename = $f;
127 $full_filename =~ m
|/| or $full_filename = "./$f"; # prepend ./ if relative path
128 $full_filename =~ s|(.*)/|abs_path($1) . "/"|e; # get absolute pathname
130 my $filename_noext = $full_filename;
131 $filename_noext =~ s/\\/\\\\/g; # escape \
132 $filename_noext =~ s/_/\\_/g; # and _
133 $filename_noext =~ s/%/\\%/g; # and %
135 my $sth = $dbh->prepare(
136 "SELECT song
.id
,title
,artist
.name
,album
.name
,track
,length," .
137 "lyrics
,language
,description
,uuid
,trimlength FROM song
" .
138 "LEFT JOIN album ON album
.id
=song
.album_id
" .
139 "LEFT JOIN artist ON artist
.id
=song
.artist_id
" .
140 "LEFT JOIN lyrics ON lyrics
.id
=song
.id
" .
141 "WHERE present AND filename LIKE ? ORDER BY track
, trimstart
");
145 $sth->execute($filename_noext)
146 or die "can
't do sql command: " . $dbh->errstr;
147 ($id[0], $ti, $ar, $al, $tr, $len, $lyrics, $language, $description, $uuid, $trimlength) =
148 $sth->fetchrow_array and last;
152 # replace extension by .% wildcard
153 $filename_noext =~ s|\.[^/.]+$|.%|;
155 # replace first pathname component by %/
156 $filename_noext =~ s|^%?/+[^/]+/+|%/| or do {
157 warn "$f: not found in dbase\n" unless $opt_q;
165 while(($id2, $ti2) = $sth->fetchrow_array) {
171 $partial and warn "$f: partial filename match on $filename_noext\n" unless $opt_q;
176 do_soepkip($dbh, $f, $ar, $ti, $al, $tr, $len, $lyrics, $description, $language, $uuid);
177 } elsif(!$opt_f && (stat $f)[3] != 1) {
178 warn "$f: file has hardlinks, not writing (use -f to override)\n";
179 } elsif($f =~ /\.mp[123]$/i) {
180 do_mp3($dbh, $f, $ar, $ti, $al, $tr, $len, $lyrics, $description, $language, $uuid);
181 } elsif($f =~ /\.flac$/i) {
182 do_flac($dbh, $f, $ar, $ti, $al, $tr, $len);
183 } elsif($f =~ /\.ogg$/i) {
184 do_ogg($dbh, $f, $ar, $ti, $al, $tr, $len);
186 warn "unknown file type: $f\n";
191 $f =~ /.*\.(.*?)$/ and $ext = lc($1);
194 $newname .= string_to_filename($ar, $language) . "-" if $ar;
195 $newname .= string_to_filename($al);
199 $newname .= sprintf "%02d-", $tr if $tr;
200 $newname .= string_to_filename($ar, $language) . "-" if $ar;
201 $newname .= string_to_filename($ti);
202 $newname =~ s/--+/-/;
203 #$newname = sprintf "%02d - ", $tr if $tr; $newname .= "$ar - $ti"; $newname =~ s/([\x80-\xff])/lc($latin9_to_ascii{ord($1)}) || $1/ge;
204 if($opt_l && length("$newname.$ext") > $opt_l) {
205 $newname = substr($newname, 0, $opt_l - length(".$ext") - 1);
206 $newname =~ s/_?$/_/;
209 $full_filename =~ m|^(.*)/|;
210 $newname = "$1/$newname";
212 if($newname ne $full_filename) {
213 warn "renaming $full_filename -> $newname\n";
215 warn "$f: $newname exists, skipping\n";
217 if(rename $full_filename, $newname) {
219 $dbh->do("DELETE FROM song WHERE filename=?",
221 or die "can't
do sql command
: " . $dbh->errstr;
222 foreach my $id (@id) {
223 $dbh->do("UPDATE song SET filename
=? WHERE id
=?
",
224 undef, $newname, $id)
225 or die "can
't do sql command: " . $dbh->errstr;
229 warn "rename $full_filename -> $newname: $!\n";
237 sub do_soepkip($$$$$$$$$) {
238 my ($dbh, $f, $ar, $ti, $al, $tr, $len, $lyrics, $lyrdesc, $lyrlang, $uuid) = @_;
241 $f =~ s|(.*)/+|| and $dir = $1;
243 open OUT, ">$dir/.soepkiptng_info.tmp"
244 or do { warn "$dir/.soepkiptng_info.tmp: $!\n"; return undef; };
253 if(open IN
, "$dir/.soepkiptng_info") {
256 if(/^\[([^\]]+)\]/) { $skip = $1 eq $f; }
257 print OUT
unless $skip;
261 if(!rename "$dir/.soepkiptng_info.tmp", "$dir/.soepkiptng_info") {
262 warn "rename $dir/.soepkiptng_info.tmp -> $dir/.soepkiptng_info: $!\n";
263 unlink "$dir/.soepkiptng_info.tmp"
264 or warn "$dir/.soepkiptng_info.tmp: $!\n";
270 sub do_mp3
($$$$$$$$$) {
271 my ($dbh, $f, $ar, $ti, $al, $tr, $len, $lyrics, $lyrdesc, $lyrlang, $uuid) = @_;
275 open F
, "+<$f" or do {
284 $buf =~ /^TAG/ or last;
285 warn "$f: removed ID3 tag\n";
287 truncate F
, tell F
or warn "truncate $f: $!\n"
294 $mp3 = MP3
::Tag
->new($f) or die "$f: $!\n";
295 $mp3->config("autoinfo", "ID3v2");
296 my ($p_ti, $p_tr, $p_ar, $p_al) = $mp3->autoinfo();
298 my ($p_ufidname, $p_uuid);
300 my $newtag = $mp3->{ID3v2
};
301 if(defined($newtag)) {
302 $newtag->remove_frame('TIT2');
303 $newtag->remove_frame('TPE1');
304 $newtag->remove_frame('TALB');
305 $newtag->remove_frame('TRCK');
306 $p_len = $mp3->{ID3v2
}->getFrame('TLEN');
307 $newtag->remove_frame('TLEN');
308 $p_uslt = $mp3->{ID3v2
}->getFrame('USLT');
309 $newtag->remove_frame('USLT');
310 ($p_ufid) = $mp3->{ID3v2
}->getFrame('UFID');
311 if($p_ufid->{Text
} eq "UUID") { $p_uuid = $p_ufid->{_Data
}; }
312 $newtag->remove_frame('UFID');
313 #warn Dumper($p_uslt);
315 $newtag = $mp3->newTag('ID3v2');
317 $p_uslt->{Text
} =~ s/\s+$//;
319 if($opt_f || $p_ti ne $ti || $p_tr != $tr || $p_ar ne $ar || $p_al ne $al ||
320 $p_len != 1000 * $len || $p_uslt->{Description
} ne $lyrdesc ||
321 ($lyrics && $p_uslt->{Language
} ne $lyrlang) ||
322 $p_uslt->{Text
} ne $lyrics || $p_uuid ne $uuid) {
324 $newtag->add_frame('TIT2', $ti) if $ti;
325 $newtag->add_frame('TPE1', $ar) if $ar;
326 $newtag->add_frame('TALB', $al) if $al;
327 $newtag->add_frame('TRCK', $tr) if $tr;
329 $newtag->add_frame('TLEN', 1000 * $len);
332 $newtag->add_frame('USLT', 0, $lyrlang, $lyrdesc, $lyrics);
335 $newtag->add_frame('UFID', "UUID", $uuid);
337 warn "$f: updating ID3v2 tag\n";
342 $ti =~ s/^(.{29}).{2,}/$1_/;
343 $ar =~ s/^(.{29}).{2,}/$1_/;
344 $al =~ s/^(.{29}).{2,}/$1_/;
345 my $id3v1 = $mp3->{ID3v1
};
346 if(defined($id3v1)) {
347 if($id3v1->song eq $ti &&
348 $id3v1->artist eq $ar &&
349 $id3v1->album eq $al &&
350 $id3v1->track == $tr) { $id3v1 = undef; }
352 $id3v1 = $mp3->newTag('ID3v1');
354 if(defined($id3v1)) {
359 warn "$f: updating ID3 tag\n";
366 sub do_ogg
($$$$$$$) {
367 my ($dbh, $f, $ar, $ti, $al, $tr, $len) = @_;
371 # read current tags & see what needs to be changed
372 if(open(FR
, "-|") == 0) {
373 exec "vorbiscomment", "-ql", $f;
379 /^(\w+)=(.*)/ and $oldtag{$1} = $2;
383 return 1 if !$opt_f && $oldtag{artist
} eq $ar && $oldtag{title
} eq $ti && $oldtag{album
} eq $al && $oldtag{tracknumber
} == $tr;
385 warn "$f: updating vorbis comments\n";
388 if(open(FR
, "-|") == 0) {
389 exec "vorbiscomment", "-ql", $f;
392 if(open(FW
, "|-") == 0) {
393 exec "vorbiscomment", "-qw", $f;
403 # copy other tags verbatim
405 next if /^(artist|title|album|tracknumber)=/;
413 sub do_flac
($$$$$$$) {
414 my ($dbh, $f, $ar, $ti, $al, $tr, $len) = @_;
417 # read current tags & see what needs to be changed
419 if(open(F
, "-|") == 0) {
420 exec "metaflac", "--export-tags-to=-", $f;
426 /^(\w+)=(.*)/ and $oldtag{$1} = $2;
429 if($opt_f || $oldtag{artist
} ne $ar) { push @changeargs, "--remove-tag=artist", "--set-tag=artist=$ar"; }
430 if($opt_f || $oldtag{title
} ne $ti) { push @changeargs, "--remove-tag=title", "--set-tag=title=$ti"; }
431 if($opt_f || $oldtag{album
} ne $al) { push @changeargs, "--remove-tag=album", "--set-tag=album=$al"; }
432 if($opt_f || $oldtag{tracknumber
} != $tr) { push @changeargs, "--remove-tag=tracknumber", "--set-tag=tracknumber=$tr"; }
436 warn "$f: updating vorbis comments\n";
437 if(!$opt_t) { system "metaflac", @changeargs, $f; }
443 getopts
('rfhqtl:c:1sADS');
445 read_configfile
(\
%conf, $opt_c);
447 ($prog = $0) =~ s
|.*/||;
448 if($opt_h || !@ARGV) {
450 Usage: $prog [-hrqfts] [-l len] mp3files...
454 -q : don't warn about partial/failed filename matches.
455 -f : force tag writing
456 -t : testmode, don't actually write any tags.
457 -l len : restrict length of filenames to 'len'
458 -1 : also write ID3 tag
459 -s : write to .soepkiptng_info instead of file
461 $prog reads Artist/Title/Album/Track info from the SoepkipTNG
462 database and writes it to the specified files using ID3v2 (unless the files
463 already have ID3v2 tags containing the exact same info). All ID3v1 tags are
466 If the -r switch is used, the files are renamed to a standard format:
467 track-artist-title.mp3 (if there is a track number) or artist-title.mp3
468 (if there is no track number).
469 * track consists of two or more digits.
470 * artist may contain only alphanumeric characters and underscores.
471 * title may contain only alphanumeric characters, underscores and dashes.
479 my $dbh = connect_to_db
(\
%conf);