Re: Recursive scanner perl script

Dan Sully <[email protected]>
Newsgroups gmane.comp.audio.netjuke.user
Message-ID <[email protected]>
* Michael Strong <[email protected]> shaped the electrons to say...

> I was just wondering if anyone had come up with a perl script that would 
> be able to do the job of the recursive file finder, but on the command 
> line. I have just installed Netjuke and I have about 2000 tracks to 
> import, and they are in directories based on artist, so there are about 
> 250 subdirs. It is excruciatingly slow to do this from the webpage, 
> somewhere aroung 5 tracks per minute!

I wrote one a while ago and posted it to the list. It's attached.

-D
-- 
There once was a chicken named Phil. His flying saucer for breakfast
rolled down a hill. The hill was made of soap. It was sort of a soap
slope. On a rope. Homogenized, even.
pusher-robot (text/plain, 5.6 KB)
#!/usr/bin/perl -w

# $Id: pusher-robot,v 1.7 2002/10/07 20:24:43 daniel Exp $
# move mp3's into the correct directories.
# import them into netjuke
# [email protected]

use strict;
use DBI;
use File::Basename;
use File::Copy;
use File::Find;
use File::Path;
use MP3::Info;
use MP3::Tag;
use POSIX qw(strftime);
use URI::Escape;

my $DEBUG	= 1;

my $INCOMING	= '/bits/incoming';
my $HOME	= '/bits/mp3';
my $dupeCheck	= 1;

my %dbiSettings = (
	PrintError => 1,
	RaiseError => 0,
);

my $dbName	= 'netjuke';
my $dbUser	= 'netjuke';
my $dbPass	= 'insecure';
my $dbDriver	= 'mysql';

my %trackCache	= ();  
my %idCache	= ();
my %escapeCache = ();

sub main {
	MP3::Info::use_winamp_genres();

	my $dbh = DBI->connect("dbi:$dbDriver:$dbName", $dbUser, $dbPass, \%dbiSettings) or die $!;

	my ($dir,$count) = ($INCOMING,0);

	# parse the commandline if given
	while (my $arg = shift @ARGV) {
		if ($arg =~ /^-d/o) { $dir = shift @ARGV; next }
		if ($arg =~ /^-nodupe?/o) { $dupeCheck = 0; shift @ARGV; next }
	}

	# find files to import
	find sub {
		my $file = $File::Find::name;

		return unless $file =~ /\.mp3$/io;
		return unless -f $file;

		my ($artist,$album) = process($dbh,$file);

		# don't move the file if we didn't import it.
		return unless defined $artist and defined $album;

		my $destDir  = "$HOME/$artist/$album";
		my $destFile = "$destDir/" . basename($file);
		
		# only move if the dest and source are different
		if ($file ne $destFile) {

			mkpath([$destDir], 0, 0775) unless -d $destDir;
			move($file, $destDir) or warn "Can't move [$file] to [$destDir]: $!";
		}

	}, $dir;

	# update the counts
	while (my ($table,$data) = each %trackCache) {

		while (my ($id,$value) = each %$data) {
			updateCount($dbh,$table,'increment',$value,$id);
		}
	}
}

# get the artist/album/genre id if it exists. otherwise increment.
sub findId {
	my ($dbh,$table,$name) = @_;

	my $cnt = 0;
	my $err = '';
	my $id  = 1;

	if (!defined $name or length($name) == 0) {
		return wantarray ? ($id,$cnt,$err) : $id;
	}

	if (defined $idCache{$table}->{$name}) {
		return $idCache{$table}->{$name};
	}

	my $select = $dbh->prepare_cached("SELECT id FROM $table WHERE name = ?");

	if ($select->execute($name) && $select->rows() < 1) {

		my $insert = $dbh->prepare_cached("INSERT INTO $table (name) VALUES (?)");

		if ($insert->execute($name)) {
			$select->finish();
			$select->execute();
		}
	}

	$id = $idCache{$table}->{$name} = ($select->fetchrow_array())[0];

	$select->finish();
	$cnt++;

	return wantarray ? ($id,$cnt,$err) : $id;
}

sub updateCount {
	my ($dbh,$table,$action,$val,$id) = @_;

	if ($action eq 'increment') {

		$dbh->do("update $table set track_cnt = track_cnt + $val where id = $id");

	} elsif ($action eq 'decrement') {

		$dbh->do("update $table set track_cnt = track_cnt - $val where id = $id");

	} else {

		$dbh->do("update $table set track_cnt = 0 where track_cnt < 1 or track_cnt is null");
	}

	return 1;
}

sub process {
	my ($dbh,$file) = @_;

	my $tag  = get_mp3tag($file);
	my $info = get_mp3info($file);

	# try and get data from the filename
	if (scalar keys %$tag != 4) {

		my %auto = ();
		my $mp3  = MP3::Tag->new($file);
		$mp3->config('autoinfo','filename');

		($auto{'TITLE'}, $auto{'TRACKNUM'}, $auto{'ARTIST'}, $auto{'ALBUM'}) = $mp3->autoinfo();

		# try and get data from the filename
		for my $field (qw(TITLE TRACKNUM ARTIST ALBUM)) {

			next if defined $tag->{$field} and $tag->{$field} !~ /^\s*$/;

			if (defined $auto{$field} and $auto{$field} !~ /^\s*$/) {

				$tag->{$field} = $auto{$field};
			}
		}
	}

	my ($song, $track, $artist, $album) = 
		($tag->{'TITLE'},$tag->{'TRACKNUM'} || 1,$tag->{'ARTIST'},$tag->{'ALBUM'});

	# normalize
	if ($track =~ m|^(\d+)/\d+$|) {
		$track = $1;
	}

	if (defined $artist and $artist !~ /^\s*$/) {

		$artist =~ s/^Various Artists$/Various/i;

	} else {

		print "artist is undefined for file: [$file]\n";
		return;
	}

	printf("Working on: %32s %64s\n", $artist,$song) if $DEBUG;

	my $date = strftime("%Y-%m-%d %H:%M:%S", localtime((stat($file))[9]));

	# escape the file. cache results.
	$file = join('/', (
		$escapeCache{$artist} ||= uri_escape($artist),
		$escapeCache{$album}  ||= uri_escape($album),
		uri_escape(basename($file)))
	);

	my %data = (
		'name'		=> $song,
		'size'		=> $info->{'SIZE'} || (stat(_))[7],
		'time'		=> $info->{'TIME'},
		'track_number'	=> $track,
		'kind'		=> 'MPEG AUDIO FILE',
		'date'		=> "'$date'",
		'bit_rate'	=> $info->{'BITRATE'},
           	'sample_rate'	=> int($info->{'FREQUENCY'} * 1000),
		'location'	=> $file,
	);

	# check for a duplicate entry
	if ($dupeCheck) {

		my $dupe = $dbh->prepare_cached("select id from netjuke_tracks where location = ?");

		if ($dupe->execute($file) && $dupe->rows() > 1) {

			print "duplicate! skipping $artist - $song\n";
			$dupe->finish();
			return undef;
		}
		
		$dupe->finish();
	}
        
	# Get Artist, Album & Genre ID based on their respective names.
	$data{'ar_id'} = findId($dbh,'netjuke_artists', $artist);
	$data{'al_id'} = findId($dbh,'netjuke_albums', $album);
	$data{'ge_id'} = findId($dbh,'netjuke_genres', $tag->{'GENRE'});

	my (@columns, @values) = ();
  
	while (my ($key, $value) = each %data) {
		push @columns, $key;
		push @values, $value;
	}

	my $insert = $dbh->prepare_cached('INSERT INTO netjuke_tracks ('.
		join(',',@columns).') VALUES ('. join(',', (map { '?' } @values)) .')');

	unless ($insert->execute(@values)) {
	   
		print "An error occured on insert. Skipping $file\n";
		return;
     
	} else {

		$insert->finish();
     
		$trackCache{'netjuke_artists'}->{$data{'ar_id'}}++;
		$trackCache{'netjuke_albums'}->{$data{'al_id'}}++;
		$trackCache{'netjuke_genres'}->{$data{'ge_id'}}++;
	}

	return ($artist,$album);
}

main();

__END__
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.