7 #use File::Glob ':glob';
13 $version="Blueberry Armageddon 20041015";
17 $query='cdparanoia -d $device -Q 2>&1';
18 $rip='cdparanoia -ez -d $device $track -w $wavfile';
19 $encode='oggenc -q 4.9 -a {$metaartist} -t {$metatitle} -l {$metaalbum} $wavfile -o $outfile';
20 $playrip='cdparanoia -vZd $device $playstatus $pipe';
21 $play='play -t wav $pipe ';
23 $trackbase="/media/audio/albums";
27 print "Usage: Rip /dev/???\n\n";
33 my $toplevel=new MainWindow
(-class=>'MontyRip');
34 my $Xname=$toplevel->Class;
36 $toplevel->optionAdd("$Xname.background", "#3d84ff",20);
37 $toplevel->optionAdd("$Xname*highlightBackground", "#80c0d3",20);
38 $toplevel->optionAdd("$Xname.Panel.background", "#3d84ff",20);
39 $toplevel->optionAdd("$Xname.Panel.foreground", "#d0d0d0",20);
40 $toplevel->optionAdd("$Xname.Panel.font",
41 '-*-helvetica-bold-o-*-*-18-*-*-*-*-*-*-*',20);
42 $toplevel->optionAdd("$Xname*Statuslabel.font",
43 '-*-helvetica-bold-r-*-*-18-*-*-*-*-*-*-*',20);
44 $toplevel->optionAdd("$Xname*Statuslabel.foreground", "#606060");
45 $toplevel->optionAdd("$Xname*Status.font",
46 '-*-helvetica-bold-r-*-*-18-*-*-*-*-*-*-*',20);
48 $toplevel->optionAdd("$Xname*AlertDetail.font",
49 '-*-helvetica-medium-r-*-*-10-*-*-*-*-*-*-*',20);
52 $toplevel->optionAdd("$Xname*background", "#d0d0d0",20);
53 $toplevel->optionAdd("$Xname*foreground", '#000000',20);
55 $toplevel->optionAdd("$Xname*Button*background", "#f0d0b0",20);
56 $toplevel->optionAdd("$Xname*Button*foreground", '#000000',20);
57 $toplevel->optionAdd("$Xname*Button*borderWidth", '2',20);
58 $toplevel->optionAdd("$Xname*Button*relief", 'groove',20);
59 $toplevel->optionAdd("$Xname*Button*padY", 1,20);
61 $toplevel->optionAdd("$Xname*Checkbutton*background", "#f0d0b0",20);
62 $toplevel->optionAdd("$Xname*Checkbutton*foreground", '#000000',20);
63 $toplevel->optionAdd("$Xname*Checkbutton*borderWidth", '2',20);
64 $toplevel->optionAdd("$Xname*Checkbutton*relief", 'groove',20);
66 $toplevel->optionAdd("$Xname*activeBackground", "#ffffff",20);
67 $toplevel->optionAdd("$Xname*activeForeground", '#0000a0',20);
68 $toplevel->optionAdd("$Xname*borderWidth", 0,20);
69 $toplevel->optionAdd("$Xname*relief", 'flat',20);
70 $toplevel->optionAdd("$Xname*activeBorderWidth", 1,20);
71 $toplevel->optionAdd("$Xname*highlightThickness", 0,20);
72 $toplevel->optionAdd("$Xname*padX", 2,20);
73 $toplevel->optionAdd("$Xname*padY", 2,20);
74 $toplevel->optionAdd("$Xname*font",
75 '-*-helvetica-bold-r-*-*-12-*-*-*-*-*-*-*',20);
76 $toplevel->optionAdd("$Xname*Entry.font",
77 '-*-helvetica-medium-r-*-*-12-*-*-*-*-*-*-*',20);
79 $toplevel->optionAdd("$Xname*Exit.font",
80 '-*-helvetica-bold-r-*-*-10-*-*-*-*-*-*-*',20);
81 $toplevel->optionAdd("$Xname*Exit.relief", 'groove',20);
82 $toplevel->optionAdd("$Xname*Exit.padX", 1,20);
83 $toplevel->optionAdd("$Xname*Exit.padY", 1,20);
84 $toplevel->optionAdd("$Xname*Exit.borderWidth", 2,20);
85 $toplevel->optionAdd("$Xname*Exit*background", "#a0a0a0",20);
86 $toplevel->optionAdd("$Xname*Exit*disabledForeground", "#ffffff",20);
88 $toplevel->optionAdd("$Xname*Entry.background", "#ffffff",20);
89 $toplevel->optionAdd("$Xname*Entry.disabledForeground", "#c0c0c0",20);
90 $toplevel->optionAdd("$Xname*Entry.relief", "sunken",20);
91 $toplevel->optionAdd("$Xname*Entry.borderWidth", 1,20);
93 $toplevel->optionAdd("$Xname*Field.background", "#ffffff",20);
94 $toplevel->optionAdd("$Xname*Field.disabledForeground", "#c0c0c0",20);
95 $toplevel->optionAdd("$Xname*Field.relief", "flat",20);
96 $toplevel->optionAdd("$Xname*Field.borderWidth", 1,20);
98 $toplevel->optionAdd("$Xname*Label.disabledForeground", "#c0c0c0",20);
99 $toplevel->optionAdd("$Xname*Label.borderWidth", 1,20);
101 $toplevel->configure(-background
=>$toplevel->optionGet("background",""));
103 #$toplevel->resizable(FALSE,FALSE);
105 $window_shell=$toplevel->Label(Name
=>"shell",-borderwidth
=>1,-relief
=>raised
)->
106 place
(-x
=>10,-y
=>36,-relwidth
=>1.0,-relheight
=>1.0,
107 -width
=>-20,-height
=>-46,-anchor
=>'nw');
109 $window_quit=$window_shell->Button(-class=>"Exit",-text
=>"quit")->
110 place
(-x
=>-1,-y
=>-1,-relx
=>1.0,-rely
=>1.0,-anchor
=>'se');
112 $window_version=$toplevel->
113 Label
(Name
=>"logo text",-class=>"Panel",-text
=>$version)->
114 place
(-x
=>5,-y
=>5,-anchor
=>'nw');
119 $window_statuslabel=$window_shell->
120 Label
(Name
=>"statuslabel",-class=>"Statuslabel",-text
=>"Status: ")->
121 place
(-x
=>$x,-y
=>$y,-anchor
=>'nw');
123 $window_status=$window_shell->
124 Label
(Name
=>"status",-class=>"Status",-text
=>"Starting...",-anchor
=>'w')->
125 place
(-x
=>0,-y
=>0,-relx
=>1.,-anchor
=>'nw',-in=>$window_statuslabel);
127 $y+=$window_statuslabel->reqheight()*1.3;
129 $window_quit->configure(-command
=>[sub{Shutdown
();}]);
131 $window_device_label=$window_shell->Label(-text
=>"device: ")->
132 place
(-x
=>$x,-y
=>$y,-anchor
=>'nw');
133 $window_device=$window_shell->Label(-class=>"Field",-text
=>"$device")->
134 place
(-relx
=>1.,-anchor
=>'nw',-bordermode
=>outside
,
135 -in=>$window_device_label);
137 $window_go=$window_shell->Button(-text
=>"go",-state=>'disabled')->
138 place
(-x
=>-$x,-y
=>$y,-relx
=>1.,-anchor
=>'ne',-bordermode
=>outside
);
140 $window_check=$window_shell->Button(-text
=>"recheck",-state=>disabled
)->
141 place
(-anchor
=>'ne',-bordermode
=>outside
,-in=>$window_go);
143 $window_scan=$window_shell->Button(-text
=>"rescan")->
144 place
(-anchor
=>'ne',-bordermode
=>outside
,-in=>$window_check);
146 $y+=$window_scan->reqheight()*1.2+5;
148 $window_artist_label=$window_shell->Label(-text
=>"artist: ")->
149 place
(-x
=>$x,-y
=>$y,-anchor
=>'nw');
151 $window_artist=$window_shell->
152 Entry
(-width
=>1024,-textvariable
=>\
$artist)->
153 place
(-x
=>$x+$window_artist_label->reqwidth(),-y
=>$y,
155 -width
=>-10-$window_artist_label->reqwidth(),
158 $y+=$window_artist_label->reqheight();
160 $window_album_label=$window_shell->Label(-text
=>"album: ")->
161 place
(-x
=>$x,-y
=>$y,-anchor
=>'nw');
163 $window_album=$window_shell->
164 Entry
(-width
=>1024,-textvariable
=>\
$album)->
165 place
(-x
=>$x+$window_artist_label->reqwidth(),-y
=>$y,
167 -width
=>-10-$window_artist_label->reqwidth(),
170 $y+=$window_artist_label->reqheight()+5;
175 for(my$i=1;$i<=99;$i++){
177 $text="track0$i: " if($i<10);
179 $window_track_label[$i]=$window_shell->Label(-text
=>$text);
181 $window_track[$i]=$window_shell->
182 Entry
(-width
=>1024,-textvariable
=>\
$trackname[$i]);
183 $window_play[$i]=$window_shell->
184 Button
(-text
=>"play",-command
=>[main
::playtrack
,$i]);
185 $window_rip[$i]=$window_shell->
186 Checkbutton
(-variable
=>\
$trackrip[$i],-text
=>"rip");
189 $totaly=int(46+$window_quit->reqheight()+10+$y);
191 $toplevel->configure(-width
=>$width);
192 $toplevel->configure(-height
=>$totaly);
194 $window_scan->configure(-command
=>[sub{Rescan
();}]);
195 $window_check->configure(-command
=>[sub{Recheck
();}]);
196 $window_go->configure(-command
=>[sub{Rip
();}]);
198 Status
("Ready to Query");
208 unlink "/tmp/$$.pipe";
209 $toplevel->fileevent(PLAY_STDERR
,'readable' => '');
210 Status
("Stopping playback");
213 kill 15, ($playpid1);
217 $window_play[$playstatus]->configure(-text
=>"play");
218 Status
("Playback stopped");
226 if($playstatus>0 && $playstatus==$tracknum){
230 $playstatus=$tracknum;
231 $window_play[$tracknum]->configure(-text
=>"stop");
233 # creating a process -> process pipe is buggy if we [the Perl
234 # script] are not one of the processes (first one works,
235 # subsequent ones always fail). So, make a named pipe
238 my$pipe="/tmp/$$.pipe";
239 system('mknod', $pipe, 'p')
240 && system('mkfifo', $pipe)
241 && die "can't mknod $pipe: $!";
244 eval "\$glob=\"$playrip\"";
246 eval "\$glob2=\"$play\"";
247 $SIG{PIPE
} = 'IGNORE';
249 die "pipe call failed unexpectedly: $!" unless
250 pipe PLAY_STDERR
,WRITEH2
;
252 $playpid1=open3
("STDIN","STDOUT",">&WRITEH2",$glob);
253 $playpid2=open3
("STDIN","STDOUT",">&WRITEH2",$glob2);
255 die $! unless close WRITEH2
;
256 Status
("Playing track $tracknum...");
257 $toplevel->fileevent(PLAY_STDERR
,'readable'=>[sub{PlayStderr
();}]);
263 Status
("Cancelling");
264 if(defined($rippid) && $rippid>=0){
265 $toplevel->fileevent(RIP_STDERR
,'readable' => '');
272 $window_go->configure(-command
=>[sub{Rip
();}],-text
=>"go");
273 $window_scan->configure(-command
=>[sub{Rescan
();}],-text
=>"rescan");
281 if(defined($rippid) && $rippid>=0){
282 $toplevel->fileevent(RIP_STDERR
,'readable' => '');
296 $window_go->configure(-command
=>[sub{Cancel
();}],-text
=>"cancel");
297 $window_scan->configure(-command
=>[sub{Skip
();}],-text
=>"skip");
300 while($ripstatus<=$tracks){
301 if($trackrip[$ripstatus]){
302 # rip this track; event driven, set up the callback.
305 my$title=$trackname[$track];
307 $track=0 if($track==$tracks && $seekrit);
309 $wavfile="/tmp/$$.wav";
311 eval "\$glob=\"$rip\"";
314 die "pipe call failed unexpectedly: $!" unless
315 pipe RIP_STDERR
,WRITEH
;
316 $rippid=open3
("STDIN",">&STDOUT",">&WRITEH",$glob);
319 Status
("Starting read...");
320 $toplevel->fileevent(RIP_STDERR
,'readable'=>[sub{ReadStderr
();}]);
327 $window_go->configure(-command
=>[sub{Rip
();}],-text
=>"go");
328 $window_scan->configure(-command
=>[sub{Rescan
();}],-text
=>"rescan");
336 $trackpad=0 if($trackpad==$tracks && $seekrit);
337 $trackpad="0$trackpad" if($trackpad<10);
338 "$trackbase/$artist/$album/$artist - $album - $trackpad - $trackname[$tracknum]$suffix";
342 Status
("Scanning disc");
345 $window_go->configure(-state=>"disabled");
347 # get table of contents
349 eval "\$foo=\"$query\"";
353 $output=~s/[^=]+=+\n(.+)/$1/;
355 foreach (split(/\n/,$output)){
356 if(m/^\s*([0-9]+)\.\s+(\d+)\s+\S+\s+(\d+)/){
362 $tracks=$1 if ($1>$tracks);
364 push @toc,"$1 $m $s $f";
365 $initial=$3 if($1==1);
375 push @toc, "999 $m $s $f";
382 Status
("Query failed");
383 Alert
("Could not query cdrom; drive empty?",$output);
385 for(my$i=1;$i<=99;$i++){
389 Status
("Querying database");
393 my $cddb = new CDDB
() or die $!;
394 my @discs = $cddb->get_discs_by_toc(@toc);
396 my@temp=@
{$discs[0]};
398 if(!defined(@discs)|| $#discs<0 || !defined(@temp[1])){
400 # CDDB protocol is stupidly designed in many ways, but one
401 # of the biggest is that matching depends on the offset of
402 # the first track... which is neither deterministic from
403 # CDROM drive to CDROM drive, nor a well defined concept
404 # in the RedBook spec. Even worse, it appears that much
405 # CDROM software always reports it as MSF 0 2 0 regardless
406 # of what it is, and thus CDDB often has the offset for
407 # the whole disc fucked up.
415 $cddb = new CDDB
() or die $!;
416 Status
("Requerying database");
418 @discs = $cddb->get_discs_by_toc(@toc);
422 if(!defined(@discs)|| $#discs<0 || !defined(@temp[1])){
425 Status
("Album Loaded; no CDDB entry");
426 for(my$i=1;$i<=$tracks;$i++){
434 $trackname[$tracks]="Hidden Track Zero";
441 $window_go->configure(-state=>"normal");
442 $window_check->configure(-state=>"normal");
447 my ($genre, $cddb_id, $title) = @
{$discs[0]};
448 $title=~/(.+)\s\/\s
(.+)/;
453 my $disc_info = $cddb->get_disc_details($genre, $cddb_id);
455 for(my$i=0;$i<$tracks;$i++){
456 $trackname[$i+1]=$disc_info->{"ttitles"}[$i];
457 $trackname[$i+1]=~s/\//:/g
;
463 $trackname[$tracks]="Hidden Track Zero";
468 Status
("Album Loaded");
472 $window_go->configure(-state=>"normal");
473 $window_check->configure(-state=>"normal");
478 for(my$i=1;$i<=$tracks;$i++){
479 $trackrip[$i]=0 if(-e Track_Path
($i));
486 # re-place widgets up to number of tracks...
487 for(my$i=1;$i<=$tracks;$i++){
488 $window_track_label[$i]->
489 place
(-y
=>$y,-x
=>$x,-anchor
=>'nw',-bordermode
=>outside
);
491 place
(-y
=>$y,-x
=>-$x,-relx
=>1.,-anchor
=>'ne',-bordermode
=>outside
);
493 place
(-y
=>$y,-x
=>-$x-$window_rip[$i]->reqwidth(),-relx
=>1.,
494 -anchor
=>'ne',-bordermode
=>outside
);
497 -x
=>$x+$window_track_label[$i]->reqwidth(),
500 -$window_rip[$i]->reqwidth()
501 -$window_track_label[$i]->reqwidth()
502 -$window_play[$i]->reqwidth(),
503 -anchor
=>'nw',-bordermode
=>outside
);
506 $y+=$window_rip[$i]->reqheight();
508 # unplace widgets past number of tracks
509 for(my$i=$tracks+1;$i<=99;$i++){
510 $window_track_label[$i]->placeForget();
511 $window_track[$i]->placeForget();
512 $window_play[$i]->placeForget();
513 $window_rip[$i]->placeForget();
516 my $totaly=int(46+$window_quit->reqheight()+10+$y);
517 my $geometry=$toplevel->geometry();
518 $toplevel->minsize(400,$totaly);
520 $toplevel->configure(-width
=>$1);
521 $toplevel->configure(-height
=>$totaly);
533 $window_status->configure(-text
=>"$text");
534 #$toplevel->update();
538 my($message,$detail,$window)=@_;
540 $window=$toplevel if(!defined($window));
541 $modal->destroy() if(defined($modal));
543 print STDERR
"\n\n$message :: $detail\n";
545 $modal=new MainWindow
(-class=>"$Xname");
546 $modal->configure(-background
=>$modal->optionGet("background",""));
548 $modal_shell=$modal->Label(-class=>Alert
,Name
=>"shell",
549 Borderwidth
=>1,Relief
=>raised
)->
550 place
(-x
=>4,-y
=>4,-relwidth
=>1.0,-relheight
=>1.0,
551 -width
=>-8,-height
=>-8,-anchor
=>'nw');
553 $modal_exit=$modal_shell->
554 Button
(-class=>"Exit",-text
=>"X")->
555 place
(-x
=>-1,-y
=>-1,-relx
=>1.0,-rely
=>1.0,-anchor
=>'se');
557 $modal_message=$modal_shell->
558 Label
(-text
=>$message,-class=>"AlertText")->
561 my$width=$modal_message->reqwidth();
562 $width=300 if($width<300);
564 $detail="" if(!defined($detail));
565 $modal_detail=$modal_shell->
566 Message
(-text
=>$detail,-class=>"AlertDetail",
567 -width
=>($width-$modal_exit->reqwidth()))->
568 place
(-relx
=>0,-y
=>5,-rely
=>1.0,-anchor
=>'nw',
569 -in=>$modal_message);
572 my$height=$modal_message->reqheight()+$modal_detail->reqheight()+30;
574 my$xx=$window->rootx();
575 my$yy=$window->rooty();
576 my$ww=$window->width();
577 my$hh=$window->height();
579 my$x=$xx+$ww/2-$width/2;
580 my$y=$yy+$hh/2-$height/2;
582 $modal->geometry($width."x".$height."+".int($x)."+".int($y));
584 $modal->resizable(FALSE
,FALSE
);
585 $modal->transient($window);
586 $modal_exit->configure(-command
=>[sub{$modal->destroy();undef $modal}]);
592 $bytes=sysread RIP_STDERR
, my$scalar, 4096;
594 $toplevel->fileevent(RIP_STDERR
,'readable' => '');
595 Alert
("ripper unexpectedly exited!","Skipping track $ripstatus for now...");
600 push my@lines, split /[\n|\r]/, $saved_stderr.$scalar;
601 if((chomp $scalar)==0){
602 $saved_stderr=$lines[$#lines];
607 foreach my$line (@lines){
609 if($line=~/Done encoding file/){
615 $toplevel->fileevent(RIP_STDERR
,'readable' => '');
619 my$trackname="$trackbase";
620 mkdir $trackname if(!-e
$trackname);
622 $trackname="$trackbase/$artist";
623 mkdir $trackname if(!-e
$trackname);
625 $trackname="$trackbase/$artist/$album";
626 mkdir $trackname if(!-e
$trackname);
628 my$trackname=Track_Path
($ripstatus);
629 print STDERR
"moving $outfile to $trackname\n";
632 $trackname=~s/([\"\$\`\\])/\\$1/g;
633 die "could not move file!: $!" if `mv "$outfile" "$trackname"`;
635 # unclick the rip button
643 if($line=~/\[finished\]/){
644 # cdparanoia finished
646 $toplevel->fileevent(RIP_STDERR
,'readable' => '');
652 my$metatitle=$trackname[$ripstatus];
655 $wavfile="/tmp/$$.wav";
656 $outfile="/tmp/$$.ogg";
658 $metatitle=~s/([\"\$\`\\])/\\$1/g;
659 $metaartist=~s/([\"\$\`\\])/\\$1/g;
660 $metaalbum=~s/([\"\$\`\\])/\\$1/g;
663 eval "\$glob=\"$encode\"";
664 $glob=~s/[\{\}]/\"/g;
668 die "pipe call failed unexpectedly: $!" unless
669 pipe RIP_STDERR
,WRITEH
;
670 $rippid=open3
("STDIN",">&STDOUT",">&WRITEH",$glob);
673 Status
("Starting encode...");
674 $toplevel->fileevent(RIP_STDERR
,'readable'=>[sub{ReadStderr
();}]);
678 if($line=~/Encoding with/){
679 Status
("Encoding track $ripstatus");
682 if($line=~/\[\s*(\d*\.\d)\%\]/){
683 Status
("Encoding track $ripstatus [$1%]");
687 if($line=~/to sector\s*(\d+)/){
691 if($line=~/\[wrote\]\s\@\s(\d+)/){
692 $left=$endsector-int($1/1176);
693 Status
("Reading track $ripstatus [$left]");
704 $bytes2=sysread PLAY_STDERR
, my$scalar, 4096;
713 push my@lines, split /[\n\r]/, $saved_stderr2.$scalar;
714 if((chomp $scalar)==0){
715 $saved_stderr2=$lines[$#lines];
720 foreach my$line (@lines){
722 if($line=~/\[finished\]/){
723 # cdparanoia finished