1 #!/usr/local/bin/perl -w 2 # author: seth 3 # e-mail: for e-mail-address see http://www.wg-karlsruhe.de/seth/email_address.php 4 # description: modifies id3-tags, e.g. using filename 5 6 use MP3::Tag; 7 use Cwd; 8 use strict; 9 10 sub syntaxCheck{ 11 my @params=@_; 12 my @path_splitted=split(/[\/\\]/, reverse($0)); 13 my $prg_name=reverse($path_splitted[0]); 14 my $version='0.51.20060820'; 15 my $usage='writes id3-tags using filename (recursively) 16 17 usage: '.$prg_name.' filesRE [options] 18 19 filesRE files to work on (use regular expressions!) 20 -r, --recursively search subdirectories recursively 21 --removeAllTags removes all tags only (forced info will be ignored) 22 --1to2 just copy id3v1 to id3v2 23 --2to1 just copy id3v2 to id3v1 24 -u --update don\'t overwrite existing id3-tags 25 -l --list_id3 list id3-tags only (similar to --test --v=3, but less output) 26 --v[erbose]=x verbose (x=0: no output, x=1: default output, x=2: much output, x=3: too much output) 27 -t, --test don\'t change anything, just print possible changes 28 -V --version display version and exit. 29 forced info: may be (multiple) chosen from the following case-sensitive list 30 --TIT2=".*" title [string] 31 --TRCK=\\d{2} track no. [2 digit number] 32 --TPE1=".*" artist [string] 33 --TYER=\\d{4} year [4 digit number] 34 --TALB=".*" album [string] 35 36 examples: '.$prg_name.' "^\\d\\d.*\\.mp3$" 37 '.$prg_name.' "\\.mp3$" --removeAllTags 38 '.$prg_name.' "\\.mp3$" -r --v=0 --TYER=2004 --TALB="dick und doof" 39 '.$prg_name.' "\\.mp3$" -r -u --TYER=2004 --TALB="dick und doof"'."\n"; 40 my $syntax_correct=0; 41 my %param_hash; 42 $param_hash{'recursively'} =0; 43 $param_hash{'removeAllTags'}=0; 44 $param_hash{'update'} =0; 45 $param_hash{'list_id3'} =0; 46 $param_hash{'1to2'} =0; 47 $param_hash{'2to1'} =0; 48 $param_hash{'verbose'} =1; 49 $param_hash{'version'} =0; 50 $param_hash{'test'} =0; 51 if(defined($params[0])){ 52 $param_hash{'filesRE'}=shift(@params); 53 $syntax_correct=1; 54 foreach(@params){ 55 if($_=~/^-[^-]./){ 56 while(length($_)>2){ 57 push(@params, '-'.substr($_, 2, 1)); 58 $_=substr($_, 0, 2).((length($_)>3)?substr($_, 3):''); 59 } 60 } 61 if($_ eq '-r' || $_ eq '--recursively'){ 62 $param_hash{'recursively'}=1; 63 next; 64 } 65 if($_ eq '-u' || $_ eq '--update'){ 66 $param_hash{'update'}=1; 67 next; 68 } 69 if($_ eq '-l' || $_ eq '--list_id3'){ 70 $param_hash{'list_id3'}=1; 71 next; 72 } 73 if($_ eq '--1to2'){ 74 $param_hash{'1to2'}=1; 75 next; 76 } 77 if($_ eq '--2to1'){ 78 $param_hash{'2to1'}=1; 79 next; 80 } 81 if($_ eq '--removeAllTags'){ 82 $param_hash{'removeAllTags'}=1; 83 next; 84 } 85 if($_ eq '-t' || $_ eq '--test'){ 86 $param_hash{'test'}=1; 87 next; 88 } 89 if($_=~/^--v(erbose)?=([0123])$/){ 90 $param_hash{'verbose'}=$2; 91 next; 92 } 93 if($_ eq '-V' || $_ eq '--version'){ 94 $param_hash{'version'}=1; 95 next; 96 } 97 if($_=~/^--(T[A-Z0-9]{3})=(.*)$/){ 98 $param_hash{$1}=$2; 99 }else{ 100 $syntax_correct=0; 101 last; 102 } 103 } 104 } 105 $syntax_correct=0 if $param_hash{'2to1'}==1 && $param_hash{'1to2'}==1; 106 $syntax_correct=0 if @params==0 && defined $param_hash{'filesRE'} && $param_hash{'filesRE'}=~/^--?h(elp)?$/; 107 if($param_hash{'version'}){ 108 my $version_info='mod_id3.pl '.$version."\n".' 109 this program is distributed in the hope that it will be useful, 110 but without any warranty; without even the implied warranty of 111 merchantability or fitness for a particular purpose. 112 113 originally written by seth (for e-mail-address see http://www.wg-karlsruhe.de/seth/email_address.php).'."\n"; 114 die $version_info; 115 }else{ 116 $syntax_correct || die $usage; 117 } 118 return %param_hash; 119 } 120 121 sub print_id3_tags{ 122 my $file=shift; 123 my $mp3=MP3::Tag->new($file); 124 $mp3->get_tags(); 125 if(exists $mp3->{ID3v1}){ 126 my @tagdata_v1=$mp3->{ID3v1}->all; 127 print ' id3v1: ('.$tagdata_v1[2].') '.$tagdata_v1[5].'. '.$tagdata_v1[1].' - '.$tagdata_v1[0].' ('.$tagdata_v1[3].")\n"; 128 print ' '.$tagdata_v1[6].'; '.$tagdata_v1[4]."\n" if($tagdata_v1[4].$tagdata_v1[6] ne ''); 129 } 130 if(exists $mp3->{ID3v2}){ 131 my @tagdata_v2=('', '', '', '', '', '', ''); 132 my $frameIDs_hash=$mp3->{ID3v2}->get_frame_ids; 133 foreach(keys %$frameIDs_hash){ 134 my ($cont, $name)=$mp3->{ID3v2}->get_frame($_); 135 if(ref $cont){ 136 while(my ($key,$val)=each %$cont){ 137 push(@tagdata_v2, [($_, $key, $val)]) if $val ne '' && substr($key,0,1) ne '_'; 138 } 139 }else{ 140 if($_ eq 'TIT2'){ $tagdata_v2[0]=$cont; 141 }elsif($_ eq 'TPE1'){ $tagdata_v2[1]=$cont; 142 }elsif($_ eq 'TALB'){ $tagdata_v2[2]=$cont; 143 }elsif($_ eq 'TYER'){ $tagdata_v2[3]=$cont; 144 }elsif($_ eq 'TRCK'){ $tagdata_v2[5]=$cont; 145 }elsif($_ eq 'TCON'){ $tagdata_v2[6]=$cont; 146 }else{ push(@tagdata_v2, [($_, $name, $cont)]) if $cont ne '' && substr($name,0,1) ne '_'; 147 } 148 } 149 } 150 print ' id3v2: ('.$tagdata_v2[2].') '.$tagdata_v2[5].'. '.$tagdata_v2[1].' - '.$tagdata_v2[0].' ('.$tagdata_v2[3].")\n"; 151 print ' '; 152 print $tagdata_v2[6].'; ' if $tagdata_v2[6] ne ''; 153 for(my $i=7;$i<@tagdata_v2;++$i){ 154 print $tagdata_v2[$i][1].' ('.$tagdata_v2[$i][0].')='.$tagdata_v2[$i][2].'; '; 155 } 156 print "\n"; 157 } 158 $mp3->close; 159 } 160 161 sub set_info_to_file{ 162 my $file=shift; 163 my @info; 164 for(1..5){ 165 push(@info, shift); 166 } 167 my %params=@_; 168 die 'error 2078346: see code'."\n" if @_%2!=0; 169 my $removeAllTags=$params{'removeAllTags'}; 170 my $verbose=$params{'verbose'}; 171 my $update=$params{'update'}; 172 my $one_to_two=$params{'1to2'}; 173 my $two_to_one=$params{'2to1'}; 174 my $test=$params{'test'}; 175 my $mp3=MP3::Tag->new($file); 176 $mp3->get_tags() if $update==1; 177 if($test==0 && $one_to_two==0){ 178 if($update==0 || not exists $mp3->{ID3v1}){ 179 $mp3->new_tag("ID3v1") if not exists $mp3->{ID3v1}; 180 if($removeAllTags==1){ 181 $mp3->{ID3v1}->remove_tag(); 182 }else{ 183 $mp3->{ID3v1}->all($info[0], $info[2], $info[3], $info[4], '', $info[1], ''); 184 $mp3->{ID3v1}->write_tag; 185 } 186 }else{ # $update==1 && exists $mp3->{ID3v1} 187 $mp3->{ID3v1}->title($info[0]) if($mp3->{ID3v1}->title eq ''); 188 $mp3->{ID3v1}->track($info[1]) if($mp3->{ID3v1}->track eq ''); 189 $mp3->{ID3v1}->artist($info[2]) if($mp3->{ID3v1}->artist eq ''); 190 $mp3->{ID3v1}->album($info[3]) if($mp3->{ID3v1}->album eq ''); 191 $mp3->{ID3v1}->year($info[4]) if($mp3->{ID3v1}->year eq ''); 192 $mp3->{ID3v1}->write_tag; 193 } 194 } 195 if($two_to_one==0){ 196 if($update==0 || not exists $mp3->{ID3v2}){ 197 if($test==0){ 198 $mp3->new_tag("ID3v2") if not exists $mp3->{ID3v2}; 199 $mp3->{ID3v2}->remove_tag() if($update==0); 200 if($removeAllTags==0){ 201 (defined $mp3->{ID3v2}->get_frame('TIT2'))? $mp3->{ID3v2}->change_frame('TIT2', $info[0]) : $mp3->{ID3v2}->add_frame('TIT2', $info[0]); 202 (defined $mp3->{ID3v2}->get_frame('TRCK'))? $mp3->{ID3v2}->change_frame('TRCK', $info[1]) : $mp3->{ID3v2}->add_frame('TRCK', $info[1]); 203 (defined $mp3->{ID3v2}->get_frame('TPE1'))? $mp3->{ID3v2}->change_frame('TPE1', $info[2]) : $mp3->{ID3v2}->add_frame('TPE1', $info[2]); 204 if(exists $params{'TYER'}){ 205 (defined $mp3->{ID3v2}->get_frame('TYER'))? $mp3->{ID3v2}->change_frame('TYER', $info[4]) : $mp3->{ID3v2}->add_frame('TYER', $info[4]); 206 } 207 (defined $mp3->{ID3v2}->get_frame('TALB'))? $mp3->{ID3v2}->change_frame('TALB', $info[3]) : $mp3->{ID3v2}->add_frame('TALB', $info[3]); 208 $mp3->{ID3v2}->write_tag; 209 } 210 } 211 }else{ # $update==1 && exists $mp3->{ID3v2} 212 my $output=' forced : '; 213 my $changed=1; 214 if(defined $mp3->{ID3v2}->get_frame('TALB')){ 215 if($mp3->{ID3v2}->get_frame('TALB') eq ''){ 216 $mp3->{ID3v2}->change_frame('TALB', $info[3]) if($test==0); 217 }else{ 218 $changed=0; 219 } 220 }else{ 221 $mp3->{ID3v2}->add_frame('TALB', $info[3]) if($test==0); 222 } 223 $output.='('.(($changed==1)?$info[3]:'[leave]').') '; 224 $changed=1; 225 if(defined $mp3->{ID3v2}->get_frame('TRCK')){ 226 if($mp3->{ID3v2}->get_frame('TRCK') eq ''){ 227 $mp3->{ID3v2}->change_frame('TRCK', $info[1]) if($test==0); 228 }else{ 229 $changed=0; 230 } 231 }else{ 232 $mp3->{ID3v2}->add_frame('TRCK', $info[1]) if($test==0); 233 } 234 $output.=(($changed==1)?$info[1]:'[leave]').'. '; 235 $changed=1; 236 if(defined $mp3->{ID3v2}->get_frame('TPE1')){ 237 if($mp3->{ID3v2}->get_frame('TPE1') eq ''){ 238 $mp3->{ID3v2}->change_frame('TPE1', $info[2]) if($test==0); 239 }else{ 240 $changed=0; 241 } 242 }else{ 243 $mp3->{ID3v2}->add_frame('TPE1', $info[2]) if($test==0); 244 } 245 $output.=(($changed==1)?$info[2]:'[leave]').' - '; 246 $changed=1; 247 if(defined $mp3->{ID3v2}->get_frame('TIT2')){ 248 if($mp3->{ID3v2}->get_frame('TIT2') eq ''){ 249 $mp3->{ID3v2}->change_frame('TIT2', $info[0]) if($test==0); 250 }else{ 251 $changed=0; 252 } 253 }else{ 254 $mp3->{ID3v2}->add_frame('TIT2', $info[0]) if($test==0); 255 } 256 $output.=(($changed==1)?$info[0]:'[leave]').' '; 257 $changed=1; 258 if(exists $params{'TYER'}){ 259 if(defined $mp3->{ID3v2}->get_frame('TYER')){ 260 if($mp3->{ID3v2}->get_frame('TYER') eq ''){ 261 $mp3->{ID3v2}->change_frame('TYER', $info[4]) if($test==0); 262 }else{ 263 $changed=0; 264 } 265 }else{ 266 $mp3->{ID3v2}->add_frame('TYER', $info[4]) if($test==0); 267 } 268 } 269 $output.='('.(($changed==1)?$info[4]:'[leave]').')'; 270 print $output."\n" if($verbose>1); 271 $mp3->{ID3v2}->write_tag if($test==0); 272 } 273 } 274 $mp3->close; 275 } 276 277 sub get_info_from_file{ 278 my $file=shift; 279 my $verbose=shift; 280 my $list_id3=shift; 281 my $mp3=MP3::Tag->new($file); 282 $mp3->config('autoinfo', @_); 283 my @info=$mp3->autoinfo(); 284 $info[4]=$info[5]; 285 pop(@info); 286 pop(@info); 287 $info[1]='0'.$info[1] if(length($info[1])==1); 288 $mp3->close; 289 print_id3_tags($file) if($verbose>2 || ($verbose>0 && $list_id3)); 290 return @info; 291 } 292 293 sub search_dir{ 294 my $working_dir=shift; 295 my %params=@_; 296 my $filesRE=$params{'filesRE'}; 297 my $recursively=$params{'recursively'}; 298 my $removeAllTags=$params{'removeAllTags'}; 299 my $verbose=$params{'verbose'}; 300 my $one_to_two=$params{'1to2'}; 301 my $two_to_one=$params{'2to1'}; 302 my $update=$params{'update'}; 303 my $list_id3=$params{'list_id3'}; 304 my $entry; 305 my @files; 306 my @dirs; 307 print "\n".' '.$working_dir.'/'."\n" if $verbose>1 && $list_id3==0; 308 opendir(DIR, ".") || die $working_dir.": $!"; 309 while(telldir(DIR)>=0){ 310 $entry=readdir(DIR); 311 if(-d $entry){ 312 push(@dirs, $entry) 313 }else{ 314 if($entry=~/$filesRE/){ 315 print "\n".' '.$working_dir.'/'."\n" if(($verbose>1 && $list_id3==1) || ($verbose==1 && @files==0 && $list_id3==0)); 316 print 'change: '.$entry."\n" if $verbose>0 && $list_id3==0; 317 push(@files, $entry); 318 }else{ 319 print ' skip: '.$entry."\n" if $verbose>1 && $list_id3==0; 320 } 321 } 322 } 323 closedir(DIR); 324 @files=sort(@files); 325 @dirs=sort(@dirs); 326 my @info; 327 my @resources=('filename', 'ID3v2', 'ID3v1'); # 'ID3v2', 'ID3v1', 'filename' 328 @resources=('ID3v1') if $one_to_two==1; 329 @resources=('ID3v2') if $two_to_one==1; 330 foreach(@files){ 331 @info=(); 332 if($removeAllTags==0){ 333 @info=get_info_from_file($working_dir.'\\'.$_, $verbose, $list_id3, @resources); 334 die('@info too small or to large.') if @info!=5; 335 print ' found : ('.$info[3].') '.$info[1].'. '.$info[2].' - '.$info[0].' ('.$info[4].')'."\n" if($verbose>1 && $list_id3==0 && $update==0); 336 $info[0]=$params{'TIT2'} if exists $params{'TIT2'}; 337 $info[1]=$params{'TRCK'} if exists $params{'TRCK'}; 338 $info[2]=$params{'TPE1'} if exists $params{'TPE1'}; 339 $info[3]=$params{'TALB'} if exists $params{'TALB'}; 340 $info[4]=$params{'TYER'} if exists $params{'TYER'}; 341 print ' forced: ('.$info[3].') '.$info[1].'. '.$info[2].' - '.$info[0].' ('.$info[4].')'."\n" if($verbose>1 && $list_id3==0 && $update==0); 342 }elsif($removeAllTags==1){ 343 @info=('','','','',''); 344 } 345 set_info_to_file($working_dir.'\\'.$_, @info, %params) if $list_id3==0; 346 } 347 if($recursively==1){ 348 foreach(@dirs){ 349 if($_ ne '.' && $_ ne '..'){ 350 chdir($_); 351 search_dir($working_dir.'/'.$_, %params); 352 chdir('..'); 353 } 354 } 355 } 356 } 357 358 sub write_id3_tags_using_filenames{ 359 my %params=syntaxCheck(@_); 360 my $working_dir=cwd; 361 search_dir($working_dir, %params); 362 chdir($working_dir); 363 } 364 365 write_id3_tags_using_filenames(@ARGV);