File Coverage

blib/lib/MP3/Cut/Gapless.pm
Criterion Covered Total %
statement 60 65 92.3
branch 17 24 70.8
condition 4 7 57.1
subroutine 7 7 100.0
pod 3 3 100.0
total 91 106 85.8


line stmt bran cond sub pod time code
1             package MP3::Cut::Gapless;
2              
3 2     2   2035770 use strict;
  2         6  
  2         1981  
4              
5             our $VERSION = '0.03';
6              
7             require XSLoader;
8             XSLoader::load('MP3::Cut::Gapless', $VERSION);
9              
10             sub new {
11 29     29 1 1704 my ( $class, %args ) = @_;
12            
13 29 50 66     109 if ( !$args{file} && !$args{cue} ) {
14 0         0 die "Either file or cue argument must be specified";
15             }
16            
17 29         88 my $self = bless \%args, $class;
18            
19 29 100       114 if ( $self->{cue} ) {
20 2         11 $self->_parse_cue;
21             }
22            
23 29 50       1004 if ( !-f $self->{file} ) {
24 0         0 die "Invalid file: $self->{file}";
25             }
26            
27 29 100       95 if ( $self->{cache_dir} ) {
28 3         32 require Digest::MD5;
29 3         15 require File::Spec;
30            
31 3 100       90 if ( !-d $self->{cache_dir} ) {
32 1         5 require File::Path;
33 1         11155 File::Path::mkpath( $self->{cache_dir} );
34             }
35            
36             # Cache key is filename + size + mtime so if the file changes
37             # we will not read a stale cache file.
38 3         104 my ($size, $mtime) = (stat $self->{file})[7, 9];
39 3         112 $self->{cache_file} = File::Spec->catfile(
40             $self->{cache_dir},
41             Digest::MD5::md5_hex( $self->{file} . $size . $mtime ) . '.mllt'
42             );
43             }
44            
45             # Pre-scan the file for the range we will be cutting
46 29         110 $self->_init;
47            
48 28         113 return $self;
49             }
50              
51             sub tracks {
52 2     2 1 229 my $self = shift;
53            
54 2 50       7 return @{ $self->{_tracks} || [] };
  2         13  
55             }
56              
57             sub write {
58 4     4 1 32 my ( $self, $track, $filename ) = @_;
59            
60 4 50       12 if ( !$filename ) {
61             # Default filename is "position - performer - title.mp3"
62 0         0 $filename = join(' - ', $track->{position}, $track->{performer}, $track->{title}) . '.mp3';
63             }
64            
65 4 50       111 if ( -e $filename ) {
66 0         0 warn "$filename already exists, will not overwrite\n";
67 0         0 return;
68             }
69            
70             # Reset XS counter for read()
71 4         12 $self->__reset_read();
72            
73 4         9 delete $self->{start_ms};
74 4         8 delete $self->{end_ms};
75            
76 4         9 $self->{start_ms} = $track->{start_ms};
77 4 100       14 $self->{end_ms} = $track->{end_ms} if $track->{end_ms};
78            
79 4         878 print "Writing $filename...\n";
80            
81 4         364 open my $fh, '>', $filename;
82 4         733 while ( $self->read( my $buf, 65536 ) ) {
83 4         399 syswrite $fh, $buf;
84             }
85 4         59 close $fh;
86             }
87              
88             # read() is implemented in XS
89              
90             sub _init {
91 29     29   64 my $self = shift;
92            
93 29   50     1469 open $self->{_fh}, '<', $self->{file} || die "Unable to open $self->{file} for reading";
94            
95 29         90 binmode $self->{_fh};
96            
97             # XS init
98 29         9602 $self->{_mp3c} = $self->__init(@_);
99             }
100              
101             sub _parse_cue {
102 2     2   5 my $self = shift;
103            
104 2         1262 require Audio::Cuefile::Parser;
105 2         12375 require MP3::Cut::Gapless::Track;
106 2         12 require File::Spec;
107            
108 2         19 my $cue = Audio::Cuefile::Parser->new( $self->{cue} );
109            
110 2 50       2587 if ( !$self->{file} ) {
111 2   50     44 $self->{file} = $cue->file || die "No FILE entry found in cue sheet";
112            
113             # Handle relative path
114 2         120 my ($vol, $dirs, $file) = File::Spec->splitpath( $self->{file} );
115 2 50       9 if ( $dirs !~ m{^[/\\]} ) {
116 2         58 my ($cvol, $cdirs, undef) = File::Spec->splitpath( File::Spec->rel2abs( $self->{cue} ) );
117 2         116 $self->{file} = File::Spec->rel2abs( $self->{file}, File::Spec->catdir($cvol, $cdirs) );
118             }
119             }
120            
121 2         6 $self->{_tracks} = [];
122 2         9 for my $track ( $cue->tracks ) {
123 8         68 push @{ $self->{_tracks} }, MP3::Cut::Gapless::Track->new($track);
  8         38  
124             }
125            
126             # Set end_ms values, last track will fill to the end
127 2         7 for ( my $i = 0; $i < scalar @{ $self->{_tracks} } - 1; $i++ ) {
  8         45  
128 6         27 $self->{_tracks}->[$i]->{end_ms} = $self->{_tracks}->[$i + 1]->{start_ms};
129             }
130             }
131              
132             sub DESTROY {
133 29     29   61365 my $self = shift;
134            
135 29         567 close $self->{_fh};
136            
137 29 100       686 $self->__cleanup( $self->{_mp3c} ) if exists $self->{_mp3c};
138             }
139              
140             1;
141             __END__