anytophone



#!/usr/bin/env perl
# anytophone - Converts video clips to "phone" formats
# License:  Creative Commons Attribution-NonCommercial-ShareAlike 3.0
# Revision: 111111

#---------------------------------------------------------------------
#                         license information
#---------------------------------------------------------------------

# This section may not be modified except as approved by the author or
# licensor, or to make non-content changes such as adjustments to par-
# agraph formatting or white space.

# This  version of this software is  distributed  under  the following
# license:

# You may use, modify, and redistribute this software without fees  or
# royalties, but only under the terms and conditions set forth  by the
# license.  In particular, copies and derived works cannot be used for
# commercial purposes.  Additionally, the license propagates to copies
# and derived works. Furthermore, you must provide attribution "in the
# manner  specified  by the  author or licensor".  The latter point is
# discussed below.

# The author (and licensor) hereby specifies that  attribution must be
# handled in the following manner:  a. If the software is interactive,
# any  About or Credits dialog boxes, windows, or output text provided
# by the  original version must be preserved and readily accessible to
# the end user at runtime.  b. If the software is non-interactive,  or
# if it does not  provide  About or Credits dialog boxes, windows,  or
# output text,  the  operating system and/or  desktop environment used
# must provide attribution that is  visible and/or  readily accessible
# to the end user at runtime.

# The following techniques  do not meet the  attribution requirements:
# Attribution through text  files, attribution  through  printed docu-
# mentation,  verbal  attribution, or postings on  external  web sites
# (i.e.,  web  sites  that are not an intrinsic local component of the
# operating system or  desktop environment used).  These  examples are
# provided for illustrative purposes only.

# It should be noted that trademarks are an additional issue.  If this
# software  uses any  trademarks,  trademark-related  restrictions may
# apply.

# This is not a  complete explanation of the  terms and conditions in-
# volved.  For more information, see the Creative Commons Attribution-
# NonCommercial-ShareAlike 3.0 license.

#---------------------------------------------------------------------
#                        standard module setup
#---------------------------------------------------------------------

require 5.10.1;
use strict;
use Carp;
use warnings;
                                # Trap warnings
$SIG {__WARN__} = sub { die @_; };

#---------------------------------------------------------------------
#                           basic constants
#---------------------------------------------------------------------

use constant ZERO  => 0;        # Zero
use constant ONE   => 1;        # One
use constant TWO   => 2;        # Two

use constant FALSE => 0;        # Boolean FALSE
use constant TRUE  => 1;        # Boolean TRUE

#---------------------------------------------------------------------
#                         program parameters
#---------------------------------------------------------------------

# $REVISION  specifies a program revision string.  This  should  be  a
# short text string. The string should be changed whenever the program
# is modified.

my $REVISION = '111111';

#---------------------------------------------------------------------

# If the "help" screen(s) used are more than about 23 lines long,  set
# $USE_LESS to TRUE. Otherwise, set this parameter to FALSE.

my $USE_LESS = TRUE;

#---------------------------------------------------------------------

# These parameters may  be set by  option switches  with the same name
# (in lower case). If they're set, they specify arguments for associa-
# ted "mencoder" filters.

my $EQ2     = "";
my $HQDN3D  = "";

#---------------------------------------------------------------------

# $FLAG_OVERWRITE specifies whether or not overwrites should be allow-
# ed. Valid settings include:
#
#     0 , off , false , n , no      - Don't allow overwrites
#     1 , on  , true  , y , yes     - Allow overwrites

# The factory default is "no". The command-line  switch  "--purge" may
# be used to override the value specified below.

my $FLAG_OVERWRITE = "no";

#---------------------------------------------------------------------

# $FLAG_SHARPEN specifies  whether or not videos  should be sharpened.
# Valid settings include:
#
#     0 , off , false , n , no      - Don't sharpen
#     1 , on  , true  , y , yes     - Sharpen

# The factory default is "yes".  The  command-line switch  "--sharpen"
# may be used to override the value specified below.

my $FLAG_SHARPEN = "yes";

#---------------------------------------------------------------------

# $INPASPECT specifies the  video input-file's aspect ratio.  Two set-
# tings are supported: "4/3" and "16/9". Note: These should be strings
# (not numeric values).

# The factory default is "4/3". The command-line switch "--aspect" may
# be used to override the value specified below.  Additionally, if the
# user doesn't specify "--aspect",  the program may choose a different
# value automatically at run-time.

my $INPASPECT = "4/3";

#---------------------------------------------------------------------

# $OUTFPS  specifies the video output-file's framerate.  This value is
# expressed in  frames  per second,  and  it can have a  decimal  por-
# tion.

# The factory setting (and the recommended setting, in most cases)  is
# "23.976".  The command-line  switch "--outfps" may  be used to over-
# ride the value specified below.

my $OUTFPS = "23.976";

#---------------------------------------------------------------------

# $XTEMPDIR  specifies an absolute pathname for a temporary-files dir-
# ectory.  The directory used  should already exist,  and it should be
# world-writable. The factory setting is "/var/tmp".

my $XTEMPDIR = "/var/tmp";

#---------------------------------------------------------------------

# TEMP_VIDEO specifies a pathname for  a  temporary  video  file.  The
# pathname may be relative or absolute. It should end with ".avi".

# The factory default is:
#
#     $TEMP_VIDEO = "$XTEMPDIR/temp$$.avi";

my $TEMP_VIDEO = "$XTEMPDIR/temp$$.avi";

#---------------------------------------------------------------------

# $OUTVIDRATE specifies the video output-file's  bitrate.  This should
# be an integer from 300 to 4000. Lower numbers  produce lower-quality
# output.

# The factory setting  is "600".  The command-line switch  "--bitrate"
# may be used to override the value specified below.

my $OUTVIDRATE = "600";

#---------------------------------------------------------------------

# $OUTWIDTH specifies the video output-file's frame width.  This value
# is expressed in pixels . The factory setting is "320".  The command-
# line switch "--width" may be used to override  the  value  specified
# below.

my $OUTWIDTH   = "320";

#---------------------------------------------------------------------
#                             usage text
#---------------------------------------------------------------------

my $USAGE_TEXT_MAIN = << 'END_OF_DOCUMENTATION';
__META_PROGNAME__ rev. __META_REVISION__
- Converts video clips to "phone" formats

1. Usage:  __META_PROGNAME__ -i INPUT.flv -o OUTPUT.mp4 ...

This program converts an arbitrary video clip to the following format:
MP4 video, MP3 audio, and MP4 container. The result should be playable
on various smartphones.

By default,  output width is set to 320.  An option switch of the form
"--width 480" may be used to select the width that's appropriate for a
given model of phone.

For more information (including a list of supported options),  see the
following sections.  To page up and down,  use the PgUp and PgDn keys.
To exit this "help" text, press "q" or "Q".

----------------------------------------------------------------------

2. Options:

Note:  The exact number of dashes used at  the start of a switch isn't
important. Additionally, for switches that take arguments, "--foo bar"
and "--foo=bar" are equivalent.

    --aspect  ...           For an explanation of "--aspect",  see the
                            notes at the end of this section.

    --eq2     ...           Can be used  to adjust  brightness and re-
                            lated values. Takes four real numbers sep-
                            arated by colons as arguments:

                            Gamma       0.1 to 10 (1 = normal)
                            Contrast   -2   to  2 (1 = normal)
                            Brightness -1   to  1 (0 = normal)
                            Saturation  0   to  3 (1 = normal) 

    --hq      ...           Can be used to  activate a filter known as
                            "hqdn3d". Use if a video has visual noise.
                            Takes three or four real numbers separated
                            by colons as arguments. Some possible val-
                            ues:

                            2:1:2       For general cases
                            2:1.5:3     For MPEG-2 conversions
                            3:2:3:3     For animation

    --hqdn3d  ...           Same as "--hq ..."

    --input   INPFILE.mp4   Required switch.  Sets pathname for  input
                            file. Numerous formats are supported.  Can
                            also use "-i" or "--inp".

    --output  OUTFILE.mp4   Required switch.  Sets pathname for output
                            file.  Pathname must end with ".mp4".  Can
                            also use "-o" or "--out".

    --fps     23.976        Sets video-output  framerate.  The default
                            setting is "23.976". "--outfps" is an ali-
                            as for "--fps".

    --purge   yes           By default,  this  program won't overwrite
                            existing files.  If you'd  like  to  allow
                            overwrites, specify "--purge yes". You can
                            also use "--overwrite". The following flag
                            values are equivalent: 1, on, true, y, yes

    --sharpen no            By default,  this program  sharpens  video
                            clips.  If you'd like to disable this fea-
                            ture, specify "--sharpen no". You can also
                            use "--sharp".  The following  flag values
                            are equivalent: 0, off, false, n, no

    --width   480           Sets video-output  frame width  (expressed
                            in pixels).  The default setting is "320".
                            For new devices, try "480".

The "--aspect" switch should be optional, in most cases.  However, for
some video clips, it may be required.  If a given clip produces output
that  appears to be  stretched or squashed,  you may  be  able  to use
"--aspect" to correct the problem.

"--aspect" identifies the  input file's aspect ratio.  If the original
source is fullscreen,  try setting  "--aspect" to  "4/3".  Note:  If a
widescreen video clip  is embedded in a fullscreen frame,  treat it as
fullscreen.

If the original source is widescreen (and it's not embedded in a full-
screen frame), try setting "--aspect" to "16/9".

If the  "--aspect" settings "4/3" and "16/9"  don't work,  try numbers
between "1.00" and "2.00".

----------------------------------------------------------------------

3. Supported input-file formats.

This program would work with most input files,  including typical AVI,
FLV, MKV, MOV, MPEG, RM, WEBM, and WMV files. Flash animations  (i.e.,
SWF files)  aren't supported  but Flash video files (i.e.,  FLV files)
_are_ supported.

Note:  This program is  intended for  use with  short video clips.  It
doesn't support video DVDs.

----------------------------------------------------------------------

4. Requirements.

This program requires three video utility programs:  ffmpeg, mencoder,
and mplayer. Some Linux distros provide the latter programs, and  some
don't.

If possible, use versions dated  mid-2010 or later.  You can use older
versions.  However,  if you do so,  you may need to modify the program
slightly.

These three programs must include  the appropriate  codecs.  This is a
"build"-time  configuration issue.  For  more  information,  consult a
developer or a sysadmin.

This program also requires "perl" and "less".  The latter programs are
standard Linux features. They're provided by almost every distro.

----------------------------------------------------------------------

5. Program license.

This program is distributed under the following license:

      Creative Commons Attribution-NonCommercial-ShareAlike 3.0

Some  specific terms and conditions apply.  For more information,  see
the "license information" section in the source code.
END_OF_DOCUMENTATION

#---------------------------------------------------------------------
#                       misc. global variables
#---------------------------------------------------------------------

my $PROGNAME;                   # Single-word program name
   $PROGNAME =  $0;             # Obtain program name
   $PROGNAME =~ s@^.*/@@;       # Remove path component (if any)

my $INPFILE;                    # Input  -file (or pathname)
my $OUTFILE;                    # Output -file (or pathname)

#---------------------------------------------------------------------
#                      misc. tables and patterns
#---------------------------------------------------------------------

# @VIDEO_EXT specifies the video-filename extensions that this program
# should accept on the input side. Leading periods are omitted. Notes:
# a. Alphabetic case is ignored.  b. This array is  only used  to con-
# struct $VIDEO_EXT. After that, it's discarded.

# $VIDEO_EXT is a pattern-string version of @VIDEO_EXT.

my @VIDEO_EXT = qw
(
    asf    avi    flv    mkv    mov    m2v
    m4v    mp4    mpeg   mpg    ogm    qt
    qtw    rm     webm   wmv
);

my $VIDEO_EXT =  join '|', @VIDEO_EXT;
   $VIDEO_EXT =~ s@\.@\\.@g;
   @VIDEO_EXT =  ();

#---------------------------------------------------------------------

# @ExternalPrograms lists the names  (without directory components) of
# some  of the external programs  used by this program.  This array is
# used  by code which verifies that the  external programs are  avail-
# able.

# "less" and "perl" are omitted.  Reasons:  "less" is  omitted because
# it's used, but not strictly required. "perl" is omitted because this
# program  won't run without it,  so there's no need to  check for its
# presence.

my @ExternalPrograms = qw (ffmpeg mencoder mplayer);

#---------------------------------------------------------------------

# The keys of  %OptionArgs are the names of option  switches that take
# arguments  (omitting  the leading dashes).  The corresponding values
# are pointers to the associated option-value variables.

my %OptionArgs =
(
    'aspect'    => \$INPASPECT       ,
    'eq2'       => \$EQ2             ,
    'hq'        => \$HQDN3D          ,
    'hqdn3d'    => \$HQDN3D          ,
    'vidrate'   => \$OUTVIDRATE      ,
    'width'     => \$OUTWIDTH        ,

    'fps'       => \$OUTFPS          ,
    'outfps'    => \$OUTFPS          ,

    'i'         => \$INPFILE         ,
    'inp'       => \$INPFILE         ,
    'input'     => \$INPFILE         ,

    'o'         => \$OUTFILE         ,
    'out'       => \$OUTFILE         ,
    'output'    => \$OUTFILE         ,

    'overwrite' => \$FLAG_OVERWRITE  ,
    'purge'     => \$FLAG_OVERWRITE  ,

    'sharp'     => \$FLAG_SHARPEN    ,
    'sharpen'   => \$FLAG_SHARPEN
);

#---------------------------------------------------------------------
#                          support routines
#---------------------------------------------------------------------

# "UsageError" prints  usage text for the current program,  then term-
# inates the program with exit status one.

#---------------------------------------------------------------------

sub UsageError
{
    my $UsageText;              # Usage text
                                # Copy appropriate text block
    $UsageText = $USAGE_TEXT_MAIN;
                                # Adjust the usage text
    $UsageText =~ s@(__META_REVISION__)\s+(-)@$1 $2@;
    $UsageText =~ s@__META_PROGNAME__@$PROGNAME@g;
    $UsageText =~ s@__META_REVISION__@$REVISION@g;
    $UsageText =~ s@^\s+@@s;
    $UsageText =~ s@\s*\z@\n@s;

    if ($USE_LESS && (-t STDOUT) && open (OFD, "|/usr/bin/less"))
    {                           # Display usage text using "less"
        $UsageText .= << 'END'; # 'END' should be single-quoted here

To exit this "help" text, press "q" or "Q".

END
        print OFD $UsageText;
        close OFD;
    }
    else
    {                           # Print usage text directly
        print "\n" if -t STDOUT;
        print $UsageText;
        print "\n" if -t STDOUT;
    }

    exit ONE;                   # Terminate the program
}

#---------------------------------------------------------------------
#                            main routine
#---------------------------------------------------------------------

sub Main
{
    my $cmd;                    # Shell-level command string
    my $str;                    # Scratch

                                # Flag: User  specified  input  aspect
                                # ratio explicitly
    my $explicit_aspect = FALSE;

#---------------------------------------------------------------------
# Initial setup.

    select STDERR; $| = ONE;    # Set flush-on-write mode for STDERR
    select STDOUT; $| = ONE;    # Set flush-on-write mode for STDOUT
    undef $/;                   # Set read-entire-file mode

#---------------------------------------------------------------------
# Print usage text, if appropriate.

    if (!scalar (@ARGV))        # Print usage text?
    {                           # Yes
        &UsageError();          # Print usage text
        exit ONE;               # Terminate the program
    }

#---------------------------------------------------------------------
# Handle option switches.

    for my $ii (ZERO..$#ARGV)
    {
        my $arg     = $ARGV [$ii];
        my $OrigArg = $arg;
        next unless length ($arg);

        if ($arg !~ s@^-+@@)
        {
            die "Error: Invalid argument: $arg\n";
        }

        $ARGV [$ii] = "";

        my ($name, $rest) = $arg =~ m@^(\w+)(|=\S+)\z@;
        die "Error: Invalid switch: $OrigArg\n" unless defined $rest;
        $rest =~ s@^=@@;
                                # Does switch take a value?
        if (defined (my $ptr_value = $OptionArgs {$name}))
        {                       # Yes - Was "--name=value" used?
            if (!length ($rest))
            {                   # No  - Pull in next argument
                my $jj = $ii + ONE;
                die "Error: Missing argument for $OrigArg\n"
                    unless defined ($str = $ARGV [$jj]) &&
                           ($str =~ m@^\S+\z@);
                $rest       = $str;
                $ARGV [$jj] = "";
            }
                                # Handle a special case
            $explicit_aspect = TRUE if $name eq 'aspect';

                                # Save specified value
            $$ptr_value = $rest;
            next;
        }
        else
        {                       # No  - Switch doesn't take a value

# Presently, there are no switches that don't take values. If switches
# of this type are added, they should be handled here.

        }
                                # Invalid switch
        die "Error: Invalid switch: $OrigArg\n";
    }
                                # Discard slots that we cleared
    @ARGV = grep { length; } @ARGV;

#---------------------------------------------------------------------
# Verify that required external programs are present.

    for my $program (sort @ExternalPrograms)
    {
        my $found = FALSE;
        my $PATH  = $ENV {PATH};
           $PATH  = "" unless defined $PATH;

        for my $dir (split (/:+/, $PATH))
        {
            next unless -d $dir;
            $dir =~ s@([^/])\z@$1/@;
            $str =  "$dir$program";
            next unless (-f $str) && (-r $str) && (-x $str);
            $found = TRUE;
            last;
        }

        die "Error: Required program not found via PATH: $program\n"
            unless $found;
    }

#---------------------------------------------------------------------
# Check and/or adjust misc. parameter(s).

    for my $ptr_value (\$FLAG_OVERWRITE, \$FLAG_SHARPEN)
    {
        $$ptr_value =~ s@^(0|off|false|n|no)\z@0@i;
        $$ptr_value =~ s@^(1|on|true|y|yes)\z@1@i;
    }

    die "Error: Invalid --overwrite setting: $FLAG_OVERWRITE\n"
        unless $FLAG_OVERWRITE =~ m@^[01]\z@;

    die "Error: Invalid --sharpen setting: $FLAG_SHARPEN\n"
        unless $FLAG_SHARPEN =~ m@^[01]\z@;

#---------------------------------------------------------------------
# Check output-file setting.

    if (!defined ($OUTFILE))
    {
        die "Error: Didn't specify a video-output file\n";
    }

    if ($OUTFILE !~ m@\.mp4\z@)
    {
        die "Error: Output-file name must end with .mp4: $INPFILE\n";
    }

    if (!$FLAG_OVERWRITE && (-e $OUTFILE))
    {
        print STDERR << "END";

Error: Specified output file already exists: $OUTFILE

If you'd like to enable overwrites, use "--overwrite yes"

END
        exit ONE;
    }

    if (open (OFD, ">$OUTFILE"))
    {
        close OFD;
        unlink $OUTFILE;
    }
    else
    {
        print STDERR << "END";

Error: Unable to create specified output file: $OUTFILE

Possible reasons: The target directory may not exist, you may not have
write access to the directory,  you may not have sufficient privileges
to overwrite an existing copy  of the file,  or a broken symbolic link
may be involved.

END
        exit ONE;
    }

#---------------------------------------------------------------------
# Check input-file setting.

    if (!defined ($INPFILE))
    {
        die "Error: Didn't specify a video input file\n";
    }

    if ($INPFILE !~ m@\.($VIDEO_EXT)\z@i)
    {
        die "Error: Not a supported file: $INPFILE\n";
    }

    die "Error: Invalid symbolic link: $INPFILE\n"
        if (-l $INPFILE) && !-f $INPFILE;

    die "Error: Missing or invalid file: $INPFILE\n"
        unless (-f $INPFILE) && (-B $INPFILE);

    if (!$explicit_aspect)
    {
        $cmd = << "END";        # "END" must be double-quoted here
mplayer -vo null -ao null -frames 0 -softsleep -identify
"$INPFILE" 2>&1 | grep ID_
END
        $cmd =~ s@\s+\z@@s;
        $cmd =~ s@\s*\n\s*@ @gs;
        $str = `$cmd`;
        $str = "" unless defined $str;

        my ($width    ) = $str =~ m@ID_VIDEO_WIDTH\s*=\s*([0-9\.]+)@;
            $width      = ZERO unless defined $width    ;

        my ($height   ) = $str =~ m@ID_VIDEO_HEIGHT\s*=\s*([0-9\.]+)@;
            $height     = ZERO unless defined $height   ;

        my ($x_aspect ) = $str =~
            m@\nID_VIDEO_ASPECT\s*=\s*(\d+\.?\d*)\n@;
            $x_aspect   = ZERO unless defined $x_aspect ;
            $x_aspect   = ZERO
                if ($x_aspect < 0.25) || ($x_aspect > 3.00);

        my $comasp = ZERO;
           $comasp = $width / $height if $height > ZERO;
           $comasp = $x_aspect if $x_aspect > ZERO;

           if ($comasp =~ m@^1\.3@) { $INPASPECT = "4/3"   ; }
        elsif ($comasp =~ m@^1\.7@) { $INPASPECT = "16/9"  ; }
        elsif (($comasp >= 1.00) && ($comasp < 1.90))
                                    { $INPASPECT = $comasp ; }
    }

#---------------------------------------------------------------------
# Check some of the settings provided.

# This code verifies that  we  can  create, write to,  and  delete the
# specified temporary and/or output files.

    $cmd = << "END";            # "END" must be double-quoted here
touch "$TEMP_VIDEO" "$OUTFILE" || exit 1 __NEWLINE__
rm    "$TEMP_VIDEO" "$OUTFILE" || exit 1
END
    $cmd =~ s@\s+\z@@s;
    $cmd =~ s@\n@@gs;
    $cmd =~ y/\011\040/ /s;
    $cmd =~ s@ *__NEWLINE__@\n@gi;

    $str = `$cmd 2>&1`;
    $str = "" unless defined $str;
    exit ONE if $str =~ m@\S@;

#---------------------------------------------------------------------
# Transcode input file to MP4 video MP3 audio AVI container.

    $cmd = << "END";            # "END" must be double-quoted here
mencoder
    "$INPFILE"
    -vf scale=$OUTWIDTH:-3,harddup
    -ofps $OUTFPS
    -oac mp3lame
    -ovc lavc
    -lameopts cbr:mode=2:br=96
    -lavcopts vcodec=mpeg4:
vbitrate=$OUTVIDRATE:aspect=$INPASPECT:
mbd=2:cbp:trell
    -o "$TEMP_VIDEO"
END
    if (length ($EQ2))
    {
        $cmd =~ s@\b(harddup)@eq2=$EQ2,$1@
    }

    if (length ($HQDN3D))
    {
        $cmd =~ s@\b(harddup)@hqdn3d=$HQDN3D,$1@
    }            

    if ($FLAG_SHARPEN)
    {
        $str = 'smartblur=.6:-.5:0,unsharp=c7x7:0.5:l7x7:0.5';
        $cmd =~ s@\b(harddup)@$str,$1@;
    }

    $cmd =~ s@\s+\z@@s;
    $cmd =~ s@\n@@gs;
    $cmd =~ y/\011\040/ /s;
    $cmd =~ s@ *__NEWLINE__@\n@gi;

    print "$cmd\n";
    system $cmd;

#---------------------------------------------------------------------
# Convert container.

    $cmd = << "END";            # "END" must be double-quoted here
ffmpeg -i "$TEMP_VIDEO" -acodec copy -vcodec copy "$OUTFILE"
END
    $cmd =~ s@\s+\z@@s;
    $cmd =~ s@\n@@gs;
    $cmd =~ y/\011\040/ /s;
    $cmd =~ s@ *__NEWLINE__@\n@gi;

    print "$cmd\n";
    system $cmd;

    unlink $TEMP_VIDEO;
    undef;
}

#---------------------------------------------------------------------
#                            main program
#---------------------------------------------------------------------

&Main();                        # Call the main routine
exit ZERO;                      # Normal exit



To continue, press the browser's Back button.