File Coverage

blib/lib/FFmpeg/Stream/Helper.pm
Criterion Covered Total %
statement 14 144 9.7
branch 0 52 0.0
condition 0 36 0.0
subroutine 5 19 26.3
pod 12 14 85.7
total 31 265 11.7


line stmt bran cond sub pod time code
1             package FFmpeg::Stream::Helper;
2              
3 1     1   12682 use 5.006;
  1         2  
4 1     1   3 use strict;
  1         1  
  1         14  
5 1     1   3 use warnings;
  1         3  
  1         19  
6 1     1   3 use base 'Error::Helper';
  1         4  
  1         409  
7 1     1   881 use String::ShellQuote;
  1         537  
  1         937  
8              
9             =head1 NAME
10              
11             FFmpeg::Stream::Helper - Helper for streaming and transcoding using ffmpeg.
12              
13             =head1 VERSION
14              
15             Version 0.0.2
16              
17             =cut
18              
19             our $VERSION = '0.0.2';
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=>'mp4',
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             # 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 -
285             my $command='ffmpeg -i '.shell_quote($file).
286             ' -async 1 -b '.shell_quote($self->{kbps}).'k '.
287             ' -ar 44100 -ac 2 -v 0 -f '.shell_quote($self->{format}).
288             ' -loglevel '.shell_quote($self->{loglevel}).
289             $bound.
290             ' -vcodec libx264 -preset superfast -threads '.shell_quote($self->{threads}).
291             ' -g 52 -movflags frag_keyframe+empty_moov'.
292 0           ' '.shell_quote($self->{output});
293              
294 0           return $command;
295             }
296              
297             =head2 formatGet
298              
299             Returns the current format to be used.
300              
301             my $format=$fsh->formatGet;
302             print "Format: ".$format."\n";
303              
304             =cut
305              
306             sub formatGet{
307 0     0 1   my $self=$_[0];
308            
309             #blank any previous errors
310 0 0         if(!$self->errorblank){
311 0           return undef;
312             }
313              
314 0           return $self->{format};
315             }
316              
317             =head2 formatSet
318              
319             Sets the new format to use.
320              
321             One argument is required and that is the format to use. The following are supported.
322              
323             mp4
324             webm
325             ogg
326              
327             $fsh->formatSet('webm');
328             if ( $fsh->error ){
329             warn('error:'.$fsh->error.': '.$fsh->errorString);
330             }
331              
332             =cut
333              
334             sub formatSet{
335 0     0 1   my $self=$_[0];
336 0           my $format=$_[1];
337            
338             #blank any previous errors
339 0 0         if(!$self->errorblank){
340 0           return undef;
341             }
342              
343             #makes sure we have a format
344 0 0         if ( ! defined( $format ) ){
345 0           $self->{error}=4;
346 0           $self->{errorString}='No format specified';
347 0           $self->warn;
348 0           return undef;
349             }
350              
351             #makes sure we have a valid format
352 0 0 0       if (
      0        
353             ( $format ne 'mp4' ) &&
354             ( $format ne 'webm' ) &&
355             ( $format ne 'ogg' )
356             ){
357 0           $self->{error}=5;
358 0           $self->{errorString}='"'.$format.'" is not a valid format';
359 0           $self->warn;
360 0           return undef;
361             }
362            
363 0           $self->{format}=$format;
364            
365 0           return 1;
366             }
367              
368             =head2 loglevelGet
369              
370             Returns what it is currently set to output to.
371              
372             my $output=$fsh->outputGet;
373             print "Output: ".$output."\n";
374              
375             =cut
376            
377             sub loglevelGet{
378 0     0 1   my $self=$_[0];
379            
380             #blank any previous errors
381 0 0         if(!$self->errorblank){
382 0           return undef;
383             }
384              
385 0           return $self->{output};
386             }
387              
388             =head2 loglevelSet
389              
390             This sets the -loglevel for ffmpeg. Please see the man for that for the value.
391              
392             One argument is taken and that is the -loglevel to use.
393              
394             Currently it only recognizes the text version, which are as below.
395              
396             quiet
397             panic
398             fatal
399             error
400             warning
401             info
402             verbose
403             debug
404             trace
405              
406              
407             $fsh->loglevelSet('panic');
408             if ( $fsh->error ){
409             warn('error:'.$fsh->error.': '.$fsh->errorString);
410             }
411              
412             =cut
413              
414             sub loglevelSet{
415 0     0 1   my $self=$_[0];
416 0           my $loglevel=$_[1];
417            
418             #blank any previous errors
419 0 0         if(!$self->errorblank){
420 0           return undef;
421             }
422              
423             #makes sure we have a -loglevel defined
424 0 0         if ( ! defined( $loglevel ) ){
425 0           $self->{error}=6;
426 0           $self->{errorString}='Loglevel is undefined.';
427 0           $self->warn;
428 0           return undef;
429             }
430            
431             #makes sure we have a valid -loglevel
432 0 0 0       if (
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
433             ( $loglevel ne 'quiet' ) &&
434             ( $loglevel ne 'panic' ) &&
435             ( $loglevel ne 'fatal' ) &&
436             ( $loglevel ne 'fatal' ) &&
437             ( $loglevel ne 'error' ) &&
438             ( $loglevel ne 'warning' ) &&
439             ( $loglevel ne 'info' ) &&
440             ( $loglevel ne 'verbose' ) &&
441             ( $loglevel ne 'debug' ) &&
442             ( $loglevel ne 'trace' )
443             ){
444 0           $self->{error}=7;
445 0           $self->{errorString}='"'.$loglevel.'" is not a valid -loglevel';
446 0           $self->warn;
447 0           return undef;
448             }
449              
450 0           $self->{loglevel}=$loglevel;
451            
452 0           return 1;
453             }
454              
455             =head2 presetsGet
456              
457             Return the current x264 -preset value.
458              
459             my $output=$fsh->outputGet;
460             print "Output: ".$output."\n";
461              
462             =cut
463            
464              
465              
466             =head2 outputGet
467              
468             Returns what it is currently set to output to.
469              
470             my $output=$fsh->outputGet;
471             print "Output: ".$output."\n";
472              
473             =cut
474            
475             sub outpubGet{
476 0     0 0   my $self=$_[0];
477            
478             #blank any previous errors
479 0 0         if(!$self->errorblank){
480 0           return undef;
481             }
482              
483 0           return $self->{output};
484             }
485              
486             =head2 outputSet
487              
488             The file to output to. If not specified, "-" will be used.
489              
490             One argument is required and that is the what it should output to.
491              
492             There is no need to escape anything as that is handled by String::ShellQuote.
493              
494             #output to STDOUT
495             $fsh->outputSet('-');
496             if ( $fsh->error ){
497             warn('error:'.$fsh->error.': '.$fsh->errorString);
498             }
499              
500             #output to '/tmp/Fear and Loathing in Las Vegas.mp4'
501             $fsh->outputSet('/tmp/Fear and Loathing in Las Vegas.mp4');
502             if ( $fsh->error ){
503             warn('error:'.$fsh->error.': '.$fsh->errorString);
504             }
505              
506             =cut
507            
508             sub outpubSet{
509 0     0 0   my $self=$_[0];
510 0           my $output=$_[1];
511            
512             #blank any previous errors
513 0 0         if(!$self->errorblank){
514 0           return undef;
515             }
516              
517 0 0         if ( ! defined( $output ) ){
518 0           $self->{error}=1;
519 0           $self->{errorString}='No file specified';
520 0           $self->warn;
521 0           return undef;
522             }
523              
524 0           $self->{output}=$output;
525            
526 0           return 1;
527             }
528              
529             =head2 threadsGet
530              
531             Gets the number of threads to use.
532              
533             my $threads=$fsh->threadsGet;
534             if ( ( $threads eq '0' ) || ( $threads eq 'auto' ) ){
535             print "The number of threads will automatically be determined.\n";
536             }else{
537             print $threads." threads will be used for this.\n";
538             }
539              
540             =cut
541              
542             sub threadsGet{
543 0     0 1   my $self=$_[0];
544            
545             #blank any previous errors
546 0 0         if(!$self->errorblank){
547 0           return undef;
548             }
549              
550 0           return $self->{threads};
551             }
552              
553             =head2 threadsSet
554              
555             Sets the number of threads to use for encoding.
556              
557             One argument is taken and that is a integeer representing the number of threads.
558              
559             'auto' may also be specified and should be the same as '0'. If either are specified,
560             ffmpeg will choose the best thread count.
561              
562             $fsh->threadsSet('0');
563             if ( $fsh->error ){
564             warn('error:'.$fsh->error.': '.$fsh->errorString);
565             }
566              
567             =cut
568              
569             sub threadsSet{
570 0     0 1   my $self=$_[0];
571 0           my $threads=$_[1];
572            
573             #blank any previous errors
574 0 0         if(!$self->errorblank){
575 0           return undef;
576             }
577              
578             #
579 0 0         if ( ! defined( $threads ) ){
580 0           $self->{error}=8;
581 0           $self->{errorString}='Nothing specified for threads.';
582 0           $self->warn;
583 0           return undef;
584             }
585              
586 0 0 0       if ( ( $threads ne 'auto' ) &&
587             ( $threads !~ /[0123456789]*/ ) ){
588 0           $self->{error}=9;
589 0           $self->{errorString}='"'.$threads.'" is not a valid value for -threads';
590 0           $self->warn;
591 0           return undef;
592             }
593            
594 0           $self->{threads}=$threads;
595            
596 0           return 1;
597             }
598              
599             =head1 ERROR CODES/FLAGS
600              
601             =head2 1/fileUNDEF
602              
603             No file specified.
604              
605             =head2 2/noFile
606              
607             File does not exist or is not a file.
608              
609             =head2 3/notINT
610              
611             Not an integer.
612              
613             =head2 4/formatUNDEF
614              
615             Format is undef.
616              
617             =head2 5/invalidFormat
618              
619             Not a valid format. Must be one of the ones below.
620              
621             mp4
622             webm
623             ogg
624              
625             The default is mp4.
626              
627             =head2 6/loglevelUNDEF
628              
629             Nothing specified to use for -loglevel.
630              
631             =head2 7/invalidLoglevel
632              
633             Not a valid -loglevel value.
634              
635             The ones recognized by name are as below.
636              
637             quiet
638             panic
639             fatal
640             error
641             warning
642             info
643             verbose
644             debug
645             trace
646              
647             Please see the ffmpeg man for descriptions.
648              
649             =head2 8/threadsUNDEF
650              
651             No value specified for what to use for -threads.
652              
653             =head2 9/threadsBadVal
654              
655             A bad value has been specified for threads. It needs to match one of the ones below.
656              
657             /^auto$/
658             /[0123456789]*/
659              
660             =head1 AUTHOR
661              
662             Zane C. Bowers-Hadley, C<< >>
663              
664             =head1 BUGS
665              
666             Please report any bugs or feature requests to C, or through
667             the web interface at L. I will be notified, and then you'll
668             automatically be notified of progress on your bug as I make changes.
669              
670              
671              
672              
673             =head1 SUPPORT
674              
675             You can find documentation for this module with the perldoc command.
676              
677             perldoc FFmpeg::Stream::Helper
678              
679              
680             You can also look for information at:
681              
682             =over 4
683              
684             =item * RT: CPAN's request tracker (report bugs here)
685              
686             L
687              
688             =item * AnnoCPAN: Annotated CPAN documentation
689              
690             L
691              
692             =item * CPAN Ratings
693              
694             L
695              
696             =item * Search CPAN
697              
698             L
699              
700             =back
701              
702              
703             =head1 ACKNOWLEDGEMENTS
704              
705              
706             =head1 LICENSE AND COPYRIGHT
707              
708             Copyright 2016 Zane C. Bowers-Hadley.
709              
710             This program is free software; you can redistribute it and/or modify it
711             under the terms of the the Artistic License (2.0). You may obtain a
712             copy of the full license at:
713              
714             L
715              
716             Any use, modification, and distribution of the Standard or Modified
717             Versions is governed by this Artistic License. By using, modifying or
718             distributing the Package, you accept this license. Do not use, modify,
719             or distribute the Package, if you do not accept this license.
720              
721             If your Modified Version has been derived from a Modified Version made
722             by someone other than you, you are nevertheless required to ensure that
723             your Modified Version complies with the requirements of this license.
724              
725             This license does not grant you the right to use any trademark, service
726             mark, tradename, or logo of the Copyright Holder.
727              
728             This license includes the non-exclusive, worldwide, free-of-charge
729             patent license to make, have made, use, offer to sell, sell, import and
730             otherwise transfer the Package with respect to any patent claims
731             licensable by the Copyright Holder that are necessarily infringed by the
732             Package. If you institute patent litigation (including a cross-claim or
733             counterclaim) against any party alleging that the Package constitutes
734             direct or contributory patent infringement, then this Artistic License
735             to you shall terminate on the date that such litigation is filed.
736              
737             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
738             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
739             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
740             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
741             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
742             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
743             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
744             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
745              
746              
747             =cut
748              
749             1; # End of FFmpeg::Stream::Helper