File Coverage

blib/lib/Audio/Metadata/Flac.pm
Criterion Covered Total %
statement 90 92 97.8
branch 19 20 95.0
condition 2 3 66.6
subroutine 17 17 100.0
pod 4 4 100.0
total 132 136 97.0


line stmt bran cond sub pod time code
1             package Audio::Metadata::Flac;
2             {
3             $Audio::Metadata::Flac::VERSION = '0.16';
4             }
5             BEGIN {
6 3     3   21454 $Audio::Metadata::Flac::VERSION = '0.15';
7             }
8              
9 3     3   20 use strict;
  3         5  
  3         105  
10 3     3   16 use warnings;
  3         34  
  3         110  
11 3     3   3018 use autodie;
  3         77494  
  3         22  
12              
13 3     3   30002 use Any::Moose;
  3         71043  
  3         26  
14 3     3   2183 use List::Util qw/first/;
  3         6  
  3         453  
15 3     3   1062 use IO::File;
  3         15755  
  3         585  
16 3     3   3783 use Audio::Metadata::Flac::Block;
  3         10  
  3         5165  
17              
18              
19             extends 'Audio::Metadata';
20              
21             has vendor_string => ( isa => 'Str', is => 'rw', );
22             has _block_chain_size_saved => ( isa => 'Int', is => 'rw', );
23              
24             has _block_chain => (
25             isa => 'Audio::Metadata::Flac::Block',
26             is => 'rw',
27             lazy_build => 1,
28             );
29             has _comments_block => (
30             isa => 'Audio::Metadata::Flac::Block::Comments',
31             is => 'rw',
32             lazy_build => 1,
33             );
34              
35              
36             __PACKAGE__->meta->make_immutable;
37              
38              
39             # FLAC constants.
40             my $FLAC_MARKER = 'fLaC';
41              
42             # Delegate variable manipulation to comments block object.
43             # Moose delegation doesn't work for delegates built lazily.
44 9     9 1 42 sub get_var { shift->_comments_block->get_var(@_) }
45 24     24 1 126 sub set_var { shift->_comments_block->set_var(@_) }
46 7     7 1 1116 sub vars_as_hash { shift->_comments_block->comments(@_) }
47              
48              
49             sub _openr_with_format_check {
50             ## Opens the file for reading, checking for format marker.
51 12     12   25 my $self = shift;
52              
53             # Open the file for reading.
54 12         76 my $fh = $self->path->openr;
55              
56             # Check lead marker to make sure the file is FLAC.
57 12         1983 $fh->read(my $marker, length $FLAC_MARKER);
58 12 50       1099 die "Not a FLAC file: it does not begin with \"$FLAC_MARKER\"" unless $marker eq $FLAC_MARKER;
59              
60 12         29 return $fh;
61             }
62              
63              
64             sub _build__block_chain {
65             ## Populates _block_chain property, reading all metadata blocks into linked list.
66 12     12   20 my $self = shift;
67              
68 12         87 my $fh = $self->_openr_with_format_check;
69 12         22 my $chain_size;
70 12         34 my ($head, $tail);
71 0         0 my $is_last;
72 12         18 do {
73 48         303 (my $block, $is_last) = Audio::Metadata::Flac::Block->new_from_fh($fh);
74 48 100       284 if (!$tail) {
75 12         24 $tail = $head = $block;
76             }
77             else {
78 36         323 $tail->next($block);
79 36         69 $tail = $block;
80             }
81 48         294 $chain_size += $block->size;
82             } while (!$is_last);
83              
84 12         70 $fh->close;
85 12         354 $self->_block_chain_size_saved($chain_size);
86 12         150 return $head;
87             }
88              
89              
90             sub _build__comments_block {
91             ## Builds _comments_block attribute.
92 12     12   20 my $self = shift;
93              
94             # Traverse block chain looking for comment block. Return undef if not found.
95 12         77 for (my $block = $self->_block_chain; defined $block; $block = $block->next) {
96 36 100       326 return $block if ref($block) =~ /::Comments$/;
97             }
98 0         0 return;
99             }
100              
101              
102             sub _adjust_padding {
103             ## Removes all padding blocks from the block chain and adds one of given size
104             ## at the end.
105 7     7   12 my $self = shift;
106 7         9 my ($new_padding_size) = @_;
107              
108             # Walk the chain, removing all padding blocks.
109 7         8 my $removed_count = 0;
110 7         10 my $last_block;
111 7         31 for (my $block = $self->_block_chain; defined $block; $block = $block->next) {
112              
113 21 100 66     188 if ($block->next && ref($block->next) =~ /::Padding$/) {
114             # Next block is padding, remove it.
115              
116 7         35 $block->next($block->next->next);
117 7         62 $removed_count++;
118             }
119 21         66 $last_block = $block;
120             }
121              
122             # Add padding of specified size to the end.
123 7         15 my $class = 'Audio::Metadata::Flac::Block::Padding';
124 7         35 my $added_padding_size = $new_padding_size + (($removed_count - 1) * $class->header_size);
125 7         65 $last_block->next($class->new(chr(0) x $added_padding_size));
126             }
127              
128              
129             sub save {
130             ## Overriden.
131 8     8 1 27 my $self = shift;
132              
133             # Measure current block chain size and available padding size.
134 8         16 my ($block_chain_size, $padding_avail) = (0, 0);
135 8         47 for (my $block = $self->_block_chain; defined $block; $block = $block->next) {
136 32         94 $block_chain_size += $block->size;
137 32 100       186 $padding_avail += $block->content_size if ref($block) =~ /::Padding$/;
138             }
139              
140             # See how much chain size changed and adjust padding respectively.
141 8         35 my $block_chain_size_delta = $block_chain_size - $self->_block_chain_size_saved;
142              
143 8         13 my $with_new_file = 1;
144 8 100       23 if ($block_chain_size_delta <= $padding_avail) {
145             # Block chain is small enough to be rewritten in the same space.
146              
147 7         30 $self->_adjust_padding($padding_avail - $block_chain_size_delta);
148 7         21 $with_new_file = 0;
149             }
150              
151             # Determine output file name - either the same as input or temporary.
152 8         53 my $out_file_name = $self->file_path;
153 8 100       1480 $out_file_name .= ".$$.tmp" if $with_new_file;
154              
155             # Write the chain out.
156 8 100       83 my $fh = IO::File->new($out_file_name, $with_new_file ? '>' : '+<');
157 8 100       1067 if ($with_new_file) {
158 1         5 $fh->syswrite($FLAC_MARKER);
159             }
160             else {
161 7         54 $fh->seek(length($FLAC_MARKER), 0);
162             }
163 8         160 for (my $block = $self->_block_chain; defined $block; $block = $block->next) {
164 32         744 $fh->syswrite($block->as_string);
165             }
166              
167 8 100       222 if ($with_new_file) {
168             # Copy sound stream to the temporary file.
169              
170 1         9 my $orig_fh = $self->path->openr;
171 1         108 $orig_fh->seek(length($FLAC_MARKER) + $self->_block_chain_size_saved, 0);
172 1         14 while ($orig_fh->read(my $buff, 1024)) {
173 2         51 $fh->syswrite($buff);
174             }
175              
176 1         23 $fh->close;
177 1         19 $orig_fh->close;
178 1         14 rename($out_file_name, $self->file_path);
179             }
180             else {
181 7         23 $fh->close;
182             }
183              
184             # Update saved block chain size.
185 8         2494 $self->_block_chain_size_saved($block_chain_size);
186             }
187              
188              
189 3     3   23 no Any::Moose;
  3         5  
  3         18  
190              
191              
192             1;
193              
194              
195             __END__