File Coverage

blib/lib/CDDB/Fake.pm
Criterion Covered Total %
statement 128 135 94.8
branch 43 52 82.6
condition 6 8 75.0
subroutine 23 29 79.3
pod 14 15 93.3
total 214 239 89.5


line stmt bran cond sub pod time code
1             # CDDB::Fake.pm -- CDDB File Faker
2             # RCS Info : $Id: CDDB-Fake.pm,v 1.5 2004/08/13 13:38:04 jv Exp $
3             # Author : Johan Vromans
4             # Created On : Tue Mar 25 22:38:32 2003
5             # Last Modified By: Johan Vromans
6             # Last Modified On: Fri Aug 13 15:28:55 2004
7             # Update Count : 139
8             # Status : Unknown, Use with caution!
9              
10             =head1 NAME
11              
12             CDDB::Fake - Fake CDDB entries if you have none
13              
14             =head1 SYNOPSIS
15              
16             use CDDB::Fake;
17             my $cddb = CDDB::Fake->new("music/Egg/Egg/.nocddb");
18             print "Artist: ", $cddb->artist, "\n";
19             foreach my $track ( $cddb->tracks ) {
20             print "Track ", $track->number, ": ", $track->title, "\n";
21             }
22              
23             =head1 DESCRIPTION
24              
25             Sometimes there's no CDDB file available for a piece of music. For
26             example, when you created a collection of tracks from other albums. In
27             this case, a text file containing the name of the artist / album,
28             followed by a mere list of the track titles can be used as a
29             fall-back.
30              
31             CDDB::Fake implements a part of the CDDB::File API based on manually
32             crafted fall-back files.
33              
34             I've adopted the convention to name files with CDDB data C<.cddb>, and
35             the fake data C<.nocddb>.
36              
37             For example, you can cut the results of a search at Gracenote
38             (cddb.com) and paste it into the file .nocddb. For example:
39              
40             Birelli Lagrene / Standards
41              
42             1. C'est Si Bon
43             2. Softly, As in a Morning Sunrise
44             3. Days of Wine and Roses
45             ...
46             12. Nuages
47              
48             The track titles may be optionally followed by trailing TABs (not
49             spaces) and a MM:SS time indicator (which may have a leading space if
50             it's M:SS).
51              
52             Extra track information can be passed on lines that follow the track
53             title. These lines must start with whitespace, and may not begin with
54             a number. Anything that follows the list of tracks is considered extra disc information. For example:
55              
56             Birelli Lagrene / Standards
57              
58             1. C'est Si Bon
59             Original version
60             2. Softly, As in a Morning Sunrise
61             Live recording.
62             Probably incomplete.
63             3. Days of Wine and Roses
64             ...
65             12. Nuages
66              
67             This album was recorded in the Olympia Studios in Paris.
68              
69             Multiple lines of additional info are concatenated with newlines
70             inbetween. However, if one of the lines contains C<\n> (that's
71             backslash-n), all lines are conctenated using a single whitespace, and
72             the C<\n>'s are turned into real newlines.
73              
74             A tool is included to generate a fake file from the names of the files
75             in the directory.
76              
77             B CDDB::Fake implements only a part of the CDDB::File API.
78              
79             =cut
80              
81             package CDDB::Fake;
82              
83             $VERSION = "2.00";
84              
85 4     4   122277 use strict;
  4         12  
  4         166  
86 4     4   22 use warnings;
  4         7  
  4         128  
87 4     4   25 use Carp;
  4         10  
  4         7977  
88              
89             =head1 METHODS
90              
91             =over 4
92              
93             =item new I
94              
95             The new() package method takes the name of a file, and parses it. A
96             CDDB::Fake object is then created from the file data.
97              
98             =cut
99              
100             sub new {
101 4     4 1 58 my ($pkg, $file) = @_;
102              
103 4         24 my $self = {
104             _title => "",
105             _artist => "",
106             _extd => "",
107             };
108              
109 4         12 my $fh;
110 4 50       19 if ( ref($file) ) {
111             # For testing.
112 4         11 $fh = $file;
113             }
114             else {
115 0 0       0 open($fh, $file) or croak("$file: $!\n");
116             }
117 4         8 my $off = 150;
118 4         9 my $state = 0;
119 4         8 my $va = 0;
120 4         8 my $to;
121 4         35 while ( <$fh> ) {
122 52 100       169 next unless /\S/;
123 44         172 s/[\r\n]+$//;
124              
125             # State 0: Looking for artist/title.
126 44 100       110 if ( $state == 0 ) {
127 4 100       31 if ( /^\s*(.+)\s+\/\s+(.*)/ ) {
128 2         8 $self->{_artist} = _deblank($1);
129 2         9 $self->{_title} = _deblank($2);
130             }
131             else {
132             # Eponymous.
133 2         11 $self->{_artist} = $self->{_title} = _deblank($_);
134             }
135 4         22 $va = lc($self->{_artist}) eq "various";
136 4         8 $state++;
137 4         15 next;
138             }
139              
140             # State 1: Processing tracks.
141 40 100       122 if ( $state == 1 ) {
142 38 100 66     222 if ( /^\s*(\d+)\.?\s+(.*)/ ) {
    100          
143 24         57 my $tn = 0 + $1;
144 24         52 my $tt = $2;
145 24         29 my $tl;
146 24 100       77 if ( $tt =~ /^(.*?)\t+ ?(\d+):(\d\d)\s*$/ ) {
147 12         25 $tt = _deblank($1);
148 12         33 $tl = 60 * $2 + $3;
149 12         20 $self->{_length} += $tl;
150             }
151             else {
152 12         22 $tt = _deblank($tt);
153             }
154 24         38 my $art = $self->{_artist};
155 24 100       62 if ( $va ) {
156 12 100       61 if ( $tt =~ /^(.+?):\s+(.*)/ ) {
    100          
157 2         7 $art = _deblank($1);
158 2         4 $tt = _deblank($2);
159             }
160             elsif ( $tt =~ /^(.+?)\s+\/\s+(.*)/ ) {
161 2         8 $art = _deblank($1);
162 2         8 $tt = _deblank($2);
163             }
164             }
165 24         25 push(@{$self->{_tracks}},
  24         94  
166             $to = CDDB::Fake::Track->new($art, $tn, $tt,
167             $tl, $off, ""));
168 24 100       60 $off += 75 * $tl if $tl;
169 24         79 next;
170             }
171             elsif ( defined($to) && /^\s+(.+)/ ) {
172 10         30 $to->_extd_append($1);
173 10         34 next;
174             }
175             else {
176 4         8 $state++;
177             }
178             }
179              
180             # State 2: Remainder (ext info).
181 6 100       28 if ( $state == 2 ) {
182 4         13 $self->{_extd} = $_ . "\n";
183 4         8 $state++;
184 4         596 next;
185             }
186              
187             # State 3: Rest of ext info.
188 2         29 $self->{_extd} .= $_ . "\n";
189              
190             }
191              
192 4         718 $self->{_extd} = _newlines($self->{_extd});
193              
194 4         23 bless $self, $pkg;
195             }
196              
197             sub _deblank {
198 38     38   63 my $t = shift;
199 38         58 for ( $t ) {
200 38         70 s/^\s+//;
201 38         81 s/\s+$//;
202 38         164 s/\s+/ /g;
203 38         104 return $_;
204             }
205             }
206              
207             sub _newlines {
208 20     20   31 my $t = shift;
209 20         40 for ( $t ) {
210 20 100       91 return $_ unless /\\n/;
211 4         8 s/\s\n\s/ /g;
212 4         15 s/\\n/\n/g;
213 4         17 return $_;
214             }
215             }
216              
217             =item artist
218              
219             Returns the name of the artist.
220              
221             =cut
222              
223             sub artist {
224 4     4 1 7 my ($self) = @_;
225 4         21 $self->{_artist};
226             }
227              
228             =item title
229              
230             Returns the name of the album.
231              
232             =cut
233              
234             sub title {
235 4     4 1 1569 my ($self) = @_;
236 4         24 $self->{_title};
237             }
238              
239             =item track_count
240              
241             Returns the number of tracks.
242              
243             =cut
244              
245             sub track_count {
246 2     2 1 6 my ($self) = @_;
247 2         5 scalar(@{$self->{_tracks}});
  2         25  
248             }
249              
250             =item tracks
251              
252             Returns a list of track objects.
253              
254             =cut
255              
256             sub tracks {
257 8     8 1 16 my ($self) = @_;
258 8         12 @{$self->{_tracks}};
  8         37  
259             }
260              
261             =item id
262              
263             =item all_ids
264              
265             Returns the (fake) id for this disc.
266              
267             =cut
268              
269 2     2 1 7 sub id { "00000000" }
270              
271 0     0 1 0 sub all_ids { ("00000000") }
272              
273             =item year
274              
275             =item genre
276              
277             =item submitted_by
278              
279             =item processed_by
280              
281             These methods return empty strings since the information is not
282             available in CDDB::Fake files.
283              
284             =cut
285              
286 0     0 1 0 sub year { "" }
287 0     0 1 0 sub genre { "" }
288 0     0 1 0 sub submitted_by { "" }
289 0     0 1 0 sub processed_by { "" }
290 0     0 0 0 sub revision { 1 }
291              
292             =item length
293              
294             This method will return the accumulated length of all the tracks,
295             provided this information is present in the fake file.
296              
297             =cut
298              
299             sub length {
300 4     4 1 8 my ($self) = @_;
301 4 100       29 $self->{_length} || 0;
302             }
303              
304             =item extd
305              
306             Returns the extended disc information, that is everything that follows
307             the list of tracks in the fake file.
308              
309             =cut
310              
311             sub extd {
312 4     4 1 8 my ($self) = @_;
313 4 50       622 $self->{_extd} || "";
314             }
315              
316             =item as_cddb
317              
318             Returns the data in the format of a CDDB entry.
319              
320             =cut
321              
322             sub as_cddb {
323 2     2 1 1908 my ($self) = @_;
324              
325 2         6 my $ret = "";
326              
327             # Writing CDDB data requires some line breaking and such.
328             my $out = sub {
329 32     32   49 my ($tag, $desc) = @_;
330 32         45 $desc =~ s/\n/\\n/g;
331 32         39 $desc =~ s/\\n$//;
332 32         35 $tag .= "=";
333 32         57 $desc = $tag . $desc;
334 32         44 for ( ;; ) {
335 33         53 my $t = substr($desc,0,70,$tag);
336 33         53 $ret .= $t . "\n";
337 33 100       118 last if $desc eq $tag;
338             }
339 2         13 };
340              
341 2         9 my @tracks = $self->tracks;
342              
343             # Header.
344 2         4 $ret = "# xmcd 2.0 CD database file\n" .
345             "# Copyright (C) 1996,2004 Johan Vromans\n" .
346             "#\n";
347              
348 2 100       8 if ( $self->length ) {
349 1         2 $ret .= "# Track frame offsets:\n";
350 1         3 foreach ( @tracks ) {
351 6         11 $ret .= "#\t" . $_->offset . "\n";
352             }
353 1         3 $ret .= "#\n" .
354             "# Disc length: " . $self->length . " seconds\n" .
355             "#\n";
356             }
357              
358 2         8 $out->("DISCID", $self->id);
359 2         9 $out->("DTITLE", $self->artist . " / " . $self->title);
360              
361 2         4 my $i;
362 2         9 for ( $i=0; $i < @tracks; $i++ ) {
363 12 50       49 $out->("TTITLE$i", $tracks[$i]->title)
364             if defined $tracks[$i];
365             }
366              
367 2         8 $out->("EXTD", $self->extd);
368              
369 2         13 for ( $i=0; $i < @tracks; $i++ ) {
370 12         30 $out->("EXTT$i", $tracks[$i]->extd);
371             }
372 2         6 $out->("PLAYORDER", "");
373 2         20 $ret;
374             }
375              
376             =back
377              
378             =cut
379              
380             package CDDB::Fake::Track;
381              
382             sub new {
383 24     24   46 my ($pkg, $disc, $num, $tt, $len, $off, $extd) = @_;
384 24         132 bless [ $disc, $num, $tt, $len, $off, $extd ], $pkg;
385             }
386              
387             =pod
388              
389             Track objects provide the following methods:
390              
391             =over 4
392              
393             =item artist
394              
395             The artist, usually the same as the artist of the disc.
396              
397             =cut
398              
399 3 50   3   21 sub artist { shift->[0] || ""}
400              
401             =item number
402              
403             The track number, starting with 1.
404              
405             =cut
406              
407 5 50   5   44 sub number { shift->[1] || 0 }
408              
409             =item title
410              
411             The track title.
412              
413             =cut
414              
415 17 50   17   71 sub title { shift->[2] || ""}
416              
417             =item length
418              
419             The track length (in seconds).
420              
421             This will be zero unless a track length was specified in the fake info.
422              
423             =cut
424              
425 3 100   3   26 sub length { shift->[3] || 0}
426              
427             =item offset
428              
429             The track offset.
430              
431             This will be bogus unless track offsets could be estimated using the
432             length information.
433              
434             =cut
435              
436 7 50   7   30 sub offset { shift->[4] || 0 }
437              
438             =item extd
439              
440             This extended track info, if present.
441              
442             =cut
443              
444             sub extd {
445 16   100 16   80 CDDB::Fake::_newlines(shift->[5] || "");
446             }
447              
448             sub _extd_append {
449 10     10   25 my ($self, $text) = @_;
450 10 100 66     87 if ( defined($self->[5]) && CORE::length($self->[5]) > 0 ) {
451 4         77 $self->[5] .= " " . $text;
452             }
453             else {
454 6         17 $self->[5] = $text;
455             }
456             }
457              
458             =head1 EXAMPLES
459              
460             It is often handy to generalize the handling of real and fake files:
461              
462             use CDDB::File; # the real one
463             use CDDB::Fake; # the fake one
464             use Carp;
465              
466             # Return a CDDB::File object if a .cddb file is present, otherwise
467             # return a CDDB::Fake onkect from a .nocddb file, if present.
468              
469             sub cddb_info($) {
470             my $df = shift;
471             croak("cddb_info(dir)\n") unless -d $df;
472             return CDDB::File->new("$df/.cddb") if -s "$df/.cddb";
473             return CDDB::Fake->new("$df/.nocddb") if -s "$df/.nocddb";
474             undef;
475             }
476              
477             =head1 SEE ALSO
478              
479             L.
480              
481             =head1 AUTHOR
482              
483             Johan Vromans
484              
485             =head1 COPYRIGHT
486              
487             This programs is Copyright 2003,2004, Squirrel Consultancy.
488              
489             This program is free software; you can redistribute it and/or modify
490             it under the terms of the Perl Artistic License or the GNU General
491             Public License as published by the Free Software Foundation; either
492             version 2 of the License, or (at your option) any later version.
493              
494             =cut
495              
496             1;
497