File Coverage

blib/lib/FFmpeg/Stream/Helper.pm
Criterion Covered Total %
statement 14 147 9.5
branch 0 54 0.0
condition 0 36 0.0
subroutine 5 19 26.3
pod 12 14 85.7
total 31 270 11.4


line stmt bran cond sub pod time code
1             package FFmpeg::Stream::Helper;
2              
3 1     1   12905 use 5.006;
  1         2  
4 1     1   3 use strict;
  1         1  
  1         15  
5 1     1   2 use warnings;
  1         4  
  1         20  
6 1     1   2 use base 'Error::Helper';
  1         1  
  1         432  
7 1     1   961 use String::ShellQuote;
  1         578  
  1         1652  
8              
9             =head1 NAME
10              
11             FFmpeg::Stream::Helper - Helper for streaming and transcoding using ffmpeg.
12              
13             =head1 VERSION
14              
15             Version 0.1.1
16              
17             =cut
18              
19             our $VERSION = '0.1.1';
20              
21             =head1 SYNOPSIS
22              
23             This module is for helping generate a command for ffmpeg that should be good for streaming to HTML5.
24              
25             This module also does it securely by using String::ShellQuote for every definable option passed to ffmpeg.
26              
27             # Defaults Are...
28             # Output: - ((standard out))
29             # Bit Rate: 2000 kbps
30             # Bound: undef
31             # Format: mp4
32             # Log Level: quiet
33             # Threads: 0
34             my $fsh=FFmpeg::Stream::Helper->new;
35              
36             #sets the bit rate to 1500
37             $fsh->bitRateSet('1500');
38             if ( $fsh->error ){
39             warn('error:'.$fsh->error.': '.$fsh->errorString);
40             }
41              
42             # Enable printing of errors.
43             $fsh->loglevelSet('error');
44             if ( $fsh->error ){
45             warn('error:'.$fsh->error.': '.$fsh->errorString);
46             }
47              
48             # set the width bound to 800 for scaling
49             # aspect will be kept
50             $fsh->boundSet('800');
51             if ( $fsh->error ){
52             warn('error:'.$fsh->error.': '.$fsh->errorString);
53             }
54              
55             # Disable pretty much all error output. This is great if you are sending stuff
56             # to STDOUT and what you are sending it to can't tell the difference between it
57             # and STDERR
58             $fsh->loglevelSet('quiet');
59             if ( $fsh->error ){
60             warn('error:'.$fsh->error.': '.$fsh->errorString);
61             }
62              
63             # What? No. We can't stop here. This is bat country.
64             my $command=$fsh->command('/arc/video/movies/Fear and Loathing in Las Vegas.avi');
65             if ( $fsh->error ){
66             warn('error:'.$fsh->error.': '.$fsh->errorString);
67             }
68              
69             =head1 METHODS
70              
71             =head2 new
72              
73             Inits a new object for use.
74              
75             No need for error checking. This will always succeed.
76              
77             my $fsh=FFmpeg::Stream::Helper->new;
78              
79             =cut
80              
81             sub new{
82 0     0 1   my $self={
83             error=>undef,
84             errorString=>'',
85             perror=>undef,
86             errorExtra=>{
87             flags=>{
88             1=>'fileUNDEF',
89             2=>'noFile',
90             3=>'notINT',
91             4=>'formatUNDEF',
92             5=>'invalidFormat',
93             6=>'loglevelUNDEF',
94             7=>'invalidLoglevel',
95             8=>'threadsUNDEF',
96             9=>'threadsBadVal',
97             },
98             },
99             output=>'-',
100             bound=>undef,
101             kbps=>2000,
102             loglevel=>'quiet',
103             threads=>'0',
104             format=>'webm',
105             };
106 0           bless $self;
107              
108 0           return $self;
109             }
110              
111             =head2 bitRateGet
112              
113             Returns the kilobits per second used for encoding.
114              
115             my $kbps=$fsh->boundGet;
116             print "Bit Rate: ".$kbps."kbps\n"
117              
118             =cut
119              
120             sub bitRateGet{
121 0     0 1   my $self=$_[0];
122            
123             #blank any previous errors
124 0 0         if(!$self->errorblank){
125 0           return undef;
126             }
127            
128 0           return $self->{kbps};
129             }
130              
131             =head2 bitRateSet
132              
133             This sets the bitrate.
134              
135             One argument is required and that is a integer represting the kilobits per second.
136              
137             $fsh->bitRateSet('1500');
138             if ( $fsh->error ){
139             warn('error:'.$fsh->error.': '.$fsh->errorString);
140             }
141              
142             =cut
143              
144             sub bitRateSet{
145 0     0 1   my $self=$_[0];
146 0           my $kbps=$_[1];
147            
148             #blank any previous errors
149 0 0         if(!$self->errorblank){
150 0           return undef;
151             }
152              
153             #makes sure it is a integer
154 0 0         if ( $kbps !~ /^[0123456789]*$/ ){
155 0           $self->{error}=3;
156 0           $self->{errorstring}='"'.$kbps.'" is not a integer';
157 0           $self->warn;
158 0           return undef;
159             }
160              
161 0           $self->{kbps}=$kbps;
162              
163 0           return 1;
164             }
165              
166             =head2 boundGet
167              
168             Returns the current width bound for scaling the video.
169              
170             my $bound=$fsh->boundGet;
171             if ( ! defined( $bound ) ){
172             print "No bound set.\n";
173             }else{
174             print "Bound: ".$bound."\n"
175             }
176              
177             =cut
178              
179             sub boundGet{
180 0     0 1   my $self=$_[0];
181            
182             #blank any previous errors
183 0 0         if(!$self->errorblank){
184 0           return undef;
185             }
186            
187 0           return $self->{bound};
188             }
189              
190             =head2 boundSet
191              
192             Sets a new bound. The bound is the maximum size the width can be while keeping aspect when it is scaled.
193              
194             One argument is taken and that is the integer to use found bounding.
195              
196             If undef is specified, then no scaling will be done.
197              
198             #calls it with a the bound being undef, removing it
199             $fsh->boundSet;
200              
201             #sets it to 900px
202             $fsh->boundSet('900');
203             if ( $fsh->error ){
204             warn('error:'.$fsh->error.': '.$fsh->errorString);
205             }
206              
207             =cut
208              
209             sub boundSet{
210 0     0 1   my $self=$_[0];
211 0           my $bound=$_[1];
212            
213             #blank any previous errors
214 0 0         if(!$self->errorblank){
215 0           return undef;
216             }
217              
218             #removes the bound if it is undefined
219 0 0         if ( ! defined( $bound ) ){
220 0           $self->{bound}=undef;
221 0           return 1;
222             }
223              
224             #makes sure it is a integer
225 0 0         if ( $bound !~ /^[0123456789]*$/ ){
226 0           $self->{error}=3;
227 0           $self->{errorstring}='"'.$bound.'" is not a integer';
228 0           $self->warn;
229 0           return undef;
230             }
231              
232 0           $self->{bound}=$bound;
233            
234 0           return 1;
235             }
236              
237             =head2 command
238              
239             The command to run ffmpeg with the specified options.
240              
241             One argument is taken and that is the file name to run it for.
242              
243             Escaping it is not needed as String::ShellQuote is used for
244             that as well as any of the other values being passed to it.
245              
246             my $command=$fsh->command('/arc/video/movies/Fear and Loathing in Las Vegas.avi');
247             if ( $fsh->error ){
248             warn('error:'.$fsh->error.': '.$fsh->errorString);
249             }
250              
251             =cut
252            
253             sub command{
254 0     0 1   my $self=$_[0];
255 0           my $file=$_[1];
256            
257             #blank any previous errors
258 0 0         if(!$self->errorblank){
259 0           return undef;
260             }
261              
262             #make sure we havea file
263 0 0         if ( ! defined( $file ) ){
264 0           $self->{error}=1;
265 0           $self->{errorString}='No file specified';
266 0           $self->warn;
267 0           return undef;
268             }
269              
270             #make sure the file exists
271 0 0         if ( ! -f $file ){
272 0           $self->{error}=2;
273 0           $self->{errorString}='"'.$file.'" does not exist or not a file';
274 0           $self->warn;
275 0           return undef;
276             }
277              
278             #only include the bound if it is defined
279 0           my $bound='';
280 0 0         if ( defined( $self->{bound} ) ){
281 0           $bound=' -vf scale='.shell_quote($self->{bound}).':-1 ';
282             }
283              
284 0           my $command='';
285              
286 0 0         if ( $self->{format} eq 'webm' ){
287             $command='ffmpeg -i '.shell_quote($file).
288             ' -loglevel '.shell_quote($self->{loglevel}).
289             $bound.
290             ' -f webm -c:v libvpx -maxrate '.$self->{kbps}.'k -preset superfast -threads 0'.
291 0           ' '.shell_quote($self->{output});
292             }else{
293             # ffmpeg -ss %o -i %s -async 1 -b %bk -s %wx%h -ar 44100 -ac 2 -v 0 -f flv -vcodec libx264 -preset superfast -threads 0 -
294             $command='ffmpeg -i '.shell_quote($file).
295             ' -async 1 -b '.shell_quote($self->{kbps}).'k '.
296             ' -ar 44100 -ac 2 -v 0 -f '.shell_quote($self->{format}).
297             ' -loglevel '.shell_quote($self->{loglevel}).
298             $bound.
299             ' -vcodec libx264 -preset superfast -threads '.shell_quote($self->{threads}).
300             ' -g 52 -movflags frag_keyframe+empty_moov'.
301 0           ' '.shell_quote($self->{output});
302              
303             }
304 0           return $command;
305             }
306              
307             =head2 formatGet
308              
309             Returns the current format to be used.
310              
311             my $format=$fsh->formatGet;
312             print "Format: ".$format."\n";
313              
314             =cut
315              
316             sub formatGet{
317 0     0 1   my $self=$_[0];
318            
319             #blank any previous errors
320 0 0         if(!$self->errorblank){
321 0           return undef;
322             }
323              
324 0           return $self->{format};
325             }
326              
327             =head2 formatSet
328              
329             Sets the new format to use.
330              
331             One argument is required and that is the format to use. The following are supported.
332              
333             mp4
334             webm
335             ogg
336              
337             $fsh->formatSet('webm');
338             if ( $fsh->error ){
339             warn('error:'.$fsh->error.': '.$fsh->errorString);
340             }
341              
342             =cut
343              
344             sub formatSet{
345 0     0 1   my $self=$_[0];
346 0           my $format=$_[1];
347            
348             #blank any previous errors
349 0 0         if(!$self->errorblank){
350 0           return undef;
351             }
352              
353             #makes sure we have a format
354 0 0         if ( ! defined( $format ) ){
355 0           $self->{error}=4;
356 0           $self->{errorString}='No format specified';
357 0           $self->warn;
358 0           return undef;
359             }
360              
361             #makes sure we have a valid format
362 0 0 0       if (
      0        
363             ( $format ne 'mp4' ) &&
364             ( $format ne 'webm' ) &&
365             ( $format ne 'ogg' )
366             ){
367 0           $self->{error}=5;
368 0           $self->{errorString}='"'.$format.'" is not a valid format';
369 0           $self->warn;
370 0           return undef;
371             }
372            
373 0           $self->{format}=$format;
374            
375 0           return 1;
376             }
377              
378             =head2 loglevelGet
379              
380             Returns what it is currently set to output to.
381              
382             my $output=$fsh->outputGet;
383             print "Output: ".$output."\n";
384              
385             =cut
386            
387             sub loglevelGet{
388 0     0 1   my $self=$_[0];
389            
390             #blank any previous errors
391 0 0         if(!$self->errorblank){
392 0           return undef;
393             }
394              
395 0           return $self->{output};
396             }
397              
398             =head2 loglevelSet
399              
400             This sets the -loglevel for ffmpeg. Please see the man for that for the value.
401              
402             One argument is taken and that is the -loglevel to use.
403              
404             Currently it only recognizes the text version, which are as below.
405              
406             quiet
407             panic
408             fatal
409             error
410             warning
411             info
412             verbose
413             debug
414             trace
415              
416              
417             $fsh->loglevelSet('panic');
418             if ( $fsh->error ){
419             warn('error:'.$fsh->error.': '.$fsh->errorString);
420             }
421              
422             =cut
423              
424             sub loglevelSet{
425 0     0 1   my $self=$_[0];
426 0           my $loglevel=$_[1];
427            
428             #blank any previous errors
429 0 0         if(!$self->errorblank){
430 0           return undef;
431             }
432              
433             #makes sure we have a -loglevel defined
434 0 0         if ( ! defined( $loglevel ) ){
435 0           $self->{error}=6;
436 0           $self->{errorString}='Loglevel is undefined.';
437 0           $self->warn;
438 0           return undef;
439             }
440            
441             #makes sure we have a valid -loglevel
442 0 0 0       if (
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
443             ( $loglevel ne 'quiet' ) &&
444             ( $loglevel ne 'panic' ) &&
445             ( $loglevel ne 'fatal' ) &&
446             ( $loglevel ne 'fatal' ) &&
447             ( $loglevel ne 'error' ) &&
448             ( $loglevel ne 'warning' ) &&
449             ( $loglevel ne 'info' ) &&
450             ( $loglevel ne 'verbose' ) &&
451             ( $loglevel ne 'debug' ) &&
452             ( $loglevel ne 'trace' )
453             ){
454 0           $self->{error}=7;
455 0           $self->{errorString}='"'.$loglevel.'" is not a valid -loglevel';
456 0           $self->warn;
457 0           return undef;
458             }
459              
460 0           $self->{loglevel}=$loglevel;
461            
462 0           return 1;
463             }
464              
465             =head2 presetsGet
466              
467             Return the current x264 -preset value.
468              
469             my $output=$fsh->outputGet;
470             print "Output: ".$output."\n";
471              
472             =cut
473            
474              
475              
476             =head2 outputGet
477              
478             Returns what it is currently set to output to.
479              
480             my $output=$fsh->outputGet;
481             print "Output: ".$output."\n";
482              
483             =cut
484            
485             sub outpubGet{
486 0     0 0   my $self=$_[0];
487            
488             #blank any previous errors
489 0 0         if(!$self->errorblank){
490 0           return undef;
491             }
492              
493 0           return $self->{output};
494             }
495              
496             =head2 outputSet
497              
498             The file to output to. If not specified, "-" will be used.
499              
500             One argument is required and that is the what it should output to.
501              
502             There is no need to escape anything as that is handled by String::ShellQuote.
503              
504             #output to STDOUT
505             $fsh->outputSet('-');
506             if ( $fsh->error ){
507             warn('error:'.$fsh->error.': '.$fsh->errorString);
508             }
509              
510             #output to '/tmp/Fear and Loathing in Las Vegas.mp4'
511             $fsh->outputSet('/tmp/Fear and Loathing in Las Vegas.mp4');
512             if ( $fsh->error ){
513             warn('error:'.$fsh->error.': '.$fsh->errorString);
514             }
515              
516             =cut
517            
518             sub outpubSet{
519 0     0 0   my $self=$_[0];
520 0           my $output=$_[1];
521            
522             #blank any previous errors
523 0 0         if(!$self->errorblank){
524 0           return undef;
525             }
526              
527 0 0         if ( ! defined( $output ) ){
528 0           $self->{error}=1;
529 0           $self->{errorString}='No file specified';
530 0           $self->warn;
531 0           return undef;
532             }
533              
534 0           $self->{output}=$output;
535            
536 0           return 1;
537             }
538              
539             =head2 threadsGet
540              
541             Gets the number of threads to use.
542              
543             my $threads=$fsh->threadsGet;
544             if ( ( $threads eq '0' ) || ( $threads eq 'auto' ) ){
545             print "The number of threads will automatically be determined.\n";
546             }else{
547             print $threads." threads will be used for this.\n";
548             }
549              
550             =cut
551              
552             sub threadsGet{
553 0     0 1   my $self=$_[0];
554            
555             #blank any previous errors
556 0 0         if(!$self->errorblank){
557 0           return undef;
558             }
559              
560 0           return $self->{threads};
561             }
562              
563             =head2 threadsSet
564              
565             Sets the number of threads to use for encoding.
566              
567             One argument is taken and that is a integeer representing the number of threads.
568              
569             'auto' may also be specified and should be the same as '0'. If either are specified,
570             ffmpeg will choose the best thread count.
571              
572             $fsh->threadsSet('0');
573             if ( $fsh->error ){
574             warn('error:'.$fsh->error.': '.$fsh->errorString);
575             }
576              
577             =cut
578              
579             sub threadsSet{
580 0     0 1   my $self=$_[0];
581 0           my $threads=$_[1];
582            
583             #blank any previous errors
584 0 0         if(!$self->errorblank){
585 0           return undef;
586             }
587              
588             #
589 0 0         if ( ! defined( $threads ) ){
590 0           $self->{error}=8;
591 0           $self->{errorString}='Nothing specified for threads.';
592 0           $self->warn;
593 0           return undef;
594             }
595              
596 0 0 0       if ( ( $threads ne 'auto' ) &&
597             ( $threads !~ /[0123456789]*/ ) ){
598 0           $self->{error}=9;
599 0           $self->{errorString}='"'.$threads.'" is not a valid value for -threads';
600 0           $self->warn;
601 0           return undef;
602             }
603            
604 0           $self->{threads}=$threads;
605            
606 0           return 1;
607             }
608              
609             =head1 ERROR CODES/FLAGS
610              
611             =head2 1/fileUNDEF
612              
613             No file specified.
614              
615             =head2 2/noFile
616              
617             File does not exist or is not a file.
618              
619             =head2 3/notINT
620              
621             Not an integer.
622              
623             =head2 4/formatUNDEF
624              
625             Format is undef.
626              
627             =head2 5/invalidFormat
628              
629             Not a valid format. Must be one of the ones below.
630              
631             mp4
632             webm
633             ogg
634              
635             The default is mp4.
636              
637             =head2 6/loglevelUNDEF
638              
639             Nothing specified to use for -loglevel.
640              
641             =head2 7/invalidLoglevel
642              
643             Not a valid -loglevel value.
644              
645             The ones recognized by name are as below.
646              
647             quiet
648             panic
649             fatal
650             error
651             warning
652             info
653             verbose
654             debug
655             trace
656              
657             Please see the ffmpeg man for descriptions.
658              
659             =head2 8/threadsUNDEF
660              
661             No value specified for what to use for -threads.
662              
663             =head2 9/threadsBadVal
664              
665             A bad value has been specified for threads. It needs to match one of the ones below.
666              
667             /^auto$/
668             /[0123456789]*/
669              
670             =head1 AUTHOR
671              
672             Zane C. Bowers-Hadley, C<< >>
673              
674             =head1 BUGS
675              
676             Please report any bugs or feature requests to C, or through
677             the web interface at L. I will be notified, and then you'll
678             automatically be notified of progress on your bug as I make changes.
679              
680              
681              
682              
683             =head1 SUPPORT
684              
685             You can find documentation for this module with the perldoc command.
686              
687             perldoc FFmpeg::Stream::Helper
688              
689              
690             You can also look for information at:
691              
692             =over 4
693              
694             =item * RT: CPAN's request tracker (report bugs here)
695              
696             L
697              
698             =item * AnnoCPAN: Annotated CPAN documentation
699              
700             L
701              
702             =item * CPAN Ratings
703              
704             L
705              
706             =item * Search CPAN
707              
708             L
709              
710             =back
711              
712              
713             =head1 ACKNOWLEDGEMENTS
714              
715              
716             =head1 LICENSE AND COPYRIGHT
717              
718             Copyright 2016 Zane C. Bowers-Hadley.
719              
720             This program is free software; you can redistribute it and/or modify it
721             under the terms of the the Artistic License (2.0). You may obtain a
722             copy of the full license at:
723              
724             L
725              
726             Any use, modification, and distribution of the Standard or Modified
727             Versions is governed by this Artistic License. By using, modifying or
728             distributing the Package, you accept this license. Do not use, modify,
729             or distribute the Package, if you do not accept this license.
730              
731             If your Modified Version has been derived from a Modified Version made
732             by someone other than you, you are nevertheless required to ensure that
733             your Modified Version complies with the requirements of this license.
734              
735             This license does not grant you the right to use any trademark, service
736             mark, tradename, or logo of the Copyright Holder.
737              
738             This license includes the non-exclusive, worldwide, free-of-charge
739             patent license to make, have made, use, offer to sell, sell, import and
740             otherwise transfer the Package with respect to any patent claims
741             licensable by the Copyright Holder that are necessarily infringed by the
742             Package. If you institute patent litigation (including a cross-claim or
743             counterclaim) against any party alleging that the Package constitutes
744             direct or contributory patent infringement, then this Artistic License
745             to you shall terminate on the date that such litigation is filed.
746              
747             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
748             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
749             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
750             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
751             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
752             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
753             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
754             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
755              
756              
757             =cut
758              
759             1; # End of FFmpeg::Stream::Helper