#!/usr/local/bin/perl -w # author: seth # e-mail: for e-mail-address see http://www.wg-karlsruhe.de/seth/email_address.php # description: modifies id3-tags, e.g. using filename use MP3::Tag; use Cwd; use strict; sub syntaxCheck{ my @params=@_; my @path_splitted=split(/[\/\\]/, reverse($0)); my $prg_name=reverse($path_splitted[0]); my $version='0.51.20060820'; my $usage='writes id3-tags using filename (recursively) usage: '.$prg_name.' filesRE [options] filesRE files to work on (use regular expressions!) -r, --recursively search subdirectories recursively --removeAllTags removes all tags only (forced info will be ignored) --1to2 just copy id3v1 to id3v2 --2to1 just copy id3v2 to id3v1 -u --update don\'t overwrite existing id3-tags -l --list_id3 list id3-tags only (similar to --test --v=3, but less output) --v[erbose]=x verbose (x=0: no output, x=1: default output, x=2: much output, x=3: too much output) -t, --test don\'t change anything, just print possible changes -V --version display version and exit. forced info: may be (multiple) chosen from the following case-sensitive list --TIT2=".*" title [string] --TRCK=\\d{2} track no. [2 digit number] --TPE1=".*" artist [string] --TYER=\\d{4} year [4 digit number] --TALB=".*" album [string] examples: '.$prg_name.' "^\\d\\d.*\\.mp3$" '.$prg_name.' "\\.mp3$" --removeAllTags '.$prg_name.' "\\.mp3$" -r --v=0 --TYER=2004 --TALB="dick und doof" '.$prg_name.' "\\.mp3$" -r -u --TYER=2004 --TALB="dick und doof"'."\n"; my $syntax_correct=0; my %param_hash; $param_hash{'recursively'} =0; $param_hash{'removeAllTags'}=0; $param_hash{'update'} =0; $param_hash{'list_id3'} =0; $param_hash{'1to2'} =0; $param_hash{'2to1'} =0; $param_hash{'verbose'} =1; $param_hash{'version'} =0; $param_hash{'test'} =0; if(defined($params[0])){ $param_hash{'filesRE'}=shift(@params); $syntax_correct=1; foreach(@params){ if($_=~/^-[^-]./){ while(length($_)>2){ push(@params, '-'.substr($_, 2, 1)); $_=substr($_, 0, 2).((length($_)>3)?substr($_, 3):''); } } if($_ eq '-r' || $_ eq '--recursively'){ $param_hash{'recursively'}=1; next; } if($_ eq '-u' || $_ eq '--update'){ $param_hash{'update'}=1; next; } if($_ eq '-l' || $_ eq '--list_id3'){ $param_hash{'list_id3'}=1; next; } if($_ eq '--1to2'){ $param_hash{'1to2'}=1; next; } if($_ eq '--2to1'){ $param_hash{'2to1'}=1; next; } if($_ eq '--removeAllTags'){ $param_hash{'removeAllTags'}=1; next; } if($_ eq '-t' || $_ eq '--test'){ $param_hash{'test'}=1; next; } if($_=~/^--v(erbose)?=([0123])$/){ $param_hash{'verbose'}=$2; next; } if($_ eq '-V' || $_ eq '--version'){ $param_hash{'version'}=1; next; } if($_=~/^--(T[A-Z0-9]{3})=(.*)$/){ $param_hash{$1}=$2; }else{ $syntax_correct=0; last; } } } $syntax_correct=0 if $param_hash{'2to1'}==1 && $param_hash{'1to2'}==1; $syntax_correct=0 if @params==0 && defined $param_hash{'filesRE'} && $param_hash{'filesRE'}=~/^--?h(elp)?$/; if($param_hash{'version'}){ my $version_info='mod_id3.pl '.$version."\n".' this program is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose. originally written by seth (for e-mail-address see http://www.wg-karlsruhe.de/seth/email_address.php).'."\n"; die $version_info; }else{ $syntax_correct || die $usage; } return %param_hash; } sub print_id3_tags{ my $file=shift; my $mp3=MP3::Tag->new($file); $mp3->get_tags(); if(exists $mp3->{ID3v1}){ my @tagdata_v1=$mp3->{ID3v1}->all; print ' id3v1: ('.$tagdata_v1[2].') '.$tagdata_v1[5].'. '.$tagdata_v1[1].' - '.$tagdata_v1[0].' ('.$tagdata_v1[3].")\n"; print ' '.$tagdata_v1[6].'; '.$tagdata_v1[4]."\n" if($tagdata_v1[4].$tagdata_v1[6] ne ''); } if(exists $mp3->{ID3v2}){ my @tagdata_v2=('', '', '', '', '', '', ''); my $frameIDs_hash=$mp3->{ID3v2}->get_frame_ids; foreach(keys %$frameIDs_hash){ my ($cont, $name)=$mp3->{ID3v2}->get_frame($_); if(ref $cont){ while(my ($key,$val)=each %$cont){ push(@tagdata_v2, [($_, $key, $val)]) if $val ne '' && substr($key,0,1) ne '_'; } }else{ if($_ eq 'TIT2'){ $tagdata_v2[0]=$cont; }elsif($_ eq 'TPE1'){ $tagdata_v2[1]=$cont; }elsif($_ eq 'TALB'){ $tagdata_v2[2]=$cont; }elsif($_ eq 'TYER'){ $tagdata_v2[3]=$cont; }elsif($_ eq 'TRCK'){ $tagdata_v2[5]=$cont; }elsif($_ eq 'TCON'){ $tagdata_v2[6]=$cont; }else{ push(@tagdata_v2, [($_, $name, $cont)]) if $cont ne '' && substr($name,0,1) ne '_'; } } } print ' id3v2: ('.$tagdata_v2[2].') '.$tagdata_v2[5].'. '.$tagdata_v2[1].' - '.$tagdata_v2[0].' ('.$tagdata_v2[3].")\n"; print ' '; print $tagdata_v2[6].'; ' if $tagdata_v2[6] ne ''; for(my $i=7;$i<@tagdata_v2;++$i){ print $tagdata_v2[$i][1].' ('.$tagdata_v2[$i][0].')='.$tagdata_v2[$i][2].'; '; } print "\n"; } $mp3->close; } sub set_info_to_file{ my $file=shift; my @info; for(1..5){ push(@info, shift); } my %params=@_; die 'error 2078346: see code'."\n" if @_%2!=0; my $removeAllTags=$params{'removeAllTags'}; my $verbose=$params{'verbose'}; my $update=$params{'update'}; my $one_to_two=$params{'1to2'}; my $two_to_one=$params{'2to1'}; my $test=$params{'test'}; my $mp3=MP3::Tag->new($file); $mp3->get_tags() if $update==1; if($test==0 && $one_to_two==0){ if($update==0 || not exists $mp3->{ID3v1}){ $mp3->new_tag("ID3v1") if not exists $mp3->{ID3v1}; if($removeAllTags==1){ $mp3->{ID3v1}->remove_tag(); }else{ $mp3->{ID3v1}->all($info[0], $info[2], $info[3], $info[4], '', $info[1], ''); $mp3->{ID3v1}->write_tag; } }else{ # $update==1 && exists $mp3->{ID3v1} $mp3->{ID3v1}->title($info[0]) if($mp3->{ID3v1}->title eq ''); $mp3->{ID3v1}->track($info[1]) if($mp3->{ID3v1}->track eq ''); $mp3->{ID3v1}->artist($info[2]) if($mp3->{ID3v1}->artist eq ''); $mp3->{ID3v1}->album($info[3]) if($mp3->{ID3v1}->album eq ''); $mp3->{ID3v1}->year($info[4]) if($mp3->{ID3v1}->year eq ''); $mp3->{ID3v1}->write_tag; } } if($two_to_one==0){ if($update==0 || not exists $mp3->{ID3v2}){ if($test==0){ $mp3->new_tag("ID3v2") if not exists $mp3->{ID3v2}; $mp3->{ID3v2}->remove_tag() if($update==0); if($removeAllTags==0){ (defined $mp3->{ID3v2}->get_frame('TIT2'))? $mp3->{ID3v2}->change_frame('TIT2', $info[0]) : $mp3->{ID3v2}->add_frame('TIT2', $info[0]); (defined $mp3->{ID3v2}->get_frame('TRCK'))? $mp3->{ID3v2}->change_frame('TRCK', $info[1]) : $mp3->{ID3v2}->add_frame('TRCK', $info[1]); (defined $mp3->{ID3v2}->get_frame('TPE1'))? $mp3->{ID3v2}->change_frame('TPE1', $info[2]) : $mp3->{ID3v2}->add_frame('TPE1', $info[2]); if(exists $params{'TYER'}){ (defined $mp3->{ID3v2}->get_frame('TYER'))? $mp3->{ID3v2}->change_frame('TYER', $info[4]) : $mp3->{ID3v2}->add_frame('TYER', $info[4]); } (defined $mp3->{ID3v2}->get_frame('TALB'))? $mp3->{ID3v2}->change_frame('TALB', $info[3]) : $mp3->{ID3v2}->add_frame('TALB', $info[3]); $mp3->{ID3v2}->write_tag; } } }else{ # $update==1 && exists $mp3->{ID3v2} my $output=' forced : '; my $changed=1; if(defined $mp3->{ID3v2}->get_frame('TALB')){ if($mp3->{ID3v2}->get_frame('TALB') eq ''){ $mp3->{ID3v2}->change_frame('TALB', $info[3]) if($test==0); }else{ $changed=0; } }else{ $mp3->{ID3v2}->add_frame('TALB', $info[3]) if($test==0); } $output.='('.(($changed==1)?$info[3]:'[leave]').') '; $changed=1; if(defined $mp3->{ID3v2}->get_frame('TRCK')){ if($mp3->{ID3v2}->get_frame('TRCK') eq ''){ $mp3->{ID3v2}->change_frame('TRCK', $info[1]) if($test==0); }else{ $changed=0; } }else{ $mp3->{ID3v2}->add_frame('TRCK', $info[1]) if($test==0); } $output.=(($changed==1)?$info[1]:'[leave]').'. '; $changed=1; if(defined $mp3->{ID3v2}->get_frame('TPE1')){ if($mp3->{ID3v2}->get_frame('TPE1') eq ''){ $mp3->{ID3v2}->change_frame('TPE1', $info[2]) if($test==0); }else{ $changed=0; } }else{ $mp3->{ID3v2}->add_frame('TPE1', $info[2]) if($test==0); } $output.=(($changed==1)?$info[2]:'[leave]').' - '; $changed=1; if(defined $mp3->{ID3v2}->get_frame('TIT2')){ if($mp3->{ID3v2}->get_frame('TIT2') eq ''){ $mp3->{ID3v2}->change_frame('TIT2', $info[0]) if($test==0); }else{ $changed=0; } }else{ $mp3->{ID3v2}->add_frame('TIT2', $info[0]) if($test==0); } $output.=(($changed==1)?$info[0]:'[leave]').' '; $changed=1; if(exists $params{'TYER'}){ if(defined $mp3->{ID3v2}->get_frame('TYER')){ if($mp3->{ID3v2}->get_frame('TYER') eq ''){ $mp3->{ID3v2}->change_frame('TYER', $info[4]) if($test==0); }else{ $changed=0; } }else{ $mp3->{ID3v2}->add_frame('TYER', $info[4]) if($test==0); } } $output.='('.(($changed==1)?$info[4]:'[leave]').')'; print $output."\n" if($verbose>1); $mp3->{ID3v2}->write_tag if($test==0); } } $mp3->close; } sub get_info_from_file{ my $file=shift; my $verbose=shift; my $list_id3=shift; my $mp3=MP3::Tag->new($file); $mp3->config('autoinfo', @_); my @info=$mp3->autoinfo(); $info[4]=$info[5]; pop(@info); pop(@info); $info[1]='0'.$info[1] if(length($info[1])==1); $mp3->close; print_id3_tags($file) if($verbose>2 || ($verbose>0 && $list_id3)); return @info; } sub search_dir{ my $working_dir=shift; my %params=@_; my $filesRE=$params{'filesRE'}; my $recursively=$params{'recursively'}; my $removeAllTags=$params{'removeAllTags'}; my $verbose=$params{'verbose'}; my $one_to_two=$params{'1to2'}; my $two_to_one=$params{'2to1'}; my $update=$params{'update'}; my $list_id3=$params{'list_id3'}; my $entry; my @files; my @dirs; print "\n".' '.$working_dir.'/'."\n" if $verbose>1 && $list_id3==0; opendir(DIR, ".") || die $working_dir.": $!"; while(telldir(DIR)>=0){ $entry=readdir(DIR); if(-d $entry){ push(@dirs, $entry) }else{ if($entry=~/$filesRE/){ print "\n".' '.$working_dir.'/'."\n" if(($verbose>1 && $list_id3==1) || ($verbose==1 && @files==0 && $list_id3==0)); print 'change: '.$entry."\n" if $verbose>0 && $list_id3==0; push(@files, $entry); }else{ print ' skip: '.$entry."\n" if $verbose>1 && $list_id3==0; } } } closedir(DIR); @files=sort(@files); @dirs=sort(@dirs); my @info; my @resources=('filename', 'ID3v2', 'ID3v1'); # 'ID3v2', 'ID3v1', 'filename' @resources=('ID3v1') if $one_to_two==1; @resources=('ID3v2') if $two_to_one==1; foreach(@files){ @info=(); if($removeAllTags==0){ @info=get_info_from_file($working_dir.'\\'.$_, $verbose, $list_id3, @resources); die('@info too small or to large.') if @info!=5; print ' found : ('.$info[3].') '.$info[1].'. '.$info[2].' - '.$info[0].' ('.$info[4].')'."\n" if($verbose>1 && $list_id3==0 && $update==0); $info[0]=$params{'TIT2'} if exists $params{'TIT2'}; $info[1]=$params{'TRCK'} if exists $params{'TRCK'}; $info[2]=$params{'TPE1'} if exists $params{'TPE1'}; $info[3]=$params{'TALB'} if exists $params{'TALB'}; $info[4]=$params{'TYER'} if exists $params{'TYER'}; print ' forced: ('.$info[3].') '.$info[1].'. '.$info[2].' - '.$info[0].' ('.$info[4].')'."\n" if($verbose>1 && $list_id3==0 && $update==0); }elsif($removeAllTags==1){ @info=('','','','',''); } set_info_to_file($working_dir.'\\'.$_, @info, %params) if $list_id3==0; } if($recursively==1){ foreach(@dirs){ if($_ ne '.' && $_ ne '..'){ chdir($_); search_dir($working_dir.'/'.$_, %params); chdir('..'); } } } } sub write_id3_tags_using_filenames{ my %params=syntaxCheck(@_); my $working_dir=cwd; search_dir($working_dir, %params); chdir($working_dir); } write_id3_tags_using_filenames(@ARGV);