File Coverage

blib/lib/XML/FeedPP/Plugin/AddMP3.pm
Criterion Covered Total %
statement 22 80 27.5
branch 0 30 0.0
condition 0 6 0.0
subroutine 8 12 66.6
pod n/a
total 30 128 23.4


line stmt bran cond sub pod time code
1             package XML::FeedPP::Plugin::AddMP3;
2            
3             =head1 NAME
4            
5             XML::FeedPP::Plugin::AddMP3 - FeedPP Plugin for adding MP3 as item.
6            
7             =head1 SYNOPSIS
8            
9             use XML::FeedPP;
10             my $feed = XML::FeedPP->new( 'index.rss' );
11             $feed->call(AddMP3 => './mp3/test.mp3');
12             $feed->to_file('rss.xml');
13            
14             =head1 DESCRIPTION
15            
16             This plugin generate new feed item for specified MP3 file.
17            
18             $feed->call(AddMP3 => './mp3/test.mp3');
19            
20             And set default value for the item's title, author, enclosure url,
21             enclosure length, enclosure type with MP3 TAGs and INFOs.
22            
23             If use_itune option is specified, xmlns:itunes is added to feed.
24             And additional default value for itunes:author, itunes:subtitle,
25             itunes:duration, itunes:keywords are set.
26            
27             Returns added item, or undef.
28            
29             B If those values includes non-UTF-8 characters, it tries to
30             convert with Encode, or Jcode module. When both of them are avaliable,
31             It calls Carp::carp, and continues process.
32            
33             =head1 OPTIONS
34            
35             This plugin allows some optoinal arguments following:
36            
37             =over 4
38            
39             =item base_dir
40            
41             By default, url attribute of enclosure tag is set to file argument.
42             If base_dir is specified, url attribute is converted to relative path from base_dir.
43            
44             =item base_url
45            
46             If base_url is specified, url attribute is converted as relative path from base_url.
47            
48             =item link_url
49            
50             By default, link value is set to the feed's link value.
51             If link_url is specified, link value is set to link_url.
52            
53             =item use_itunes
54            
55             Use itunes name space, and add tags in the name space.
56             See also http://www.apple.com/itunes/store/podcaststechspecs.html
57            
58             =back
59            
60             For example,
61            
62             my %opt = (
63             base_dir => './mp3'
64             base_url => 'http://example.com/podcast/files',
65             link_url => 'http://example.com/podcast',
66             use_itunes => 1,
67             );
68             $feed->call(AddMP3 => './mp3/test.mp3');
69            
70             At first, URL is set to './mp3/test.mp3'.
71             Then, base_dir is specified in this case, URL is chenged to 'test.mp3'.
72             Alos base_url is specified in this case, so URL is chenged to 'http://example.com/podcast/files/test.mp3'.
73            
74             =head1 MODULE DEPENDENCIES
75            
76             L, L, L
77            
78             =head1 MODULE RECOMMENDED
79            
80             L, or L (for Japanese users)
81            
82             =head1 SEE ALSO
83            
84             L
85            
86             http://www.apple.com/itunes/store/podcaststechspecs.html (Podcast specification)
87            
88             =head1 AUTHOR
89            
90             Makio Tsukamoto
91            
92             =head1 COPYRIGHT AND LICENSE
93            
94             Copyright (c) 2007 Makio Tsukamoto. All rights reserved.
95             This program is free software; you can redistribute it
96             and/or modify it under the same terms as Perl itself.
97            
98             =cut
99            
100 4     4   231948 use strict;
  4         8  
  4         140  
101 4     4   21 use vars qw( @ISA );
  4         8  
  4         219  
102             @ISA = qw( XML::FeedPP::Plugin );
103            
104 4     4   18 use vars qw( $VERSION );
  4         9  
  4         138  
105             $VERSION = "0.02";
106            
107 4     4   20 use Carp;
  4         5  
  4         276  
108 4     4   4739 use MP3::Info;
  4         207210  
  4         403  
109 4     4   3057 use Path::Class;
  4         207879  
  4         4066  
110            
111             my $re_utf8 = qr/(?:[\x00-\x7f]|[\xC0-\xDF][\x80-\xBF]|[\xE0-\xEF][\x80-\xBF]{2}|[\xF0-\xF7][\x80-\xBF]{3})/;
112             my $use_encode = undef;
113             my $use_jcode = undef;
114            
115             sub run {
116 0     0     my $class = shift;
117 0           my $feed = shift;
118 0           &add_mp3( $feed, @_ );
119             }
120            
121             sub add_mp3 {
122 0     0     my $feed = shift;
123 0           my $file = shift;
124 0           my %opt = @_;
125             # check link
126 0 0         my $link = ($opt{'link_url'}) ? $opt{'link_url'} : $feed->link;
127 0 0 0       return unless (defined($link) and length($link));
128             # check file and get its information
129 0           my $path = Path::Class::file($file);
130 0 0         Carp::croak "File not exists - $file" if (not -f $path);
131 0 0         my $stat = File::stat::stat("$path") or Carp::croak "Failed to get file stat - $file";
132 0 0         my $tags = MP3::Info::get_mp3tag("$path") or Carp::croak "Failed to get mp3 tags - $file";
133 0 0         my $info = MP3::Info::get_mp3info("$path") or Carp::croak "Failed to get mp3 info - $file";
134             # define url
135 0           my $url = Path::Class::file($file);
136 0 0         $url = $url->relative($opt{'base_dir'}) if ($opt{'base_dir'});
137 0           $url = $url->as_foreign('Unix')->stringify;
138 0 0         if ($opt{'base_url'}) {
139 0           my $base_url = $opt{'base_url'};
140 0           $base_url =~ s/\/$//;
141 0           $url =~ s/^\///;
142 0           $url = "$base_url/$url";
143             }
144             # add item
145 0           my $item = $feed->add_item($link);
146 0           $item->guid($url);
147 0           $item->pubDate($stat->mtime);
148 0           my $podcast = {
149             'title' => $tags->{'TITLE'},
150             'author' => $tags->{'ARTIST'},
151             'description' => '', # CDATA is allowed
152             'enclosure@url' => $url,
153             'enclosure@length' => $stat->size,
154             'enclosure@type' => 'audio/mpeg',
155             };
156 0           foreach my $key (%{$podcast}) {
  0            
157 0           my $value = &rewrite_value($podcast->{$key});
158 0 0         if (defined($value)) {
159 0 0         if (my $error = &is_invalid($value)) {
160 0           Carp::carp "$error - $file->$key, '$value'";
161             }
162 0           $item->set($key => $value);
163             }
164             }
165             # for itunes
166 0 0         if ($opt{'use_itunes'}) {
167 0           $feed->xmlns('xmlns:itunes' => 'http://www.itunes.com/DTDs/Podcast-1.0.dtd');
168 0           my $itunes = {
169             'itunes:author' => $tags->{'ARTIST'},
170             'itunes:subtitle' => [$tags->{'ALBUM'}, $tags->{'TRACKNUM'}],
171             'itunes:summary' => '', # CDATA is disallowed
172             'itunes:duration' => $info->{'TIME'},
173             'itunes:keywords' => [$tags->{'ARTIST'}, $tags->{'YEAR'}],
174             };
175 0           foreach my $key (%{$itunes}) {
  0            
176 0           my $value = &rewrite_value($itunes->{$key});
177 0 0         $item->set($key => $value) if (defined($value));
178             }
179             }
180 0           return $item;
181             }
182            
183             sub is_invalid {
184 0     0     my $value = shift;
185 0 0         return "Not utf8 character, ignored" unless ($value =~ /^(?:$re_utf8)*$/);
186 0           return;
187             }
188            
189             sub rewrite_value {
190 0     0     my $value = shift;
191 0 0         if (UNIVERSAL::isa($value, 'ARRAY')) {
192 0           my @values = map { s/^\s+//s; s/\s+$//s; $_ } @{$value}; #}
  0            
  0            
  0            
  0            
193 0           @values = map { &encode_value($_) } grep { length($_) } @values;
  0            
  0            
194 0           $value = join(', ', @values);
195             } else {
196 0           $value = &encode_value($value);
197             }
198 0 0 0       return (defined($value) and length($value)) ? $value : undef;
199             }
200            
201             sub encode_value {
202             my $value = shift;
203             return $value if (defined($use_encode) and defined($use_jcode) and not ($use_encode or $use_jcode)); # Can't encode (already tried).
204             return $value if (not defined($value)); # $value is null.
205             return $value if ($value =~ /^(?:$re_utf8)*$/); # $value is utf8.
206             # try Encode
207             if (not defined($use_encode)) {
208 4     4   64 eval { use Encode; };
  4         7  
  4         593  
209             $use_encode = $@ ? 0 : 1;
210             }
211             return encode_utf8($value) if ($use_encode);
212             # try Jcode
213             if (not defined($use_jcode)) {
214 4     4   6113 eval { use Jcode; };
  0            
  0            
215             $use_jcode = $@ ? 0 : 1;
216             }
217             return Jcode->new($value)->utf8 if ($use_jcode);
218             # can't encode
219             return $value;
220             }
221            
222             1;