File Coverage

blib/lib/Aws/Polly/Select.pm
Criterion Covered Total %
statement 71 120 59.1
branch 8 14 57.1
condition n/a
subroutine 19 28 67.8
pod 0 18 0.0
total 98 180 54.4


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2             #-------------------------------------------------------------------------------
3             # Select AWS Polly speakers with specified characteristics
4             # Philip R Brenan at gmail dot com, Appa Apps Ltd, 2017
5             #-------------------------------------------------------------------------------
6              
7             package Aws::Polly::Select;
8             require v5.16.0;
9 1     1   465 use warnings FATAL => qw(all);
  1         2  
  1         31  
10 1     1   5 use strict;
  1         1  
  1         16  
11 1     1   4 use Carp;
  1         1  
  1         57  
12 1     1   468 use Data::Table::Text qw(:all);
  1         16757  
  1         235  
13 1     1   6 use Data::Dump qw(dump);
  1         2  
  1         192  
14              
15             our $VERSION = '2017.444';
16              
17             #-------------------------------------------------------------------------------
18             # Load speakers
19             #-------------------------------------------------------------------------------
20              
21             sub speaker($) # Construct a speaker
22 517     517 0 804 {my ($speaker) = @_; # Speaker details
  0         0  
23             package Aws::Polly::Select::Speaker;
24 517         662 my %l;
25 517         1456 my %p = %$speaker;
26 517         1083 my ($gender, $name, $code, $language) = @p{qw(Gender Id LanguageCode LanguageName)};
27 517         1040 my ($base, $country) = split /-/, $code;
28 517         823 $gender = lc($gender);
29 517         1081 $language =~ s/\s/-/g;
30 517         2188 bless{name=>$name, gender=>$gender, speaking=>$language,
31             written=>$base, country=>lc($country), code=>$code};
32              
33             BEGIN # Fields and projections
34 1     1   4 {for(qw(name gender speaking written country code))
35 6         17 {Data::Table::Text::genLValueScalarMethods($_);
36             package Aws::Polly::Select;
37 6     1 0 598 eval <
  1     1 0 4  
  47     1 0 1361  
  1     1 0 68  
  1     1 0 5  
  47     1 0 1351  
  1         63  
  1         5  
  47         1374  
  1         53  
  1         4  
  47         1387  
  1         86  
  1         5  
  47         1382  
  1         74  
  1         6  
  47         1355  
  1         73  
38             sub $_
39             {my %l = map {\$_->$_=>1} &speakers;
40             join ' ', sort keys %l;
41             }
42             END
43 6 50       22 $@ and confess $@;
44              
45 6     0 0 269 eval <
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
46             sub ${_}AsArray
47             {my %l = map {\$_->$_=>1} &speakers;
48             sort keys %l;
49             }
50             END
51 6 50       914 $@ and confess $@;
52             }
53             }
54             }
55              
56 11     11 0 21 sub speakers {map {speaker($_)} @{&speakerDetails}} # Speakers
  517         919  
  11         23  
57              
58             #-------------------------------------------------------------------------------
59             # Select speakers
60             #-------------------------------------------------------------------------------
61              
62             sub select(@) ## Select speakers by fields
63 5     5 0 18 {my (%selection) = @_; # Selection fields: name=>"regular expression" where the field of that name must match the regular expression regardless of case
64 5         8 my @s;
65 5         11 for my $speaker(speakers)
66 235         324 {my $m = 1;
67 235         394 for my $field(keys %selection)
68 409 100       797 {last unless $m;
69 281         409 my $r = $selection{$field};
70 281         402 my $v = $speaker->{$field};
71 281 50       527 confess "No such field: $field" unless $v;
72 281         777 $m = $v =~ m/$r/i; # Case insensitive
73             }
74 235 100       499 push @s, $speaker if $m; # Exclude potential speaker unless they match all valid fields
75             }
76 5         133 sort {$a->name cmp $b->name} @s
  42         820  
77             }
78              
79             #-------------------------------------------------------------------------------
80             # Renew speaker details, but only when AWS changes the list of speakers
81             #-------------------------------------------------------------------------------
82              
83             sub Aws::Polly::Select::Speaker::generateSpeech($$$)
84 0     0   0 {my ($speaker, $text, $outFile) = @_; # Speaker definition, text, output file
85              
86 0         0 my $tmpFile = $outFile.'.temp';
87 0         0 my $speakerId = $speaker->name;
88 0         0 makePath($outFile);
89              
90 0         0 if (1) # Create speech using Polly
91 0         0 {my $c = "aws polly synthesize-speech ".
92             "--output-format mp3 --text \"$text\" --voice-id $speakerId $tmpFile";
93 0         0 say STDERR $c;
94 0         0 print STDERR $_ for qx($c);
95             }
96              
97             my $maxVol = 1 ? sub # Find volume
98 0     0   0 {my $c = "ffmpeg -i $tmpFile -af \"volumedetect\" -f null /dev/null";
99 0         0 say STDERR $c;
100 0         0 my @l = grep {/max_volume: /} qx($c 2>&1);
  0         0  
101 0 0       0 if ($l[0] =~ /max_volume: (\S+) dB/)
102 0         0 {my $v = -$1;
103 0         0 return "volume=${v}dB"
104             }
105             ''
106 0         0 }->() : "volume=1dB";
  0         0  
107              
108 0         0 if (1) # Normalize volume
109 0         0 {my $filter = " -af \"$maxVol\"";
110 0         0 my $c = "ffmpeg -nostats -y -i $tmpFile $filter $outFile"; # ffmpeg command
111 0         0 say STDERR $c;
112 0         0 print STDERR $_ for qx($c);
113 0         0 unlink $tmpFile;
114             }
115             }
116              
117             #-------------------------------------------------------------------------------
118             # Renew speaker details, but only when AWS changes the list of speakers
119             #-------------------------------------------------------------------------------
120              
121             sub renewSpeakerDetailsButOnlyWhenAWSChangesTheListOfSpeakers
122 0     0 0 0 {my $c = "aws polly describe-voices";
123 0         0 my $j = qx($c);
124 0         0 my $p = decode_json $j;
125 0         0 my @voices = sort {$a->{LanguageCode} cmp $b->{LanguageCode}} @{$p->{Voices}};
  0         0  
  0         0  
126 0         0 say STDERR dump([@voices]);
127             }
128              
129             #-------------------------------------------------------------------------------
130             # Speaker details from AWS via the method above
131             #-------------------------------------------------------------------------------
132              
133             sub speakerDetails{[
134             {
135 11     11 0 506 Gender => "Female",
136             Id => "Gwyneth",
137             LanguageCode => "cy-GB",
138             LanguageName => "Welsh",
139             Name => "Gwyneth",
140             },
141             {
142             Gender => "Female",
143             Id => "Naja",
144             LanguageCode => "da-DK",
145             LanguageName => "Danish",
146             Name => "Naja",
147             },
148             {
149             Gender => "Male",
150             Id => "Mads",
151             LanguageCode => "da-DK",
152             LanguageName => "Danish",
153             Name => "Mads",
154             },
155             {
156             Gender => "Female",
157             Id => "Marlene",
158             LanguageCode => "de-DE",
159             LanguageName => "German",
160             Name => "Marlene",
161             },
162             {
163             Gender => "Male",
164             Id => "Hans",
165             LanguageCode => "de-DE",
166             LanguageName => "German",
167             Name => "Hans",
168             },
169             {
170             Gender => "Male",
171             Id => "Russell",
172             LanguageCode => "en-AU",
173             LanguageName => "Australian English",
174             Name => "Russell",
175             },
176             {
177             Gender => "Female",
178             Id => "Nicole",
179             LanguageCode => "en-AU",
180             LanguageName => "Australian English",
181             Name => "Nicole",
182             },
183             {
184             Gender => "Female",
185             Id => "Emma",
186             LanguageCode => "en-GB",
187             LanguageName => "British English",
188             Name => "Emma",
189             },
190             {
191             Gender => "Male",
192             Id => "Brian",
193             LanguageCode => "en-GB",
194             LanguageName => "British English",
195             Name => "Brian",
196             },
197             {
198             Gender => "Female",
199             Id => "Amy",
200             LanguageCode => "en-GB",
201             LanguageName => "British English",
202             Name => "Amy",
203             },
204             {
205             Gender => "Male",
206             Id => "Geraint",
207             LanguageCode => "en-GB-WLS",
208             LanguageName => "Welsh English",
209             Name => "Geraint",
210             },
211             {
212             Gender => "Female",
213             Id => "Raveena",
214             LanguageCode => "en-IN",
215             LanguageName => "Indian English",
216             Name => "Raveena",
217             },
218             {
219             Gender => "Female",
220             Id => "Joanna",
221             LanguageCode => "en-US",
222             LanguageName => "US English",
223             Name => "Joanna",
224             },
225             {
226             Gender => "Female",
227             Id => "Salli",
228             LanguageCode => "en-US",
229             LanguageName => "US English",
230             Name => "Salli",
231             },
232             {
233             Gender => "Female",
234             Id => "Kimberly",
235             LanguageCode => "en-US",
236             LanguageName => "US English",
237             Name => "Kimberly",
238             },
239             {
240             Gender => "Female",
241             Id => "Kendra",
242             LanguageCode => "en-US",
243             LanguageName => "US English",
244             Name => "Kendra",
245             },
246             {
247             Gender => "Male",
248             Id => "Justin",
249             LanguageCode => "en-US",
250             LanguageName => "US English",
251             Name => "Justin",
252             },
253             {
254             Gender => "Male",
255             Id => "Joey",
256             LanguageCode => "en-US",
257             LanguageName => "US English",
258             Name => "Joey",
259             },
260             {
261             Gender => "Female",
262             Id => "Ivy",
263             LanguageCode => "en-US",
264             LanguageName => "US English",
265             Name => "Ivy",
266             },
267             {
268             Gender => "Male",
269             Id => "Enrique",
270             LanguageCode => "es-ES",
271             LanguageName => "Castilian Spanish",
272             Name => "Enrique",
273             },
274             {
275             Gender => "Female",
276             Id => "Conchita",
277             LanguageCode => "es-ES",
278             LanguageName => "Castilian Spanish",
279             Name => "Conchita",
280             },
281             {
282             Gender => "Female",
283             Id => "Penelope",
284             LanguageCode => "es-US",
285             LanguageName => "US Spanish",
286             Name => "Pen\xE9lope",
287             },
288             {
289             Gender => "Male",
290             Id => "Miguel",
291             LanguageCode => "es-US",
292             LanguageName => "US Spanish",
293             Name => "Miguel",
294             },
295             {
296             Gender => "Female",
297             Id => "Chantal",
298             LanguageCode => "fr-CA",
299             LanguageName => "Canadian French",
300             Name => "Chantal",
301             },
302             {
303             Gender => "Male",
304             Id => "Mathieu",
305             LanguageCode => "fr-FR",
306             LanguageName => "French",
307             Name => "Mathieu",
308             },
309             {
310             Gender => "Female",
311             Id => "Celine",
312             LanguageCode => "fr-FR",
313             LanguageName => "French",
314             Name => "C\xE9line",
315             },
316             {
317             Gender => "Male",
318             Id => "Karl",
319             LanguageCode => "is-IS",
320             LanguageName => "Icelandic",
321             Name => "Karl",
322             },
323             {
324             Gender => "Female",
325             Id => "Dora",
326             LanguageCode => "is-IS",
327             LanguageName => "Icelandic",
328             Name => "D\xF3ra",
329             },
330             {
331             Gender => "Male",
332             Id => "Giorgio",
333             LanguageCode => "it-IT",
334             LanguageName => "Italian",
335             Name => "Giorgio",
336             },
337             {
338             Gender => "Female",
339             Id => "Carla",
340             LanguageCode => "it-IT",
341             LanguageName => "Italian",
342             Name => "Carla",
343             },
344             {
345             Gender => "Female",
346             Id => "Mizuki",
347             LanguageCode => "ja-JP",
348             LanguageName => "Japanese",
349             Name => "Mizuki",
350             },
351             {
352             Gender => "Female",
353             Id => "Liv",
354             LanguageCode => "nb-NO",
355             LanguageName => "Norwegian",
356             Name => "Liv",
357             },
358             {
359             Gender => "Male",
360             Id => "Ruben",
361             LanguageCode => "nl-NL",
362             LanguageName => "Dutch",
363             Name => "Ruben",
364             },
365             {
366             Gender => "Female",
367             Id => "Lotte",
368             LanguageCode => "nl-NL",
369             LanguageName => "Dutch",
370             Name => "Lotte",
371             },
372             {
373             Gender => "Female",
374             Id => "Maja",
375             LanguageCode => "pl-PL",
376             LanguageName => "Polish",
377             Name => "Maja",
378             },
379             {
380             Gender => "Male",
381             Id => "Jan",
382             LanguageCode => "pl-PL",
383             LanguageName => "Polish",
384             Name => "Jan",
385             },
386             {
387             Gender => "Female",
388             Id => "Ewa",
389             LanguageCode => "pl-PL",
390             LanguageName => "Polish",
391             Name => "Ewa",
392             },
393             {
394             Gender => "Male",
395             Id => "Jacek",
396             LanguageCode => "pl-PL",
397             LanguageName => "Polish",
398             Name => "Jacek",
399             },
400             {
401             Gender => "Female",
402             Id => "Vitoria",
403             LanguageCode => "pt-BR",
404             LanguageName => "Brazilian Portuguese",
405             Name => "Vit\xF3ria",
406             },
407             {
408             Gender => "Male",
409             Id => "Ricardo",
410             LanguageCode => "pt-BR",
411             LanguageName => "Brazilian Portuguese",
412             Name => "Ricardo",
413             },
414             {
415             Gender => "Female",
416             Id => "Ines",
417             LanguageCode => "pt-PT",
418             LanguageName => "Portuguese",
419             Name => "In\xEAs",
420             },
421             {
422             Gender => "Male",
423             Id => "Cristiano",
424             LanguageCode => "pt-PT",
425             LanguageName => "Portuguese",
426             Name => "Cristiano",
427             },
428             {
429             Gender => "Female",
430             Id => "Carmen",
431             LanguageCode => "ro-RO",
432             LanguageName => "Romanian",
433             Name => "Carmen",
434             },
435             {
436             Gender => "Male",
437             Id => "Maxim",
438             LanguageCode => "ru-RU",
439             LanguageName => "Russian",
440             Name => "Maxim",
441             },
442             {
443             Gender => "Female",
444             Id => "Tatyana",
445             LanguageCode => "ru-RU",
446             LanguageName => "Russian",
447             Name => "Tatyana",
448             },
449             {
450             Gender => "Female",
451             Id => "Astrid",
452             LanguageCode => "sv-SE",
453             LanguageName => "Swedish",
454             Name => "Astrid",
455             },
456             {
457             Gender => "Female",
458             Id => "Filiz",
459             LanguageCode => "tr-TR",
460             LanguageName => "Turkish",
461             Name => "Filiz",
462             },
463             ]}
464              
465             #-------------------------------------------------------------------------------
466             # Test
467             #-------------------------------------------------------------------------------
468              
469             sub test
470 1 50   1 0 524 {eval join('', ) || die $@
  1     1   12123  
  1         9  
  1         86  
471             }
472              
473             test unless caller();
474              
475             # Documentation
476             #extractDocumentation unless caller;
477              
478             #-------------------------------------------------------------------------------
479             # Export
480             #-------------------------------------------------------------------------------
481              
482             require Exporter;
483              
484 1     1   7 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  1         2  
  1         86  
485              
486             @ISA = qw(Exporter);
487             @EXPORT = qw();
488             @EXPORT_OK = qw();
489             %EXPORT_TAGS = (all=>[@EXPORT, @EXPORT_OK]);
490              
491             1;
492              
493             =pod
494              
495             =encoding utf-8
496              
497             =head1 Name
498              
499             Aws::Polly::Select - Select AWS Polly speakers with specified characteristics
500              
501             =head1 Synopsis
502              
503             use Aws::Polly::Select;
504              
505             ok qw(Penelope) eq join " ", map {$_->name}
506              
507             Aws::Polly::Select::select
508              
509             country=>qr(us), gender=>qr(female), speaking=>qr(Spanish);
510              
511             =head1 Description
512              
513             Aws::Polly::Select::select(key=>qr(value), ...)
514              
515             Returns zero or more Amazon Web Services Polly speaker definitions which match
516             the hash of characteristics provided. Each hash key must name one of the
517             bs given below, the value for the key should be a regular expression to
518             select against the possible b listed beside each key. Please take care
519             with case or code qr()i;
520              
521             KEY VALUES
522              
523             written cy da de en es fr is it ja nb nl pl pt ro ru sv tr
524              
525             code cy-GB da-DK de-DE en-AU en-GB en-GB-WLS en-IN en-US
526             es-ES es-US fr-CA fr-FR is-IS it-IT ja-JP nb-NO
527             nl-NL pl-PL pt-BR pt-PT ro-RO ru-RU sv-SE tr-TR
528              
529             country au br ca de dk es fr gb in is it jp nl no pl pt ro ru se tr us
530              
531             gender Female Male
532              
533             speaking Australian-English Brazilian-Portuguese British-English
534             Canadian-French Castilian-Spanish Danish Dutch French German
535             Icelandic Indian-English Italian Japanese Norwegian Polish Portuguese
536             Romanian Russian Swedish Turkish US-English US-Spanish Welsh
537             Welsh-English
538              
539             The returned speaker definitions have the following Bs which will yield
540             one of th specified b for each speaker definition:
541              
542             METHOD VALUES
543              
544             written cy da de en es fr is it ja nb nl pl pt ro ru sv tr
545              
546             code cy-GB da-DK de-DE en-AU en-GB en-GB-WLS en-IN en-US es-ES es-US
547             fr-CA fr-FR is-IS it-IT ja-JP nb-NO nl-NL pl-PL pt-BR pt-PT
548             ro-RO ru-RU sv-SE tr-TR
549              
550             name Amy Astrid Brian Carla Carmen Celine Chantal Conchita Cristiano
551             Dora Emma Enrique Ewa Filiz Geraint Giorgio Gwyneth Hans Ines Ivy
552             Jacek Jan Joanna Joey Justin Karl Kendra Kimberly Liv Lotte Mads
553             Maja Marlene Mathieu Maxim Miguel Mizuki Naja Nicole Penelope
554             Raveena Ricardo Ruben Russell Salli Tatyana Vitoria
555              
556             speaking Australian-English Brazilian-Portuguese British-English
557             Canadian-French Castilian-Spanish Danish Dutch French German
558             Icelandic Indian-English Italian Japanese Norwegian Polish
559             Portuguese Romanian Russian Swedish Turkish US-English US-Spanish
560             Welsh Welsh-English
561              
562             gender female male
563              
564             country au br ca de dk es fr gb in is it jp nl no pl pt ro ru se tr us
565              
566              
567             =head1 Installation
568              
569             Standard Module::Build process for building and installing modules:
570              
571             perl Build.PL
572             ./Build
573             ./Build test
574             ./Build install
575              
576             =head1 Author
577              
578             philiprbrenan@gmail.com
579              
580             http://www.appaapps.com
581              
582             =head1 Copyright
583              
584             Copyright (c) 2017 Philip R Brenan.
585              
586             This module is free software. It may be used, redistributed and/or modified
587             under the same terms as Perl itself.
588              
589             =cut
590              
591             __DATA__