File Coverage

blib/lib/Audio/Cuefile/Parser.pm
Criterion Covered Total %
statement 20 65 30.7
branch 0 28 0.0
condition 0 3 0.0
subroutine 7 14 50.0
pod 1 2 50.0
total 28 112 25.0


line stmt bran cond sub pod time code
1             package Audio::Cuefile::Parser;
2              
3             =head1 NAME
4              
5             Audio::Cuefile::Parser
6              
7             =head1 VERSION
8              
9             Version 0.02
10              
11             =cut
12              
13             our $VERSION = '0.02';
14              
15             =head1 SYNOPSIS
16              
17             Class to parse a cuefile and access the chewy, nougat centre.
18             Returns Audio::Cuefile::Parser::Track objects.
19              
20             =head1 USAGE
21              
22             use Audio::Cuefile::Parser;
23              
24             my $filename = 'filename.cue';
25              
26             my $cue = Audio::Cuefile::Parser->new($filename);
27              
28             my ($audio_file, $cd_performer, $cd_title) =
29             ($cue->file, $cue->performer, $cue->title);
30              
31             foreach my $track ($cue->tracks) {
32              
33             my ($position, $index, $performer, $title) =
34             ($track->position, $track->index, $track->performer, $track->title);
35              
36             print "$position $index $performer $title";
37             }
38              
39             =cut
40              
41 2     2   60174 use warnings;
  2         6  
  2         64  
42 2     2   12 use strict;
  2         4  
  2         70  
43              
44 2     2   9 use Carp qw/croak/;
  2         8  
  2         154  
45 2     2   496842 use Class::Struct qw/struct/;
  2         4473  
  2         13  
46 2     2   2396 use IO::File;
  2         25427  
  2         370  
47              
48             # Class specifications
49             BEGIN {
50 2     2   22 struct 'Audio::Cuefile::Parser' => {
51             cuedata => '$',
52             cuefile => '$',
53             file => '$',
54             performer => '$',
55             title => '$',
56             _tracks => '@',
57             };
58              
59 2         2192 struct 'Audio::Cuefile::Parser::Track' => {
60             index => '$',
61             performer => '$',
62             position => '$',
63             title => '$',
64             };
65             }
66              
67             {
68             # Over-ride Class::Struct's constructor so
69             # we can install some custom subs
70 2     2   1374 no warnings 'redefine';
  2         3  
  2         1908  
71              
72             sub new {
73 0 0   0 0   my $class = shift or croak 'usage: '.__PACKAGE__.'->new($filename)';
74 0 0         my $cuefile = shift or croak 'no cue file specified';
75 0 0         -e $cuefile or croak "$cuefile does not exist";
76              
77 0           my $self = bless {}, $class;
78              
79 0           $self->cuefile($cuefile);
80              
81 0           $self->_loadcue;
82 0           $self->_parse;
83              
84 0           return $self;
85             }
86             }
87              
88             # Load .cue file's contents into memory
89             sub _loadcue {
90 0     0     my $self = shift;
91 0           my $cuefile = $self->cuefile;
92              
93 0           my $data = join "",
94             IO::File->new($cuefile, 'r')->getlines;
95              
96 0           $self->cuedata($data);
97             }
98              
99             # Parse text and dispatch headers and data into
100             # their respective methods
101             sub _parse {
102 0     0     my $self = shift;
103              
104 0 0         my $data = $self->cuedata or return;
105              
106 0           my ($header, $tracks) = (
107             $data =~ m{
108             \A # start of string
109             (.*?) # capture all header text
110             (^ \s* TRACK .*) # capture all tracklist text
111             \z # end of string
112             }xms
113             );
114              
115 0           $self->_parse_header($header);
116 0           $self->_parse_tracks($tracks);
117             }
118              
119             # Process each pair and dispatch
120             # value to object mutator
121             sub _parse_header {
122 0     0     my ($self, $header) = @_;
123              
124 0 0         $header or return;
125              
126 0           my @lines = split /\r*\n/, $header;
127              
128              
129             LINE:
130 0           foreach my $line (@lines) {
131 0           _strip_spaces($line);
132              
133 0 0         $line =~ m/\S/ or next LINE;
134              
135 0           my ($keyword, $data) = (
136             $line =~ m/
137             \A # anchor at string beginning
138             (\w+) # capture keyword (e.g. FILE, PERFORMER, TITLE)
139             \s+ ['"]? # optional quotes
140             (.*?) # capture all text as keyword's value
141             (?: # non-capture cluster
142             ['"] # quote, followed by
143             (?:
144             \s+ # spacing, followed by
145             \w+ # word (e.g. MP3, WAVE)
146             )? # make cluster optional
147             )?
148             \z # anchor at line end
149             /xms
150             );
151              
152 0 0 0       ($keyword && $data) or next LINE;
153              
154 0           $keyword = lc $keyword;
155              
156 0           my %ISKEYWORD = map { $_ => 1 } qw/file performer title/;
  0            
157              
158 0 0         if ( $ISKEYWORD{$keyword} ) {
159             # print "\$self->$keyword($data)\n";
160 0           $self->$keyword($data);
161             }
162             }
163             }
164              
165             # Walk through the track data, line by line,
166             # creating track objects and populating them
167             # as we go
168             sub _parse_tracks {
169 0     0     my ($self, $tracks) = @_;
170              
171 0 0         $tracks or return;
172              
173 0           my @lines = split /\r*\n/, $tracks;
174              
175 0           my @tracks;
176              
177 0           foreach my $line (@lines) {
178 0           _strip_spaces($line);
179              
180             # TRACK 01
181             # TRACK 02 AUDIO
182 0 0         $line =~ /\A TRACK \s+ (\d+) .* \z/xms
183             and push @tracks, Audio::Cuefile::Parser::Track->new(position => $1);
184              
185 0 0         next unless @tracks;
186              
187             # TITLE Track Name
188             # TITLE "Track Name"
189             # TITLE 'Track Name'
190 0 0         $line =~ /\A TITLE \s+ ['"]? (.*?) ['"]? \z/xms
191             and $tracks[-1]->title($1);
192              
193             # PERFORMER Artist Name
194             # PERFORMER "Artist Name"
195             # PERFORMER 'Artist Name'
196 0 0         $line =~ /\A PERFORMER \s+ ['"]? (.*?) ['"]? \z/xms
197             and $tracks[-1]->performer($1);
198              
199             # INDEX 01 06:32:20
200 0 0         $line =~ /\A INDEX \s+ (?: \d+ \s+) ([\d:]+) \z/xms
201             and $tracks[-1]->index($1);
202             }
203              
204             # Store them for safe keeping
205 0           $self->_tracks(\@tracks);
206             }
207              
208             sub tracks {
209 0     0 1   @{ shift->_tracks };
  0            
210             }
211              
212             # strip leading and trailing whitespace from input string
213             sub _strip_spaces {
214 0     0     $_[0] =~ s/
215             (?:
216             \A \s+
217             |
218             \s+ \z
219             )
220             //xms;
221             }
222              
223             =head1 CUEFILE METHODS
224              
225             =head2 $cue->tracks
226              
227             Returns a list of Audio::Cuefile::Parser::Track objects.
228              
229             =head2 $cue->file
230              
231             Returns the filename associated with the FILE keyword from
232             the .cue's headers (i.e. the audio file that the .cue file
233             is describing).
234              
235             =head2 $cue->performer
236              
237             The audio file's performer.
238              
239             =head2 $cue->title
240              
241             The title of the audio file.
242              
243             =head1 TRACK METHODS
244              
245             =head2 $track->index
246              
247             Timestamp that signifies the track's beginning.
248              
249             =head2 $track->performer
250              
251             The track's performer.
252              
253             =head2 $track->position
254              
255             The track's position in the audio file.
256              
257             =head2 $track->title
258              
259             Track title.
260              
261             =cut
262              
263             =head1 AUTHOR
264              
265             Matt Koscica
266              
267             =head1 BUGS
268              
269             Probably a few, the regexes are very simple.
270              
271             Please report any bugs or feature requests to
272             C, or through the web interface at
273             L.
274             I will be notified, and then you'll automatically be notified of progress on
275             your bug as I make changes.
276              
277             =head1 COPYRIGHT & LICENSE
278              
279             Copyright 2005-2010 Matt Koscica, all rights reserved.
280              
281             This program is free software; you can redistribute it and/or modify it
282             under the same terms as Perl itself.
283              
284             =cut
285              
286             1; # End of Audio::Cuefile::Parser