]> diplodocus.org Git - flac-archive/blob - flacsplit
fix tests after shortening CD filenames
[flac-archive] / flacsplit
1 #! /usr/bin/env perl
2
3 use strict;
4 use warnings;
5
6 use Getopt::Long qw(:config gnu_getopt no_ignore_case);
7 use POSIX ':sys_wait_h';
8 use Pod::Usage;
9
10 my $quiet;
11 my $verbose;
12
13 sub verbose {
14 $verbose and print(STDERR $_) for @_;
15 }
16
17 sub run_or_die {
18 my $command = shift;
19 my $status;
20
21 $verbose and print(STDERR "$command\n");
22 $status = system($command);
23
24 if (WIFEXITED($status)) {
25 if (($status = WEXITSTATUS($status)) != 0) {
26 die("$command exited with status $status");
27 }
28 } elsif (WIFSIGNALED($status)) {
29 die("$command killed with signal ", WTERMSIG($status));
30 } elsif (WIFSTOPPED($status)) {
31 die("$command stopped with signal ", WSTOPSIG($status));
32 } else {
33 die("Major horkage on system($command): \$? = $? \$! = $!");
34 }
35 }
36
37 sub tformat {
38 return sprintf('%02d:%02d.%02d', @_);
39 }
40
41 sub get_decode_args {
42 my $fn = shift;
43 my @l;
44
45 open(F, '-|', 'metaflac', '--no-utf8-convert', '--export-cuesheet-to=-',
46 $fn);
47 while (<F>) {
48 /INDEX 01 (\d\d):(\d\d):(\d\d)$/ or next;
49 push(@l, [$1, $2, $3]);
50 }
51
52 my @args;
53 for my $i (0..$#l) {
54 my $arg = ["--skip=" . tformat(@{$l[$i]})];
55 my $next = $l[$i+1];
56 if (defined($next)) {
57 if ($next->[2] == 0) {
58 if ($next->[1] == 0) {
59 push(@$arg, '--until=' . tformat($next->[0] - 1, 59, 74));
60 } else {
61 push(@$arg, '--until=' . tformat($next->[0], $next->[1] - 1,
62 74));
63 }
64 } else {
65 push(@$arg, '--until=' . tformat($next->[0], $next->[1],
66 $next->[2] - 1));
67 }
68 }
69 push(@args, $arg);
70 }
71
72 if (@args == 0) {
73 die('no cue sheet');
74 }
75
76 return @args;
77 }
78
79 # Return the ARTIST, ALBUM, and DISCNUMBER followed by a list of all
80 # the lines in the file FN.
81 sub get_tags {
82 my $fp = shift;
83 my $fn = shift;
84 my $tag;
85 my $value;
86 my $artist;
87 my $album;
88 my $discnum;
89 my @tags;
90
91 while (<$fp>) {
92 chomp;
93 push(@tags, $_);
94
95 ($tag, $value) = split(/=/, $_, 2);
96
97 if (/^ARTIST=/i) {
98 $artist = $value;
99 verbose("ARTIST $artist from $fn\n");
100 } elsif (/^ALBUM=/i) {
101 $album = $value;
102 verbose("ALBUM $album from $fn\n"); # cperl-mode sucks "
103 } elsif (/^DISCNUMBER=/i) {
104 $discnum = int($value);
105 verbose("DISCNUMBER $discnum from $fn\n");
106 }
107 }
108
109 return ($artist, $album, $discnum, @tags);
110 }
111
112 sub track_tags {
113 my $h = shift;
114 my @result;
115
116 while (my ($key, $vall) = each(%$h)) {
117 for my $val (@$vall) {
118 push(@result, "$key=$val")
119 }
120 }
121
122 return @result;
123 }
124
125 sub flacsplit {
126 my $fn = shift;
127 my $artist;
128 my $album;
129 my $discnum;
130 my @tags;
131 my $outdir;
132
133 open(my $fp, '-|', 'metaflac', '--no-utf8-convert', '--export-tags-to=-',
134 $fn)
135 or die("open(metaflac --no-utf8-convert --export-tags-to=- $fn): $!");
136 ($artist, $album, $discnum, @tags) = get_tags($fp, $fn);
137 close($fp)
138 or die("close(metaflac --no-utf8-convert --export-tags-to=- $fn): $?");
139 for ($artist, $album) {
140 s|/|_|g;
141 }
142
143 -d $artist or mkdir($artist) or die("mkdir($artist): $!");
144 $outdir = "$artist/$album";
145 -d "$outdir" or mkdir("$outdir") or die("mkdir($outdir): $!");
146
147 # Go over @tags, store all [n] tags in a list keyed by n in
148 # %tracks_to_tags, store all ARTIST (not ARTIST[n]) tags in
149 # @disc_artist, and leave the rest in @tags.
150 my %tracks_to_tags;
151 my @disc_artist;
152 my @tmp;
153 my $hack = 1;
154 for my $tag (@tags) {
155 if ($tag =~ /^([^[]+)\[(\d+)]=(.*)/) {
156 push(@{$tracks_to_tags{$2}->{$1}}, $3);
157 } elsif ($tag =~ /^TITLE=(.*)/) {
158 push(@{$tracks_to_tags{$hack++}->{'TITLE'}}, $1);
159 } elsif ($tag =~ /^ARTIST=/) {
160 push(@disc_artist, $tag);
161 } else {
162 push(@tmp, $tag);
163 }
164 }
165 @tags = @tmp;
166
167 $fn =~ s/'/'\\''/g;
168
169 for my $tracknum (sort(map(int, keys(%tracks_to_tags)))) {
170 my $title = join(' ', map(split, @{$tracks_to_tags{$tracknum}->{'TITLE'}}));
171 my $outfile = join(' ',
172 (defined($discnum)
173 ? sprintf('%02d', $discnum)
174 : ()),
175 sprintf('%02d', $tracknum),
176 $title);
177 $outfile =~ s|/|_|g;
178 $outfile = "$outdir/$outfile";
179 $outfile =~ s/'/'\\''/g;
180
181 # If we have ARTIST[n] tags for this track, set @track_artist
182 # to the empty list; they will go in along with the other [n]
183 # tags.
184 my @track_artist;
185 if (exists($tracks_to_tags{$tracknum}->{'ARTIST'})) {
186 @track_artist = ();
187 } else {
188 @track_artist = @disc_artist;
189 }
190
191 my $flac_options = '';
192 my ($skip_arg, $until_arg) = @{$_[$tracknum - 1]};
193 $skip_arg ||= '';
194 $until_arg ||= '';
195 run_or_die(join(' ',
196 "flac $flac_options -cd $skip_arg $until_arg '$fn'",
197 " | flac -o '$outfile.flac' -V --no-padding --best",
198 map({ my $tag = "$_"; $tag =~ s/'/'\\''/g; ('-T', "'$tag'") }
199 @track_artist,
200 @tags,
201 "TRACKNUMBER=$tracknum",
202 track_tags($tracks_to_tags{$tracknum})),
203 '-'));
204 }
205
206 return 0;
207 }
208
209 MAIN: {
210 my $help;
211 GetOptions(
212 'quiet|q' => \$quiet,
213 'verbose|v' => \$verbose,
214 'help|h|?' => \$help,
215 ) or pod2usage();
216 $help and pod2usage(-exitstatus=>0, -verbose=>1);
217
218 @ARGV or pod2usage();
219 for my $fn (@ARGV) {
220 my @args = get_decode_args($fn);
221 flacsplit($fn, @args);
222 }
223 }