用.cue文件切割.wav文件

用.cue文件切割.wav文件

先发布源代码,再说明干什么用,免得烂在我的硬盘里。

[Copy to clipboard] [ - ]
CODE:
#!/usr/bin/env perl
# This program splits a wav file that contains multiple tracks into multiple wav
# files, each of which contains one track based on indices provided by a cue file.
# Written by Shufeng Tan <shufengtan@gmail.com> January, 2007
# It is assumed that the cue file is in the same directory with the wav file.
# The cue file with the same basename with the wav file will be picked.
# Failing that, the program will pick the first cue file listed by DirHandle.

use strict;
use Audio::Wav;
use Data::Dumper;
use DirHandle;
use FileHandle;
use File::Basename;

my $wavfile = shift;
die "Usage: $0 file.wav\n" unless $wavfile;
die "File $wavfile not found.\n" unless -f $wavfile;

my $cuefile = find_cue_file($wavfile);
warn "CUE file not found for $wavfile\n" unless $cuefile;

my $WAV = new Audio::Wav;
my $read = $WAV->read($wavfile);
my $details = $read->details;
my $Track_Details = {
        bits_sample => $details->{bits_sample},
        sample_rate => $details->{sample_rate},
        channels => $details->{channels},
};

exit unless $cuefile;
my @tracks = parse_cue_file($cuefile, $read);

print "$#tracks tracks from $cuefile\n";

my $Max_Buf_Len = 1048576;

foreach my $i (0 .. ($#tracks-1)) {
        my $track_info = $tracks[$i];
        my $track = $track_info->{TRACK};
        my $name = $track_info->{TITLE};
        my $artist = $track_info->{PERFORMER};
        my $track_file = "${track}_$name.wav";
        $track_file =~ tr|/:|  |;
        my @pos = @{$track_info->{POS}};
        my $track_len = $tracks[$i+1]->{POS}->[0] - $pos[0];
        print "TRACK $track '$name' by '$artist' $track_len bytes POS=[@pos]\n";

        my $write = $WAV->write($track_file, $Track_Details);
        $write->set_info('software' => 'Audio::Wav', 'artist' => $artist, 'name' => $name);

        $read->move_to($pos[0]);
        my $read_len = 0;
        while($read_len < $track_len) {
                my $remain = $track_len - $read_len;
                my $buf_len = $remain >= $Max_Buf_Len ? $Max_Buf_Len : $remain;
                my $buf = $read->read_raw($buf_len);
                my $buf_len2 = length($buf);
                $read_len += $buf_len2;
                $write->write_raw($buf, $buf_len2);
                if ($buf_len != $buf_len2) {
                        print "Last track: $read_len out of $track_len bytes\n";
                        last;
                }
        }
        $write->finish();
}

exit;

sub find_cue_file {
        my ($file) = @_;
        if ($file =~ s/\.\w+$/.cue/) {
                return $file if -f $file;
                $file =~ s/\.cue$/.CUE/;
                return $file if -f $file;
        }
        my $dir = dirname($file);
        my $dh = new DirHandle $dir;
        if (defined $dh) {
                while(my $f = $dh->read) {
                        return "$dir/$f" if $f =~ /\.cue$/i;
                }
        }
}

sub parse_cue_file {
        my ($cuefile, $wav_in) = @_;
        my $cueh = new FileHandle $cuefile;
        return unless defined $cueh;
        my $wav_data = $wav_in->{data};
        print Data::Dumper->Dump([$wav_data]);
        return unless defined $wav_data;
        my $wav_sec = $wav_data->{length};
        my $wav_offset = $wav_data->{data_start};
        my $wav_bps = $wav_data->{bytes_sec};
        my (@tracks, $href);
        while(<$cueh>) {
                chomp;
                s/\r$//;
                if (/^\s*TRACK\s+(\d+)/i) {
                        push(@tracks, $href) if defined $href;
                        $href = { TRACK => $1 };
                } elsif (/^\s*INDEX\s+\d+\s+0?(\d+):0?(\d+):0?(\d+)/i) {
                        my $pos = $wav_offset + ($1*60 + $2) * $wav_bps + $3*$wav_bps/75;
                        $href->{POS} = exists($href->{POS}) ? [@{$href->{POS}}, $pos] : [$pos];
                } elsif (/^\s*(\w+)\s*(\S.*)/) {
                        my ($key, $val) = ($1, $2);
                        $val =~ s/^"//;
                        $val =~ s/"$//;
                        $href->{$key} = $val if defined $href;
                }
        }
        if (defined $href) {
                push @tracks, $href;
                push @tracks, { POS => [$wav_data->{total_length}] };
        }
        return @tracks;
}

Good Topic!再写个说明文档吧.
话要从bittorrent说起,很多下载的文件都把整盘CD十几首歌放在一个APE文件里,另外配一个CUE文件说明每一首歌的开始和结束时间。
一般来说文件名都叫CDImage.ape和CDImage.cue。
APE是一种无损压缩格式,我只知道一个叫Monkey's Audio的软件可以把APE格式解压成WAV格式。
CDImage.ape => CDImage.wav
把CDImage.wav和CDImage.cue放在同一个目录里就可以用上面的程序把一个大WAV文件分成几个小文件。

$ ls
CDImage.ape    CDImage.cue    CDImage.wav
$ /usr/local/bin/wavcut.pl CDImage.wav
最后别忘了存.wav文件的目录里最好只能有一个.cue文件,如果有好几个.cue文件的话,主文件名(除掉后坠.cue)必须和.wav的相同。要不然回乱套了。
很不错啊