#!/usr/bin/perl -w
use Tk;
use Tk::JPEG;
use Tk::DialogBox;
use Tk::BrowseEntry;
use Tk::FBox;
use Tk::TextUndo;
#use POSIX;
use strict;
use MP3::Tag;
### TODO
#
# make all buttons alive
#
# view binary data (as hex dump)
#
# delete/add more than one frame at once
#
# better open function
#
# ...
#
# doing changes automatically to all selected files
# (like setting filename, applying Control-u to all filename,
# deleting all ID3v2 frames or whatsoever)
#
# Sorry, but I know that this is ugly code...
#
use vars qw/$tktag $VERSION %element %var/;
$VERSION="0.15";
$var{dir}= shift || "./"; #/mp3/unsorted";
$var{v2specent}={TIT2=>{name=>"Song"}, TPE1=>{name=>"Artist"},
TALB=>{name=>"Album"}};
$var{filter}="";
$var{fnformat} = "%a - %s.mp3";
$var{autoinfo} = ["ID3v2","ID3v1","filename"];
formatstr($var{fnformat});
$tktag = MainWindow->new(); #main window
create_window();
#&load_filelist;
&scan_dir;
&filter_and_show;
MainLoop;
exit 0;
####################################
###### tk subs
sub key_bindings {
$tktag->bind('<Control-U>'=>\&use_ucase);
$tktag->bind('<Control-u>'=>\&use_ucase_first);
$tktag->bind('<Control-l>'=>\&use_lcase);
$tktag->bind('<Control-space>'=>\&use_spaces);
$tktag->bind('<Control-S>'=>\&save);
$tktag->bind('<Control-f>'=>\&autosetfilename);
$tktag->bind('<Control-n>'=>
sub {
$element{filelist}->activate($element{filelist}
->index("active")+1);
&select; });
}
sub create_window {
my ($l);
&key_bindings;
$element{leftFrame} = $tktag->Frame();
$element{rightFrame} = $tktag->Frame();
$element{rightFrameMul} = $tktag->Frame();
# create menu
$element{menu} = create_menu($tktag);
# special fonts
$tktag->fontCreate('C_big', -family => 'courier', -weight => 'bold',
-size => 18);
# create Filelist
$element{filelist} = $element{leftFrame}->Scrolled("Listbox", -width=>25,
-selectmode=>"extended",
-scrollbars=>"osre",
-takefocus=>1,
-exportselection=>0);
$element{filelist}->bind("<Double-1>" => \&select);
$element{filelist}->bind("<Key-Return>" => \&select);
$var{simple}=1;
$element{filelist}->bind("<1>" => \&check_multi);
$element{filelist}->bind("<Control-a>" => [$element{filelist},
'selectionSet',"0","end"]);
my $filter=$element{leftFrame}->Frame();
my $cbline=$filter->Frame();
my $illine=$filter->Frame();
$cbline->Checkbutton(-text => 'ID3v1',
-variable => \$var{v1filter},
-command => \&filter_and_show,
-relief => 'flat')->pack(-side=>"left");
$cbline->Checkbutton(-text => 'ID3v2',
-variable => \$var{v2filter},
-command => \&filter_and_show,
-relief => 'flat')->pack(-side=>"left");
$illine->Checkbutton(-text => 'Inverse',
-variable => \$var{filter_inv},
-command => \&filter_and_show,
-relief => 'flat')->pack(-side=>"left");
$illine->Label(-textvariable=>\$var{visiblefiles}, -relief=>"sunken",
width=>5)->pack(-side=>"left", -anchor=>"e");
$cbline->pack(-side=>"top");
$l=$filter->Entry(-textvariable=>\$var{filter}, -width=>25)->pack(-side=>"top");
$l->bind("<FocusOut>" => \&filter_and_show);
$l->bind("<KeyPress-Return>" => \&filter_and_show);
$illine->pack(-side=>"top");
$element{rightFrameMul}->Label(-text=>"Options for automatic processing ".
"of serveral files")
->pack(-side=>"top", -expand=>1, -fill=>"x");
# create filename-area
my $fn1area = $element{rightFrame}->Frame(Name=>"fnframe");
my $fn2area = $element{rightFrame}->Frame();
$element{filenamelabel} = $fn1area->Label(-text=>"Filename:")
->pack(-side=>"left", anchor=>"w");
$var{bgcolor}=$element{filenamelabel}->cget("-background");
$l=$fn1area->Entry(-textvariable=>\$var{filename}, -validate=>"key",
-vcmd=>val_fn($element{filenamelabel}))
->pack(-side=>"left", -fill=>"x", -expand=>"yes", -anchor=>"w");
$fn1area->Button(-text=>"Save changes", -command=>\&save)
->pack(-side=>"left", -anchor=>"w");
$element{setfilename} = $fn2area->Menubutton(qw/-underline 0 -relief raised/,
-text => "Set Filename --",
-direction => "below");
$element{setfilename}->configure(-menu => $element{setfilename}->menu);
$element{setfilename}->command(-label => "from ID3v1 Tag",
-command=>[\&setfilename, "ID3v1"]);
$element{setfilename}->command(-label => "from ID3v2 Tag",
-command=>[\&setfilename, "ID3v2"]);
$element{setfilename}->pack(-side=>"left", anchor=>"n", -padx=>15);
$element{setid3v1} = $fn2area->Menubutton(qw/-underline 0 -relief raised/,
-text => "Set ID3v1 --",
-direction => "below");
$element{setid3v1}->configure(-menu => $element{setid3v1}->menu);
$element{setid3v1}->command(-label => "from Filename",
-command=>[\©fntov1]);
$element{setid3v1}->command(-label => "from ID3v2 Tag",
-command=>[\©v2tov1]);
$element{setid3v1}->pack(-side=>"left", anchor=>"n", -padx=>15);
$element{setid3v2} = $fn2area->Menubutton(qw/-underline 0 -relief raised/,
-text => "Set ID3v2 --",
-direction => "below");
$element{setid3v2}->configure(-menu => $element{setid3v2}->menu);
$element{setid3v2}->command(-label => "from Filename",
-command=>[\©fntov2]);
$element{setid3v2}->command(-label => "from ID3v1 Tag",
-command=>\©v1tov2);
$element{setid3v2}->pack(-side=>"left", anchor=>"n", -padx=>15);
$element{removebutton} = $fn2area->Menubutton(qw/-underline 0 -relief raised/,
-text => "Remove --",
-direction => "below");
$element{removebutton}->configure(-menu => $element{removebutton}->menu);
$element{removebutton}->command(-label => "File",
-command=>sub{
$var{filename}="";
$element{filenamelabel}
->configure(-background=>"red");});
$element{removebutton}->command(-label => "ID3v1 Tag",
-command=>sub{
if (exists $var{v1}) {
&remove_id3v1;
$element{v1caption}
->configure(-background=>"red");}});
$element{removebutton}->command(-label => "ID3v2 Tag",
-command=>sub{
if (exists $var{v2}) {
&remove_id3v2;
$element{v2caption}
->configure(-background=>"red");}});
$element{removebutton}->pack(-side=>"left", anchor=>"n", -padx=>15);
# create ID3v1 area
my $v1area = $element{rightFrame}->Frame(Name=>"v1frame");
my $v1caption = $v1area->Frame(-background=>"red");
my $v1labels = $v1area->Frame();
my $v1entries = $v1area->Frame();
my $v2apic = $v1area->Frame();
my $a=0;
$element{v1caption}=$v1caption->Label(-text=>"ID3v1.1", -font=>"C_big",
-background=>"red", -relief=>"ridge")
->pack(-side=>"top", -anchor=>"c", -expand=>"yes", -fill=>"x");
$l = $v1labels->Label(-text=>"Song:")->pack(-side=>"top", -anchor=>"e");
$v1entries->Entry(-textvariable=>\$var{v1song}, -width=>30,
-validate=>"key", -vcmd=>val_text($l,30))
->pack(-side=>"top", -anchor=>"w");
$l = $v1labels->Label(-text=>"Artist:")->pack(-side=>"top", -anchor=>"e",
-pady=>2);
$v1entries->Entry(-textvariable=>\$var{v1artist}, -width=>30,
-validate=>"key", -vcmd=>&val_text($l,30))
->pack(-side=>"top", -anchor=>"w");
$l = $v1labels->Label(-text=>"Album:")->pack(-side=>"top", -anchor=>"e");
$v1entries->Entry(-textvariable=>\$var{v1album}, -width=>30,
-validate=>"key", -vcmd=>&val_text($l,30))
->pack(-side=>"top", -anchor=>"w");
$l = $v1labels->Label(-text=>"Comment:")->pack(-side=>"top", -anchor=>"e",
-pady=>1);
$v1entries->Entry(-textvariable=>\$var{v1comment}, -width=>30,
-validate=>"key", -vcmd=>val_text($l,28))
->pack(-side=>"top", -anchor=>"w");
$element{genrelabel} = $v1labels->Label(-text=>"Genre:")->pack(-side=>"top",
-anchor=>"e");
my $genres = ["", sort @{MP3::Tag::genres()}];
$v1entries->BrowseEntry(-variable => \$var{v1genre}, -choices => $genres,
-state=>"readonly", -browsecmd=>
sub { $element{genrelabel}
->configure(-background=>"yellow");
$element{v1caption}
->configure(-background=>"yellow");
push @{$var{labels}}, $element{genrelabel} })
->pack(-side=>"top", -anchor=>"w");
$l = $v1labels->Label(-text=>"Year:")->pack(-side=>"top", -anchor=>"e");
$v1entries->Entry(-textvariable=>\$var{v1year}, -width=>4,
-validate=>"key", -vcmd=>val_num($l,4))
->pack(-side=>"top", -anchor=>"w");
$l = $v1labels->Label(-text=>"Track:")->pack(-side=>"top", -anchor=>"e",
-pady=>2);
$v1entries->Entry(-textvariable=>\$var{v1track}, -width=>4,
-validate=>"key", -vcmd=>val_num($l,3))
->pack(-side=>"top", -anchor=>"w");
$element{apic} = $v2apic->Photo('apic');
$element{apic}->blank;
$v2apic->Label(-image=>'apic', -height=>150, -width=>180)
->pack(-side=>"top", -anchor=>"center");
$element{apictext}=$v2apic->Label(-text=>"")->pack(-side=>"top",
-anchor=>"center");
$v1caption->pack(-side=>"top", -expand=>"yes", -fill=>"x");
$v1labels->pack(-side=>"left");
$v1entries->pack(-side=>"left");
$v2apic->pack(-side=>"right");
# ID3v2 area
my $v2area = $element{rightFrame}->Frame(Name=>"v2frame");
my $v2caption = $v2area->Frame();
my $v2top = $v2area->Frame();
my $v2right = $v2area->Frame();
my $v2buttons = $v2top->Frame();
my $v2labent = $v2top->Frame();
my $v2labels = $v2labent->Frame();
my $v2entries = $v2labent->Frame();
my $v2parea = $v2right->Frame();
my $v2iarea = $v2right->Frame();
$element{v2caption}=$v2caption->Label(-text=>"ID3v2.3", -font=>"C_big",
-background=>"red", -relief=>"ridge")
->pack(-side=>"top", -anchor=>"c", -fill=>"x", -expand=>"yes");
while (my ($fname,$val) = each %{$var{v2specent}}) {
$val->{label} = $v2labels->Label(-text=>$val->{name})->pack(-side=>"top",
-anchor=>"e");
$val->{entry} = $v2entries->Entry(-textvariable=>\$var{"v2$fname"},
-width=>40, -validate=>"key",
-vcmd=>[\&val_v2,$element{v2caption},
$val->{label}, $a])
->pack(-side=>"top", -anchor=>"w");
}
$v2buttons->Button(-text=>"Add Frame", -command=>\&add_frame)
->pack(-side=>"left", -anchor=>"c");
$v2buttons->Button(-text=>"Delete Frame(s)", -command=>\&del_frame)
->pack(-side=>"left", -anchor=>"c");
$element{frames} = $v2iarea->Scrolled("Listbox", -scrollbars=>"re", -width=>"8",
-selectmode=>"browse", -takefocus=>1)
->pack(-side=>"left");
$element{frames}->bind("<Double-1>" => \&show_frame);
$element{frames}->bind("<Key-Return>" => \&show_frame);
$v2parea->Label(-textvariable=>\$var{longname})->pack(-side=>"top",
-anchor=>"n", -pady=>5);
$element{frameinfo} = $v2parea;
$v2labels->pack(-side=>"left");
$v2entries->pack(-side=>"left");
$v2labent->pack(-side=>"left", -anchor=>"nw");
$v2buttons->pack(-side=>"left", -fill=>"y", -expand=>"yes");
$v2iarea->pack(-side=>"left", -anchor=>"nw");
$v2parea->pack(-side=>"left", -fill=>"both", -expand=>"yes", -anchor=>"nw");
$v2caption->pack(-side=>"top", -expand=>"yes", -fill=>"x");
$v2top->pack(-side=>"top", -fill=>"x", -expand=>"yes", -anchor=>"n");
$v2right->pack(-side=>"left", -fill=>"both", -expand=>"yes", -anchor=>"n");
# pack left Frame
$element{filelist}->pack(-side=>"top", -fill=>"y", -expand=>"yes");
$filter->pack(-side=>"bottom");
# pack right Frame
$fn1area->pack(-side=>"top", -anchor=>"nw", -expand=>"yes", -fill=>"x");
$fn2area->pack(-side=>"top", -anchor=>"nw", -expand=>"yes", -fill=>"x");
$v1area->pack(-side=>"top", -anchor=>"nw", -expand=>"yes", -fill=>"x",
-pady=>10);
$v2area->pack(-side=>"top", -anchor=>"nw", -expand=>"yes", -fill=>"x");
$v2parea->pack(-side=>"top", -anchor=>"nw", -expand=>"yes", -fill=>"x");
# pack main window
$element{menu}->pack(-side=>"top", -fill=>"x", -anchor=>"nw");
$element{leftFrame}->pack(-side=>"left", -fill=>"y", -anchor=>"w");
$element{rightFrame}->pack(-side=>"left",-fill=>"both",-expand=>"yes",
-anchor=>"nw");
#prepare open dialog window
$element{open} = $tktag->DialogBox(-title=>"Open directory",
-buttons=>["Open", "Cancel"]);
$element{open}->add("Label", -text=>"Directory:")->pack();
$element{open}->add("Entry", -width=>35, -textvariable=>\$var{dir})->pack();
$element{fnformat} = $tktag->DialogBox(-title=>"Set filename format",
-buttons=>["Set", "Cancel"]);
$element{fnformat}->add("Label", -text=>"Format")->pack();
$element{fnformat}->add("Entry", -width=>35, -textvariable=>\$var{fnformat})
->pack();
$element{fnformat}->add("Label",
-text=>"%s - Song %l - Album\n%a - Artist".
" %t - Track\n %c - Comment ".
"%g - Genre\n %y - Year".
"\nSee also README file")->pack();
$element{addelframe} = $tktag->DialogBox(-buttons=>["Ok", "Cancel"]);
$element{addelabel} = $element{addelframe}->add("Label")
->pack();
$element{addelistbox} = $element{addelframe}->add("Scrolled", "Listbox",
-scrollbars=>"osre", -width=>50)
->pack(-fill=>"both", -expand=>1);
}
sub val_v2 {
my ($label1, $label2, $change) = @_;
if ($_[7] == -1) {
$change=0;
return 1;
}
return 1 if $change++;
$label1->configure(-background=>"yellow");
if (defined $label2) {
$label2->configure(-background=>"yellow");
push @{$var{labels}}, $label2;
}
return 1;
}
sub val_fn {
my ($label1, $change) = @_;
return
sub {
if ($_[4] == -1) {
$change=0;
return 1;
}
return 1 if $change++;
$label1->configure(-background=>"yellow");
return 1;
}
}
sub val_text {
my ($label, $length, $change) = @_;
return
sub {
my ($pro, undef, undef, undef, $type) = @_;
return 0 if $type == 1 && length($pro) > $length;
if ($type == -1) {
$change=0;
return 1;
}
return 1 if $change++;
$label->configure(-background=>"yellow");
$element{v1caption}->configure(-background=>"yellow");
push @{$var{labels}}, $label;
return 1;
}
}
sub val_num {
my ($label, $length, $change) = @_;
return
sub {
my ($pro, undef, undef, undef, $type) = @_;
return 0 if $type == 1 && !($pro =~ /^\d{0,$length}$/o);
if ($type == -1) {
$change=0;
return 1;
}
return 1 if $change++;
$label->configure(-background=>"yellow");
$element{v1caption}->configure(-background=>"yellow");
push @{$var{labels}}, $label;
return 1;
}
}
sub create_menu {
my $menu = shift->Menu(-type=>"menubar");
my $file=$menu->cascade(-label=>"File");
$file->command(-label=>"Open", -command=>\&load_filelist);
$file->separator();
$file->command(-label=>"Exit", -command=>sub {exit 0;});
my $opts=$menu->cascade(-label=>"Options");
$opts->command(-label=>"Filename format", -command=>\&filename_format);
return $menu;
}
sub tk_question {
my $text = shift;
my $ans = $tktag->messageBox(-text=>$text, -title=>"Question", -type=>"YesNo",
-default=>"No", -icon=>"question" );
return $ans eq "Yes" ? 1 : 0;
}
sub tk_warning {
my $text = shift;
$tktag->messageBox(-text=>$text, -title=>"Warning", -buttons=>"Ok",
-icon=>"warning" );
}
####### file subs
sub load_filelist {
$element{open}->configure(-title=>"Open directory");
my $answer = $element{open}->Show;
if ($answer eq "Open") {
$var{dir} =~ s!/$!!;
$var{dir} .= "/";
&scan_dir;
&filter_and_show;
}
}
sub scan_dir {
delete $var{files};
opendir(DIR, $var{dir}) or return;
$tktag->Busy;
$tktag->update;
my $file;
while (defined($file = readdir(DIR))) {
if (-d $var{dir} . $file) {
$var{files}->{$file}={dir=>$var{dir}, isDir=>1};
} elsif (-f _) {
my $mp3=MP3::Tag->new($var{dir}.$file);
$var{files}->{$file}={dir=>$var{dir}};
foreach ($mp3->getTags) {
$var{files}->{$file}->{$_}=1;
}
}
}
closedir DIR;
$tktag->Unbusy;
}
sub filter_and_show {
my (@files, @dirs, $name, $val, $filter);
$element{filelist}->delete("0", "end");
if ($var{filter} ne "") {
$filter = qr/$var{filter}/i;
}
while (($name, $val)=each %{$var{files}}) {
if (exists $val->{isDir}) {
push @dirs, "[$name]";
} else {
if (!$var{v1filter} || (!$var{filter_inv} && $val->{ID3v1})
|| ($var{filter_inv} && !$val->{ID3v1})) {
if (!$var{v2filter} || (!$var{filter_inv} && $val->{ID3v2})
|| ($var{filter_inv} && !$val->{ID3v2})) {
if (!$filter || (!$var{filter_inv} && $name =~ $filter)
|| ($var{filter_inv} && !($name =~ $filter))) {
push @files, $name;
}
}
}
}
}
$element{filelist}->insert("end", sort @dirs);
$element{filelist}->insert("end", sort @files);
$var{visiblefiles}=$#files+1;
}
sub select {
my $active =$element{filelist}->get("active","active");
if ($active =~ /^\[(.*)\]$/) {
if (exists $var{files}->{$1}->{isDir}) {
$var{dir}.="$1/";
&scan_dir;
&filter_and_show;
return;
}
}
$var{filename}=$active;
$var{oldfilename} = $var{filename};
my $filename = $var{dir}.$var{filename};
$var{mp3}->close if exists $var{mp3};
delete $var{v1};
delete $var{v2};
$element{filenamelabel}->configure(-background=>$var{bgcolor});
$var{mp3} = MP3::Tag->new($filename);
$var{mp3}->getTags;
# remove old things
foreach (@{$var{fpack}}) {
$_->packForget();
}
$var{fpack} = [];
$var{longname}="";
if (exists $var{labels}) {
while (my $label = shift @{$var{labels}}) {
$label->configure(-background=>$var{bgcolor});
}
}
# set information of ID3v1 tag
if (exists $var{mp3}->{ID3v1}) {
$element{v1caption}->configure(-background=>"green");
$var{v1}=$var{mp3}->{ID3v1};
($var{v1song},$var{v1artist},$var{v1album},$var{v1year},$var{v1comment},$var{v1track},$var{v1genre})
= $var{v1}->all();
} else {
($var{v1song},$var{v1artist},$var{v1album},$var{v1year},$var{v1comment},$var{v1track},$var{v1genre})
= ("","","","","","","");
$element{v1caption}->configure(-background=>"red");
}
#set information of ID3v2 tag
if (exists $var{mp3}->{ID3v2}) {
$element{v2caption}->configure(-background=>"green");
$var{v2}=$var{mp3}->{ID3v2};
my $frames = $var{v2}->getFrameIDs;
while (my ($fname, $val) = each %{$var{v2specent}}) {
if (exists $frames->{$fname}) {
$var{"v2$fname"} = $var{v2}->getFrame($fname);
$val->{entry}->bind('<FocusOut>' => v2_specent_change($fname));
} else {
$var{"v2$fname"} = "";
$val->{entry}->bind('<FocusOut>' => v2_specent_create($fname));
}
}
if (exists $frames->{"APIC"}) {
show_picture("APIC");
} else {
$element{apic}->blank;
$element{apictext}->configure(-text=>"");
}
my @frames = sort keys %$frames;
$element{frames}->delete("0","end");
$element{frames}->insert("end", @frames);
show_frame($frames[0],1);
} else {
while (my ($fname, $val) = each %{$var{v2specent}}) {
$var{"v2$fname"}="";
$val->{entry}->bind('<FocusOut>' => v2_specent_create($fname));
}
$element{v2caption}->configure(-background=>"red");
$element{frames}->delete("0","end");
$element{apic}->blank;
$element{apictext}->configure(-text=>"");
}
}
sub v2_specent_change {
my $fname = shift;
return sub {
$var{v2}->change_frame($fname, $var{"v2$fname"});
if (exists $var{current_frame} && $var{current_frame} eq $fname) {
$var{frame_Text}->delete("0.1", "end");
$var{frame_Text}->insert("0.1", $var{"v2$fname"});
}
};
}
sub v2_specent_create {
my $fname = shift;
return sub {
return if $var{"v2$fname"} eq "";
$var{v2}=$var{mp3}->newTag("ID3v2") unless exists $var{v2};
$var{v2}->add_frame($fname, $var{"v2$fname"});
my @allframes = $element{frames}->get(0,"end");
push @allframes, $fname;
$element{frames}->delete(0,"end");
$element{frames}->insert("end", sort @allframes);
$var{v2specent}->{$fname}->{entry}->bind('<FocusOut>' =>
v2_specent_change($fname));
};
}
sub show_frame {
my ($fname, $tagchanged) = @_;
if (ref $fname) { # called from listbox-bind
$fname = $fname->get("active");
}
save_frameinfo() unless defined $tagchanged;
# delete last info
foreach (@{$var{fpack}}) {
$_->packForget();
}
$var{fpack} = [];
$var{current_frame}="";
return unless length($fname) == 4 || length($fname) == 6;
my $info;
$var{current_frame} = $fname;
($info, $var{longname}) = $var{v2}->getFrame($fname);
(undef, $var{restrinp}) = $var{v2}->what_data($fname);
if (ref $info) {
my ($key, $value);
foreach $key (sort keys %$info) {
$value = $info->{$key};
my $f = $element{frameinfo}->Frame();
push @{$var{fpack}}, $f;
if ($key =~ s/^_//) {
my $l=$f->Label(-text=>"$key:", -justify=>"left")
->pack(-side=>"left", -anchor=>"w");
my $b=$f->Menubutton(qw/-underline 0 -relief raised/,
-text => "Data of " .length($value) .
" Bytes --", -direction => "below");
$b->configure(-menu => $b->menu);
$var{"data_$key"}=$value;
$b->command(-label => "Save Data", -command=>[\&save_data,$key]);
$b->command(-label => "Load Data", -command=>[\&load_data,$key,$b, $l]);
$b->command(-label => "View Data", -command=>[\&view_data,$key]);
$b->pack(-side=>"left", anchor=>"n");
} else {
my $l=$f->Label(-text=>"$key:", -justify=>"left")
->pack(-side=>"left", -anchor=>"w");
if (exists $var{restrinp}->{$key}) {
my $state="readonly";
if (exists $var{restrinp}->{$key}->{"_FREE"}) {
$state="normal";
delete $var{restrinp}->{$key}->{"_FREE"};
}
$var{"frame_$key"}=$value;
my @inps=sort keys %{$var{restrinp}->{$key}};
$f->BrowseEntry(-variable => \$var{"frame_$key"}, -choices => \@inps,
-state=>$state, -width=>37, -browsecmd=>
sub { $l->configure(-background=>"yellow");
$element{v2caption}
->configure(-background=>"yellow");})
->pack(-side=>"right", -anchor=>"e");
} else {
$var{"frame_$key"} = $f->TextUndo(-height=>2, -width=>40, -wrap=>"word")
->pack(-side=>"right", -anchor=>"e");
$var{"frame_$key"}->insert("0.0", $value);
$var{"frame_$key"}->bind('<Double-1>' =>
sub{ $var{"frame_$key"}
->configure(-disable=>1);});
push @{$var{fpack}}, $var{"frame_$key"};
$var{"frame_$key"}->bind('<Key>'=>[\&yyy, $l]);
}
}
$f->pack(-side=>"top", -anchor=>"w", -fill=>"x", -expand=>"yes");
}
} else {
if (exists $var{restrinp}->{Text}) {
my $state="readonly";
if (exists $var{restrinp}->{Text}->{"_FREE"}) {
$state="normal";
delete $var{restrinp}->{Text}->{"_FREE"};
}
$var{"frame_Text"}=$info;
my @inps=sort keys %{$var{restrinp}->{Text}};
push @{$var{fpack}},$element{frameinfo}->BrowseEntry(
-variable => \$var{"frame_Text"},
-choices => \@inps, -width=>37,
-state=>$state, -browsecmd=>
sub { $element{v2caption}
->configure(-background=>"yellow")})
->pack(-side=>"top", -anchor=>"n");
} else {
$var{frame_Text} = $element{frameinfo}->TextUndo(-height=>2, -width=>40,
-wrap=>"word")
->pack(-side=>"top", -anchor=>"n");
$var{frame_Text}->insert("0.0", $info);
push @{$var{fpack}}, $var{frame_Text};
if (exists $var{v2specent}->{$fname}) {
$var{frame_Text}->bind('<Key>'=>[\&xxx, $fname]);
}
}
}
}
sub save_frameinfo {
# save last info
return unless exists $var{current_frame};
my ($format, $resinp) = $var{v2}->what_data($var{current_frame});
my @data=();
foreach (@$format) {
if (/^_/) {
push @data, $var{"data$_"};
next;
}
my $d;
if ($var{"frame_$_"} =~ /Text/) {
$d= $var{"frame_$_"}->get("0.1","end");
} else {
$d= $var{"frame_$_"};
$d= $resinp->{$_}->{$d} if (defined $resinp->{$_}->{$d});
};
chomp $d;
$d =~ s/ +$//;
push @data, $d;
}
$var{v2}->change_frame($var{current_frame}, @data);
}
sub xxx {
my ($textobject, $fname) = @_;
my $text = $textobject->get("0.1","end");
chomp($text);
$var{"v2$fname"} = $text;
$var{v2specent}->{$fname}->{label}->configure(-background=>"yellow");
$element{v2caption}->configure(-background=>"yellow");
}
sub yyy {
$_[1]->configure(-background=>"yellow");
$element{v2caption}->configure(-background=>"yellow");
}
sub show_picture {
my $fname = shift;
my $frame = $var{v2}->getFrame($fname);
if ($frame->{"MIME type"} =~ /image\/(gif|jpe?g|bmp)/i) {
my $type = lc $1;
$type =~ s/jpg/jpeg/;
open(TMP, ">/tmp/_tk_temp.jpg");
binmode TMP;
print TMP $frame->{_Data};
close TMP;
$element{apic}->configure(-format=>$1, -file=>"/tmp/_tk_temp.jpg");
my $text .= $frame->{"Picture Type"};
$text = $frame->{Description} if $text eq "Other";
$text = "ID3v2.3-" . uc($fname) . ": ". $text;
$element{apictext}->configure(-text=>$text);
unlink "/tmp/_tk_temp.jpg";
}
}
sub save {
# save changes to filename
if ($var{filename} ne $var{oldfilename}) {
return 0 unless change_filename();
}
$element{filenamelabel}->configure(-background=>$var{bgcolor});
#save changes of ID3v1 tag
if ($element{v1caption}->cget("-background") eq "yellow") {
my @fields;
for (qw/v1song v1artist v1album v1year v1comment v1track v1genre/) {
push @fields, $var{$_};
}
$var{v1}=$var{mp3}->newTag("ID3v1") unless exists $var{v1};
$var{v1}->all(@fields);
$var{v1}->writeTag;
$var{v1genre}=$var{v1}->genre();
$element{v1caption}->configure(-background=>"green");
} elsif (exists $var{v1} && $element{v1caption}->cget("-background") eq "red") {
$var{v1}->removeTag;
delete $var{v1};
}
#save changes of ID3v2 tag
if ($element{v2caption}->cget("-background") eq "yellow") {
$var{v2}=$var{mp3}->newTag("ID3v2") unless exists $var{v2};
save_frameinfo();
while (my ($fname, $val)=each %{$var{v2specent}}) {
if ($var{"v2$fname"} ne "" &&
$val->{label}->cget("-background") eq "yellow") {
my $d=$var{"v2$fname"};
$d=~s/ +$//;
if (defined $var{v2}->getFrame($fname)) {
$var{v2}->change_frame($fname, $d);
} else {
$var{v2}->add_frame($fname, $d);
}
$val->{label}->configure(-background=>$var{bgcolor});
}
}
$var{v2}->write_tag;
$element{v2caption}->configure(-background=>"green");
foreach my $v2e ( @{$var{fpack}}) {
foreach ($v2e->children) {
$_->configure(-background=>$var{bgcolor}) if /Label/;
}
}
} elsif (exists $var{v2} &&
$element{v2caption}->cget("-background") eq "red") {
$var{v2}->remove_tag;
delete $var{v2};
}
if (exists $var{labels}) {
while (my $label = shift @{$var{labels}}) {
$label->configure(-background=>$var{bgcolor});
}
}
}
sub change_filename {
my $success = 0;
if ($var{filename} eq "") {
if (tk_question("Filename is empty. Do you want to ".
"delete $var{oldfilename}?")) {
$var{mp3}->close;
if (unlink $var{dir}.$var{oldfilename}) {
remove_id3v1();
remove_id3v2();
$element{v1caption}->configure(-background=>"red");
$element{v2caption}->configure(-background=>"red");
$element{filelist}->delete("active","active");
delete $var{files}->{$var{oldfilename}};
$success = 1;
} else {
tk_warning("Cannot delete file.");
}
}
} elsif (-e $var{dir}.$var{filename}) {
if (-f _) {
if (tk_question("Files $var{filename} exists. Do you want".
" to overwrite it?")) {
goto re;
}
} else {
tk_warning("$var{filename} exists and it isn't a plain file.".
" Can't rename $var{oldfilename}!");
}
} else {
re:
if (rename($var{dir}.$var{oldfilename}, $var{dir}.$var{filename}) ||
(system("mv", $var{dir}.$var{oldfilename}, $var{dir}.$var{filename})==0)) {
$var{files}->{$var{filename}}=$var{files}->{$var{oldfilename}};
delete $var{files}->{$var{oldfilename}};
$var{oldfilename} = $var{filename};
my $index=$element{filelist}->index("active");
filter_and_show;
$element{filelist}->see($index);
$element{filelist}->activate($index);
$var{mp3}->{filename}=$var{dir}.$var{filename};
$success = 1;
} else {
tk_warning("Couldn't rename $var{oldfilename} to $var{filename}");
}
}
return $success;
}
sub remove_id3v1 {
for (qw/v1song v1artist v1album v1year v1comment v1track v1genre/) {
$var{$_} = "";
}
}
sub remove_id3v2 {
for (keys %{$var{v2specent}}) {
$var{"v2$_"} = "";
}
$element{frames}->delete("0","end");
foreach (@{$var{fpack}}) {
$_->packForget();
}
$var{fpack} = [];
$var{longname}="";
}
sub add_frame {
return unless exists $var{mp3};
$element{addelframe}->configure(-title=>"Add Frame");
$element{addelabel}->configure(-text=>"Select a frame to add:");
$element{addelistbox}->delete("0","end");
$var{v2}=$var{mp3}->newTag("ID3v2") unless exists $var{v2};
my $list = $var{v2}->supported_frames;
my @list = map {"$_ - $list->{$_}"} sort keys %$list;
$element{addelistbox}->insert("end",@list);
if ($element{addelframe}->Show() eq "Ok") {
$element{v2caption}->configure(-background=>"yellow");
my $fname= substr $element{addelistbox}->get("active","active"),0,4;
$fname = $var{v2}->add_frame($fname);
my $index=0;
my $maxindex = $element{frames}->index("end");
while ($index<$maxindex && $element{frames}->get($index,$index) lt $fname) {
$index++;
}
$element{frames}->insert($index, $fname);
$element{frames}->activate($index);
$element{frames}->selectionSet($index);
show_frame($fname);
}
}
sub del_frame {
return unless exists $var{v2};
$element{addelframe}->configure(-title=>"Delete Frame");
$element{addelabel}->configure(-text=>"Select a frame to delete:");
$element{addelistbox}->delete("0","end");
my $list = $var{v2}->getFrameIDs;
my @list = map {"$_ - $list->{$_}"} sort keys %$list;
$element{addelistbox}->insert("end",@list);
if ($element{addelframe}->Show() eq "Ok") {
$element{v2caption}->configure(-background=>"yellow");
my $fname= $element{addelistbox}->get("active","active");
$fname =~ s/^([A-Z0-9]+) .*/$1/;
$var{v2}->remove_frame($fname);
my $index=0;
my $maxindex = $element{frames}->index("end");
while ($index<$maxindex && $element{frames}->get($index,$index) ne $fname) {
$index++;
}
$element{frames}->delete($index,$index);
if ($var{current_frame} eq $fname) {
show_frame($element{frames}->get("active"),1);
}
if (exists $var{v2specent}->{$fname}) {
$var{"v2$fname"}="";
$var{v2specent}->{$fname}->{label}->configure(-background=>"yellow");
}
}
}
sub check_multi {
my ($h,$w,%info);
my @sel = $element{filelist}->curselection();
if ($#sel >0 && $var{simple}) {
$element{rightFrame}->packForget;
$element{rightFrameMul}->pack(-side=>"left",-fill=>"both",
-expand=>"yes", -anchor=>"nw");
$var{simple}=0;
} elsif ($#sel == 0 && ! $var{simple}) {
$element{rightFrameMul}->packForget;
$element{rightFrame}->pack(-side=>"left",-fill=>"both",
-expand=>"yes", -anchor=>"nw");
$var{simple}=1;
}
}
sub setfilename {
my $tag = shift; # may be 'ID3v1', 'ID3v2' or 'filename'
$tag =~ s/ID3//;
return undef unless exists $var{$tag} || $tag eq "filename";
my $new = $var{setfilename}->{stencil};
my $i=0;
foreach my $part (@{$var{setfilename}->{details}}) {
my $code=$part->{tag};
if ($tag eq "v2") {
$code =~ s/song/TIT2/ ||
$code =~ s/album/TALB/ ||
$code =~ s/artist/TPE1/;
}
my $txt="";
if ($tag eq "filename") {
if ($code eq "song" || $code eq "artist" ||
$code eq "album" || $code eq "track") {
$txt = $var{mp3}->{filename}->$code($var{dir}.$var{filename});
}
} else {
$txt = $var{"$tag$code"};
}
$txt =~ s/ *$//;
$txt = substr $txt, 0, $part->{length} if exists $part->{length} &&
((! exists $part->{fill}) || exists $part->{precise} );
$txt = $part->{fill} x ($part->{length}-length($txt)) . $txt
if exists $part->{fill};
$new =~ s/%$i/$txt/;
$i++;
}
$new =~ s/ /_/g if exists $var{setfilename}->{nospaces};
$var{filename}=$new;
$element{filenamelabel}->configure(-background=>"yellow");
return 1;
}
sub formatstr {
my $format = shift;
my %tags = (s=>"song", a=>"artist", l=>"album", y=>"year",
g=>"genre", t=>"track");
my @fmt;
while ($format =~ /%([0-9]*)(?:(!)?:(.))?([salygt])/g) {
my $t;
$t->{length}=$1 if defined $1 && $1 ne "";
$t->{precise}=1 if defined $2 && $2 ne "";
$t->{fill}=$3 if defined $3 && $3 ne "";
$t->{tag} = $tags{$4} if defined $4 && $4 ne "";
push @fmt, $t if defined $4 && $4 ne "";
}
my $i=0;
$format =~ s/%([0-9]*)(?:(!)?:(.))?([salygt])/"%".$i++/eg;
$var{setfilename}->{stencil}=$format;
$var{setfilename}->{details}=\@fmt;
}
sub element_change {
my $element = shift;
if ($element->PathName =~/v1/) {
$element{v1caption}->configure(-background=>"yellow");
} elsif ($element->PathName =~/v2/) {
$element{v2caption}->configure(-background=>"yellow");
} elsif ($element->PathName =~/fn/) {
$element{filenamelabel}->configure(-background=>"yellow");
}
}
sub use_ucase {
my $element=shift;
if ($element =~ /Entry/) {
my $evar = $element->cget(-textvariable);
my $ovar = $$evar;
$$evar =~ tr/[a-z]/[A-Z]/;
$$evar =~ s/\.MP3$/.mp3/;
if ($ovar ne $$evar) {
element_change($element);
}
} elsif ($element =~ /Text/) {
my $text = $element->get("0.1","end");
chomp $text;
return if $text eq "";
if ($text =~ tr/[a-z]/[A-Z]/) {
element_change($element);
$element->delete("0.1","end");
$element->insert("end",$text);
}
}
}
sub use_lcase {
my $element=shift;
if ($element =~ /Entry/) {
my $evar = $element->cget(-textvariable);
return if $$evar eq "";
if ($$evar =~ tr/[A-Z]/[a-z]/) {
element_change($element);
}
} elsif ($element =~ /Text/) {
my $text = $element->get("0.1","end");
chomp $text;
return if $text eq "";
if ($text =~ tr/[A-Z]/[a-z]/) {
element_change($element);
$element->delete("0.1","end");
$element->insert("end",$text);
}
}
}
sub use_ucase_first {
my $element=shift;
if ($element =~ /Entry/) {
my $evar = $element->cget(-textvariable);
return if $$evar eq "";
my $ovar=$$evar;
$$evar=ucase_first($$evar);
if ($ovar ne $$evar) {
element_change($element);
}
} elsif ($element =~ /Text/) {
my $text = $element->get("0.1","end");
chomp $text;
return if $text eq "";
my $otext = ucase_first($text);
if ($otext ne $text) {
$element->delete("0.1","end");
$element->insert("end",$otext);
element_change($element);
}
}
}
sub ucase_first {
my $text=shift;
$text =~ s/(\w+)/\L\u$1/gm;
$text =~ s/('[A-Z])/\L$1/g; #');
$text =~ s/\.Mp3$/.mp3/;
return $text;
}
sub use_spaces {
my $element=shift;
if ($element =~ /Entry/) {
my $evar = $element->cget(-textvariable);
return if $$evar eq "";
my $ovar=$$evar;
$$evar=spaces($$evar);
if ($ovar ne $$evar) {
element_change($element);
}
}
}
sub spaces {
my $text=shift;
$text =~ s/%20/_/g;
if ((($text =~ tr/././) >2)) {
$text =~ s/\./ /g;
$text =~ s/ (mp3)$/.$1/i;
$text =~ s/(\w)-(\w)/$1 - $2/g;
} elsif ($text =~ /_/) {
$text =~ s/_/ /g;
$text =~ s/(\w)-(\w)/$1 - $2/g;
} else {
$text =~ s/ /_/g;
$text =~ s/_-_/-/g;
}
$text=~ s/ +/ /g;
return $text;
}
sub filename_format {
if ( $element{fnformat}->Show eq "Set") {
formatstr($var{fnformat});
}
}
sub view_data {
my $key= shift;
warn "Viewing of $key of $var{current_frame} not supported yet\n";
}
sub load_data {
my ($key, $button,$label)= @_;
my @types=(["All files", "*"]);
my $file = $tktag->getOpenFile(-filetypes => \@types);
if (open (LOAD, $file)) {
binmode LOAD;
local $/;
undef $/;
$var{"data_$key"}=<LOAD>;
close LOAD;
warn "Please set mime type according to new data\n";
$button->configure(-text => "Data of " .length($var{"data_$key"}) .
" Bytes --");
$label->configure(-background=>"yellow");
$element{v2caption}->configure(-background=>"yellow");
} else {
warn "Couldn't open $file for saving $var{current_frame}.$key";
}
}
sub save_data {
my $key= shift;
my @types=(["All files", "*"]);
my $file = $tktag->getSaveFile(-filetypes => \@types,
-initialfile => 'Untitled',
-defaultextension => '.dat');
if (open (SAVE, ">$file")) {
binmode SAVE;
print SAVE $var{"data_$key"};
close SAVE;
} else {
warn "Couldn't open $file for saving $var{current_frame}.$key";
}
}
sub copyv1tov2 {
my %v1tov2 = (artist=>"TPE1", album=>"TALB", song=>"TIT2",
year=>"TYER", track=>"TRCK", comment=>"COMM", genre=>"TCON");
return unless exists $var{v1};
$var{v2}=$var{mp3}->newTag("ID3v2") unless exists $var{v2};
my $frames = $var{v2}->getFrameIDs;
while (my ($v1,$v2) = each %v1tov2) {
next if $var{"v1$v1"} eq "";
my ($data, $restr) = $var{v2}->what_data($v2);
my @newdata;
if ($#{$data}==0) {
@newdata = ($var{"v1$v1"});
} else {
foreach (@$data) {
if ($_ eq "Text") {
push @newdata, $var{"v1$v1"};
} else {
push @newdata, "";
}
}
}
if (exists $frames->{$v2}) {
$var{v2}->change_frame($v2, @newdata);
} else {
$var{v2}->add_frame($v2, @newdata);
add2framelist($v2);
}
if (exists $var{v2specent}->{$v2}) {
$var{"v2$v2"}=$var{"v1$v1"};
$var{v2specent}->{$v2}->{label}->configure(-background=>"yellow");
}
}
$element{v2caption}->configure(-background=>"yellow");
}
sub copyv2tov1 {
}
sub copyfntov1 {
$var{v1}=$var{mp3}->newTag("ID3v1") unless exists $var{v1};
foreach (qw/song artist album track/) {
$var{"v1$_"}=$var{mp3}->{filename}->$_($var{dir}.$var{filename});
}
}
sub copyfntov2 {
}
sub add2framelist {
my $fname = shift;
my $index=0;
my $maxindex = $element{frames}->index("end");
while ($index<$maxindex && $element{frames}->get($index,$index) lt $fname) {
$index++;
}
$element{frames}->insert($index, $fname);
$element{frames}->activate($index);
$element{frames}->selectionSet($index);
}
sub autosetfilename {
foreach (@{$var{autoinfo}}) {
last if setfilename($_);
}
}