#!/usr/bin/perl -w my $RCS_Id = '$Id: ls2nocddb.pl,v 1.12 2004/08/13 13:42:27 jv Exp $ '; # Author : Johan Vromans # Created On : Tue Sep 15 15:59:04 1992 # Last Modified By: Johan Vromans # Last Modified On: Fri Aug 13 15:41:52 2004 # Update Count : 115 # Status : Unknown, Use with caution! ################ Common stuff ################ use strict; # Package or program libraries, if appropriate. # $LIBDIR = $ENV{'LIBDIR'} || '/usr/local/lib/sample'; # use lib qw($LIBDIR); # require 'common.pl'; # Package name. my $my_package = 'Sciurix'; # Program name and version. my ($my_name, $my_version) = $RCS_Id =~ /: (.+).pl,v ([\d.]+)/; # Tack '*' if it is not checked in into RCS. $my_version .= '*' if length('$Locker: $ ') > 12; use FindBin; use lib $FindBin::Bin; ################ Command line parameters ################ use Getopt::Long 2.13; # Command line options. my $length = -1; # calculate length of tracks my $verbose = 0; # verbose processing my $print = 0; # send to stdout # Development options (not shown with -help). my $debug = 0; # debugging my $trace = 0; # trace (show process) my $test = 0; # test mode. # Process command line options. app_options(); # Post-processing. $trace |= ($debug || $test); ################ Presets ################ my $TMPDIR = $ENV{TMPDIR} || $ENV{TEMP} || '/usr/tmp'; ################ The Process ################ use Cwd qw(abs_path); foreach my $dir ( @ARGV ) { unless ( -d $dir ) { warn("$dir: not a directory -- skipped\n"); next; } if ( -s "$dir/.nocddb" && !$print ) { warn("$dir/.nocddb: exists -- directory skipped\n"); next; } $dir =~ s;/+$;;; my @a = split('/', abs_path($dir)); my ($artist, $album) = ("",""); if ( @a >= 2 ) { $artist = neat($a[-2]); $album = neat($a[-1]); } my @files = sort map { $1 if /([^\/]+)\.mp3$/ } glob("$dir/[0-9]*.mp3"); my $fh = *STDOUT; unless ( $print ) { open($fh, ">$dir/.nocddb") or die("$dir/.nocddb: $!\n"); } print $fh ("$artist / $album\n\n"); my $prev = 0; my @res; my $maxlen = 40; foreach my $file ( @files ) { my $mmss = 0; $mmss = getlength("$dir/$file.mp3") if $length; $file =~ s/^(\d+)_(-_)?//; $prev++; if ( $1 - $prev > 5 ) { warn("Too many 'Unknown's, suppressing\n"); $prev = $1; } my $t; while ( $prev < $1 ) { $t = sprintf (" %2d. %s", $prev, "Unknown"); $maxlen = length($t) if $maxlen < length($t); push(@res, [ $t, 0 ]); $prev++; } $t = sprintf (" %2d. %s", $1, neat($file)); $maxlen = length($t) if $maxlen < length($t); push(@res, [ $t, $mmss ]); } foreach ( @res ) { my ($t, $mmss) = @$_; my $l = length($t); unless ( $mmss ) { print $fh ($t, "\n"); next; } $t .= "\t"; $l = ($l & 0xf8) + 7; while ( $l <= $maxlen ) { $t .= "\t"; $l += 8; } print $fh ($t, $mmss, "\n"); } print $fh ("\nGenerated by $my_name $my_version on ". localtime(time)."\n"); close($fh); } exit 0; ################ Subroutines ################ sub neat { local($_) = shift; s/__/:_/g; s/;_/_\/_/g; s/_s_/'s_/gi; s/_m_/'m_/gi; s/(you|we|they)_re_/$1're_/gi; s/_/ /g; s/\s+/ /g; s/(^|-|\s|\(|\[)(.)/$1.ucfirst($2)/eg; $_; } sub getlength { my ($file) = @_; my $mmss; if ( $length < 0 ) { $mmss = getlength_mi($file); return $mmss if defined $mmss; } $mmss = getlength_af($file); return $mmss if defined $mmss; $length = 0; return " -:--"; } sub getlength_mi { my ($file) = @_; unless ( eval { require MP3::Info; } ) { warn("Cannot load MP3::Info\n"); return undef; } import MP3::Info; return " -:--" unless -f $file; my $f = get_mp3info($file); return " -:--" unless $f; sprintf("%2d:%02d", $f->{MM}, $f->{SS}); } sub getlength_af { my ($file) = @_; unless ( eval { require MPEG::Audio::Frame; } ) { warn("Cannot load MPEG::Audio::Frame\n"); return undef; } return " -:--" unless -f $file; my $f; unless ( open($f, '<', $file) ) { warn("$file: $!\n"); return " -:--"; } my $length; my $frames = 0; while ( my $frame = MPEG::Audio::Frame->read($f)) { $length += $frame->seconds; } return dptime_formatted($length); } sub dptime_formatted { my ($t) = (@_); $t += 0.5; $t = int($t); my $s = $t % 60; my $m = int($t / 60); sprintf("%2d:%02d", $m, $s); } ################ Subroutines ################ sub app_options { my $help = 0; # handled locally my $ident = 0; # handled locally # Process options, if any. # Make sure defaults are set before returning! return unless @ARGV > 0; if ( !GetOptions( 'length!' => \$length, 'ident' => \$ident, 'print' => \$print, 'verbose' => \$verbose, 'trace' => \$trace, 'help|?' => \$help, 'debug' => \$debug, ) or $help ) { app_usage(2); } app_ident() if $ident; } sub app_ident { print STDERR ("This is $my_package [$my_name $my_version]\n"); } sub app_usage { my ($exit) = @_; app_ident(); print STDERR <<EndOfUsage; Usage: $0 [options] [file ...] -[no]length [do not] calculate playing time -help this message -ident show identification -verbose verbose information EndOfUsage exit $exit if defined $exit && $exit != 0; } =head1 NAME ls2nocddb - produce CDDB::Fake info from a directory =head1 SYNOPSIS ls2nocddb [ options ] directory ... Options: --[no]length [do not] include length information --print write to standard output --verbose more verbose information --help help message =head1 DESCRIPTION This program can generate CDDB::Fake information data from a directory containing MP3 files. It requires the directory to be named Artist_Name/Album_Title. The MP3 files in the directory must be named NN_Track_Title.mp3, where I<NN> is the track number. When the package MP3::Info or MPEG::Audio::Frame is available, the program can add the lengths of the tracks to the generated information. For example: Given the directory Jazz/Dick_Onstenk/Dick's_Jazz_Stuff with the following files: 01_Body_And_Soul.mp3 02_Fly_Me_To_The_Moon.mp3 03_Lover_Man.mp3 04_Freddie_Freeloader.mp3 05_Billie's_Bounce.mp3 06_Softly_As_In_A_Morning_Sunrise.mp3 Then running B<ls2nocddb> with option C<--length> and argument C<Jazz/Dick_Onstenk/Dick's_Jazz_Stuff>, will create a file named C<.nocddb> in that directory, with the following contents: Dick Onstenk / Dick's Jazz Stuff 1. Body And Soul 3:17 2. Fly Me To The Moon 4:20 3. Lover Man 6:16 4. Freddie Freeloader 5:32 5. Billie's Bounce 4:22 6. Softly As In A Morning Sunrise 3:55 Generated by ls2nocddb 1.4 on Fri Jul 25 14:31:41 2003 Note that calculating the length using MPEG::Audio::Frame make tay some time but it is accurate. Using MP3::Info is fast but depends on the accuracy of the MP3 header info. For this reason, MP3::Info is preferred and MPEG::Audio::Frame is only used as a fall back. Specifying B<--length> forces length calculation with MPEG::Audio::Frame. B<--nolength> suppresses length calculation. =head1 SEE ALSO L<CDDB::File>. =head1 DEPENDENCIES C<MP3::Info> or C<MP3::Audio::Frame> (both optional). If installed, the program can add the lengths of the tracks to the generated information. =head1 AUTHOR Johan Vromans <jvromans@squirrel.nl> =head1 COPYRIGHT This programs is Copyright 2003,2004 Squirrel Consultancy. This program is free software; you can redistribute it and/or modify it under the terms of the Perl Artistic License or the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. =cut