#! /usr/bin/perl -w #-------------------------------------------------------------------------- # Query or set MP3 ID3v1 or Ogg Vorbis tag information #-------------------------------------------------------------------------- use strict; use File::Basename; use File::Copy; use File::Spec; use File::Temp qw/tempfile/; use FileHandle; use Getopt::Long; use MP3::Tag; use FindBin qw($RealBin); use lib "$RealBin/lib"; use SongInfo::MP3; use SongInfo::Song; # Debug level. 0: 0ff, 1: show args, 2: show args and then exit use constant DEBUG => 0; my %opts = ( tag => [], ); my (@inPaths, %tags, $cleanUp); my $basename = basename $0; my $usage = <-'); my @keys = sort $song->getKeys(); for my $key (@keys) { my @values = $song->getTag($key); for my $value (@values) { print $fh "$key=$value$/"; } } $song->close(); print $fh "\n" if (@inPaths > 1); } # Show ID3v2 tags for the file sub showV2Tags { my $mp3 = new MP3::Tag(shift); $mp3->get_tags(); if (exists $mp3->{ID3v2}) { my $id3v2 = $mp3->{ID3v2}; my $frameIDs_hash = $id3v2->get_frame_ids('truename'); foreach my $frame (keys %$frameIDs_hash) { my ($name, @info) = $id3v2->get_frame($frame); for my $info (@info) { # It's one of the frames with children if (ref $name) { print "$frame\n"; while(my ($key,$val)=each %$name) { print " $key: $val\n"; } } # It's one of the simple key/value type frames else { print "$info: $name\n"; } } } # foreach } # if $mp3->close(); print "\n" if (@inPaths > 1); } # Remove ID3v2 tags. sub killV2Tags { my ($inPath, $outPath) = @_; copy("$inPath", "$outPath") or die "Copy failed: $!"; my $mp3 = new MP3::Tag("$outPath"); $mp3->get_tags(); if (exists $mp3->{ID3v2}) { $mp3->{ID3v2}->remove_tag(); } $mp3->close(); } sub editTags { my ($inPath, $outPath) = @_; copy("$inPath", "$outPath") or die "Copy failed: $!"; my $song = new SongInfo::Song($outPath); # Arg w or e means get rid of all old tags if(($opts{write}) || ($opts{edit})) { $song->clear(); } # Lay the new tags in there. my $tagsChanged; foreach (keys %tags) { $tagsChanged = 1; if ($opts{modify}) { $song->editTag($_, $tags{$_}); } else { $song->addTag($_, $tags{$_}); } } $song->write() if($tagsChanged); $song->close(); } # Change existing tags to lowercase sub lcTags { my ($inPath, $outPath) = @_; copy("$inPath", "$outPath") or die "Copy failed: $!"; my $song = new SongInfo::Song($outPath); # Extract the tags, storing them with lowercase key names my %tags; for my $key ($song->getKeys()) { my @values = $song->getTag($key); for my $value (@values) { $tags{lc $key} = $value; } } # Get rid of all old tags $song->clear(); # Lay the new tags in there. foreach (keys %tags) { $song->addTag($_, $tags{$_}); } $song->write(); $song->close(); } sub loadCommentFile { my $commentPath = shift; open (COMMENT_FILE, "$commentPath") || die "Can't open $commentPath\n"; while () { # Get rid of the trailing newline. chop; /(.*?)=(.*)/; $tags{$1} = $2; } close COMMENT_FILE; } sub beforeNoOutfile { my $rInPath = shift; unless ($opts{out}) { $opts{out} = $$rInPath; $$rInPath .= ".mutag-tmp"; move($opts{out}, $$rInPath); $cleanUp = 1; } } sub afterNoOutfile { my $rInPath = shift; unlink ($$rInPath) if ($cleanUp); delete $opts{out}; } #-------------------------------------------------------------------------- # Parse args # No args at all $opts{help} = 1 unless @ARGV; Getopt::Long::Configure ("bundling"); GetOptions(\%opts, "id3v2|2", "append|a", "both|b", "commentfile|c=s", "edit|e", "genre|g", "kill|k", "list|l", "lowercase|L", "modify|m", "out|o=s", "tag|t=s", "write|w", "help|h", ) or die "\n$usage\n"; # User requested help die "$usage\n" if $opts{help}; # No switches, user wants --list behavior $opts{list} = 1 if (keys %opts < 2); # Parse tags input if ($opts{tag}) { for my $full (@{$opts{tag}}) { $full =~ /(.*)=(.*)/; $tags{$1} = $2; } } # Remaining args should be file paths @inPaths = @ARGV; if ((@inPaths > 1) && ($opts{out})) { die <tmpdir || '/tmp'; my ($fh, $filename) = tempfile(DIR => $tmpDir); my $timestamp = (stat $filename)[9]; # Make comment file showTags($inPath, $fh); # Edit file my $editor = $ENV{EDITOR} || 'vim'; system "$editor $filename"; # Figure out if file changed if ((stat $filename)[9] > $timestamp) { beforeNoOutfile(\$inPath); loadCommentFile($filename); editTags($inPath, $opts{out}); afterNoOutfile(\$inPath); } # Get rid of temp file close $fh; unlink $filename; } elsif($opts{kill}) { beforeNoOutfile(\$inPath); killV2Tags($inPath, $opts{out}); afterNoOutfile(\$inPath); } }