• 学而时习之专栏
  • 温故而知新专栏

  • 自动CD唱片存入电脑程序浅谈


    由于版权问题在网上听高音质音乐都收费,自己家里正版CD唱片出外听不到,加上有时不小心搞到CD唱片损坏,
    那就惨重,这就有需要保存在电脑里。如果一个一个输入命令去抓取,那就影响效率了,所以需要编写一个运行脚位去自动抓取了,这是一个运行脚位程序,是用perl语言编写的,调用slackware linux系统的cdparanoia的CD
    抓取命令和flac命令将wav格式改为无损格式flac,用flac格式可以减小空间解压时不影响音质。这个比较好选择。



    程序如下:

    #! /usr/bin/perl -w -I .
    open(STDIN, "< /dev/null");
    open(STDOUT, "> /dev/null");
    open(STDERR, "> /dev/null");

    fork && exit;


    my $cdparanoia = `which cdparanoia 2>/dev/null` || "/usr/bin/cdparanoia";
    my @cdparanoia_opts = ();

    my $encoder = "/usr/bin/flac";
    my @encoder_opts = qw/-m 4 -q 6 /;
    my $encoder_ext = "flac";

    my $eject = "/usr/bin/eject";

    my $flacdir = "/usr/local/share/music";

    my $tmpdir = "/tmp";

    my @cddab_server_list = (
    "127.0.0.1:8880",
    );

    chomp($cdparanoia);
    chomp($encoder);

    use MPEG::MP3Info;
    use Getopt::Long;
    $| = 1;

    print <
    autorip 1.0 - the no-frills mp3 encoding front-end.
    License: GPL
    Author: jmayer\@earthling.net

    Simple five-step usage guide:
    1. configure by editing $0
    2. run $0
    3. Drop audio CD into the CD-ROM tray.
    4. Do something else until autorip ejects your CD.
    5. Repeat steps 3-4 until CD supply is exhausted.

    HEADER

    my $justid = 0;
    my $ret = GetOptions ('info', \$justid);

    sub escape
    {
    my $a = shift;
    # massage the data:
    $a =~ s/[^a-zA-Z0-9\-]+/_/g;
    # whee!
    $a;
    }

    my $tmpwav = $tmpdir . "/tmp.wav";

    while (1) {
    # wait for a cd:
    print "Waiting for next disk...\n";
    {
    my $gotcd = 0;
    while (!$gotcd) {
    system("$cdparanoia -qQ &> /dev/null");
    if (($? >> 8) == 0) { $gotcd = 1; }
    else { sleep(2); }
    }
    }

    my $info = CDDB::get();

    if (!defined($info->{DTITLE})) {
    print "ERROR: Could not identify disk!\n";
    system($eject);
    sleep(2);
    next;
    }


    my ($artist,$title) = split(/\s+\/\s+/, $info->{DTITLE});
    my @track = ();
    while (my($key,$val) = each %$info) {
    if ($key =~ m/^TTITLE(\d+)/) {
    $track[$1] = $val;
    }
    }

    print "-" x 75,"\n";
    print "Title: $title\n";
    print "Artist: $artist\n";
    print "Tracks:\n\to ",join("\n\to ",@track),"\n\n";

    if ($justid) {
    system($eject);
    next;
    }

    unlink($tmpwav);
    my $subdir = escape($artist) . "-" . escape($title);
    my $dir = $tmpdir."/".$subdir;
    system ("mkdir -p $dir");

    my @tracks_pending = ( 1 .. ($#track+1) );
    my @encodes_pending = ();
    my $rip_pid = undef;
    my $enc_pid = undef;
    my $ripping = undef;

    my $started_rip = time();

    while(1) {
    if (@tracks_pending && ($#encodes_pending < 1) && !defined($rip_pid))
    {
    my $track = shift(@tracks_pending);
    $rip_pid = rip($track);
    $ripping = $track;
    }
    if (@encodes_pending && !defined($enc_pid))
    {
    my $track = shift(@encodes_pending);
    my $tmpwav = $tmpdir . "/tmp.$$.$track.wav";
    my $mp3file = sprintf ("%s/%02d-%s.%s", $dir, $track,
    escape($track[$track-1]), $encoder_ext);
    $enc_pid = enc($tmpwav, $mp3file,
    $track, $track[$track-1], $artist, $title);
    }
    last if ((!defined($enc_pid)) && (!defined($rip_pid)));
    my $pid = wait();
    if (defined($enc_pid) && $pid == $enc_pid) {
    $enc_pid = undef;
    }
    if (defined($rip_pid) && $pid == $rip_pid) {
    push (@encodes_pending, $ripping);
    $ripping = undef;
    $rip_pid = undef;
    }
    }

    my $dur_rip = time() - $started_rip;
    my $min_rip = int($dur_rip / 60);
    my $sec_rip = $dur_rip % 60;

    print "Files moved to: ${oggdir}/${subdir}\n";
    print "Finished disk (time elapsed: $min_rip:$sec_rip)\n";
    system ("mv $dir ${oggdir}/${subdir}");
    system ($eject);
    print "\n";
    }

    sub rip
    {
    my $track = shift;
    my $tmpwav = $tmpdir . "/tmp.$$.$track.wav";
    my $pid = fork();
    return $pid if ($pid);

    # I'm a child:
    my $cmd = "$cdparanoia -qw $track $tmpwav";
    print "Started ripping track #$track\n";
    my $start =time();
    system ("$cmd");
    my $dur = time() - $start;
    print "Finished ripping track #$track ($dur secs)\n";
    exit(0);
    }

    sub enc
    {
    my $inputwav = shift || die;
    my $outputmp3 = shift || die;
    my ($tracknum, $track, $artist, $title) = (shift,shift,shift,shift);
    my $pid = fork();
    return $pid if ($pid);

    # I'm a child:
    print "Started encoding $track\n";
    my $start = time();

    if ($encoder_ext eq 'mp3') {
    # mp3
    system ("$encoder @encoder_opts $inputwav \"${outputmp3}\" 2>&1 > /dev/null");
    set_mp3tag ($outputmp3,
    $track,
    $artist,
    $title,
    "",
    "",
    "",
    int($tracknum));
    my $dur = time()-$start;
    } else {
    # ogg vorbis
    system ($encoder, @encoder_opts,
    "-o", $outputmp3,
    "-t", $track,
    "-l", $title,
    "-a", $artist,
    $inputwav);
    }
    unlink($inputwav);
    my $dur = time()-$start;
    print "Finished encoding ${outputmp3} ($dur secs)\n";
    exit(0);
    }

    exit(0);

    package CDDB;

    use FileHandle;
    use Data::Dumper;
    use IO::Socket;

    sub cddb_sum
    {
    my $n = shift;
    my $ret = 0;
    while ($n > 0) {
    $ret += ($n % 10);
    $n = int($n / 10);
    }
    return $ret;
    }

    sub cddb_discid
    {
    my $offset = shift;
    my $length = shift;

    # print Data::Dumper->Dump([ $offset, $length ]);

    my $t = 0;
    my $n = 0;
    my $ref;
    my $track = 1;
    my @o = ();
    foreach $ref (@$offset)
    {
    my $this_off = $ref->[0] * 60 + $ref->[1] + 2;
    # $this_off *= 75;
    # print STDERR "$track: $this_off\n"; $track++;
    $n = $n + cddb_sum( $this_off );
    push (@o, $this_off * 75);
    }

    my $lastoff =$offset->[$#$offset]->[3];
    my $lastlen =$length->[$#$length]->[3];
    $t = $lastoff + $lastlen;
    # print STDERR "$lastoff + $lastlen: t = $t / 75...\n";
    $t = int($t / 75);
    # $t = $t & 0xFFFFFF;

    # print STDERR sprintf("n=%x(%d) t=%x(%d)\n",$n,$n,$t,$t);

    my $id = (($n & 0xFF) << 24 | $t << 8 | ($#$length+1));
    return sprintf("%08x",$id), ($#$length+1), @o, $t;
    }

    sub get_toc_cdparanoia
    {
    my $ripper = shift;

    # print STDERR "Running $ripper:\n";
    my $fh = new FileHandle("$ripper -Q 2>&1 |");

    my @length = ();
    my @offset = ();
    while (defined($_ = <$fh>)) {
    if ( /(\d+)\s+\[(\d+):(\d+)\.(\d+)\].+?(\d+)\s+\[(\d+):(\d+)\.(\d+)\]/ ) {
    # print STDERR $_;
    push (@length, [ $2, $3, $4, $1 ]); # min, sec, frame, $tracks
    push (@offset, [ $6, $7, $8, $5 ]); # min, sec, frame, $tracks
    }
    }
    $fh->close();
    # print STDERR "done with $ripper\n";

    return \@length, \@offset;
    }

    sub get_cddb
    {
    my @id = @_;

    my $status;
    my %entry;
    my $server;
    foreach $server (@cddb_server_list)
    {
    ($status, %entry) = get_cddb_entry ($server, @id);
    last if ($status != 0);
    }

    return \%entry;
    }

    sub myprint
    {
    my $fh = shift;
    # print STDERR @_;
    print $fh @_;
    }

    sub get_cddb_entry
    {
    my $server = shift;
    my @id = @_;

    my $sock = new IO::Socket::INET (
    PeerAddr => $server,
    Proto => 'tcp') || return 0;

    $_ = <$sock>; # print STDERR $_;
    return 0 unless (m/^2/);

    #my $user = $ENV{USER};
    my $user = 'root';
    my $hostname = `hostname`; chomp($hostname);
    myprint ($sock, "cddb hello $user $hostname CDDB.pm 0.1\n");
    $_ = <$sock>; # print STDERR $_;
    return 0 unless (m/^2/);

    myprint ($sock, "cddb query @id\n");
    $_ = <$sock>; print STDERR $_;
    return 0 if (m/^202/); # no match for disc
    return 0 unless (m/^2/);

    my $cat; my $real_id;
    if (m/^200\s+(\S+)\s+(\S+)/) {
    $cat = $1; $real_id = $2;
    } else {
    $_ = <$sock>; # print STDERR $_;
    return 0 unless (m/^(\S+)\s+(\S+)/);
    $cat = $1; $real_id = $2;
    while (defined($_ = <$sock>))
    { # print STDERR $_;
    last if (m/^\./);
    }
    }
    print STDERR "found real id: $cat $real_id\n";

    myprint ($sock,"cddb read $cat $real_id\n");
    $_ = <$sock>; # print STDERR $_;
    return 0 unless (m/^2/);

    my $info = {};

    while (defined ($_ = <$sock>)) { # print STDERR $_;
    last if (m/^\./);
    next if (m/^#/);
    if (m/^(\S+?)=(.+)/) {
    $info->{$1} = $2;
    $info->{$1} =~ s/\s*$//;
    }
    }

    myprint ($sock,"quit\n");
    $sock->close();

    return (1, %$info);
    }

    sub get
    {
    my ($len, $off) = get_toc_cdparanoia($cdparanoia);
    my @id = cddb_discid($off, $len);
    my $info = get_cddb(@id);
    return $info;
    }

    sub test
    {
    my ($len, $off) = get_toc_cdparanoia($cdparanoia);
    my @id = cddb_discid($off, $len);
    print "Disc ID: @id\n";
    my $info = get_cddb(@id);
    print Data::Dumper->Dump([ $info ]),"\n";
    }