2008-01-13 11:44:38 +01:00
|
|
|
#!/usr/bin/perl
|
|
|
|
|
|
|
|
# pic2mpg: Convert picture files to MPEG still frames
|
|
|
|
#
|
|
|
|
# Converts either a single picture file or all files in a
|
|
|
|
# given directory (recursively) to MPEG still frames.
|
|
|
|
#
|
|
|
|
# See the README file for copyright information and how to reach the author.
|
|
|
|
#
|
2008-02-29 14:38:44 +01:00
|
|
|
# $Id: pic2mpg 1.4 2008/02/29 14:34:22 kls Exp $
|
2008-01-13 11:44:38 +01:00
|
|
|
|
|
|
|
## TODO implement HDTV (1920 x 1080)
|
|
|
|
|
|
|
|
use File::Path;
|
2008-02-29 14:38:44 +01:00
|
|
|
use File::Spec;
|
2008-01-13 11:44:38 +01:00
|
|
|
use Getopt::Std;
|
|
|
|
use Image::Size;
|
|
|
|
|
|
|
|
$Usage = qq{
|
|
|
|
Usage: $0 [options] picture-dir mpeg-dir
|
|
|
|
$0 [options] picture-file mpeg-file
|
|
|
|
|
|
|
|
Options: -a Aspect ratio 4:3 (default is 16:9)
|
|
|
|
-f Force conversion
|
2008-02-02 11:34:43 +01:00
|
|
|
-h print Help
|
|
|
|
-i Ignore unknown file types
|
2008-01-13 11:44:38 +01:00
|
|
|
-n NTSC (default is PAL)
|
|
|
|
-v num Verbose (0=none, 1=list files, 2=detailed)
|
|
|
|
-x percent X overscan in percent
|
|
|
|
-y percent Y overscan in percent
|
|
|
|
};
|
|
|
|
|
2008-02-02 11:34:43 +01:00
|
|
|
getopts("afhinv:x:y:") || die $Usage;
|
2008-01-13 11:44:38 +01:00
|
|
|
|
|
|
|
die $Usage if $opt_h;
|
|
|
|
|
|
|
|
$Aspect = $opt_a;
|
|
|
|
$Force = $opt_f;
|
2008-02-02 11:34:43 +01:00
|
|
|
$Ignore = $opt_i;
|
2008-01-13 11:44:38 +01:00
|
|
|
$NTSC = $opt_n;
|
|
|
|
$Verbose = $opt_v;
|
|
|
|
$OverscanX = $opt_x;
|
|
|
|
$OverscanY = $opt_y;
|
|
|
|
|
|
|
|
$ListFiles = $Verbose >= 1;
|
|
|
|
$Detailed = $Verbose >= 2;
|
|
|
|
|
|
|
|
# Screen size:
|
|
|
|
|
|
|
|
$SW = $NTSC ? 720 : 720;
|
|
|
|
$SH = $NTSC ? 480 : 576;
|
|
|
|
|
|
|
|
$ScreenRatio = $Aspect ? 4 / 3 : 16 / 9;
|
|
|
|
|
|
|
|
# Converter programs:
|
|
|
|
|
|
|
|
%PNMCONV = (
|
|
|
|
bmp => "bmptopnm",
|
|
|
|
gif => "giftopnm",
|
|
|
|
jpeg => "jpegtopnm",
|
|
|
|
jpg => "jpegtopnm",
|
|
|
|
png => "pngtopnm",
|
|
|
|
pnm => "cat",
|
|
|
|
tif => "tifftopnm",
|
|
|
|
tiff => "tifftopnm",
|
|
|
|
);
|
|
|
|
|
|
|
|
# Command options:
|
|
|
|
|
|
|
|
die "$0: missing parameter\n" unless $ARGV[0] && $ARGV[1];
|
|
|
|
die "$0: file or directory not found: $ARGV[0]\n" unless -e $ARGV[0];
|
|
|
|
die "$0: source and destination must be different\n" if $ARGV[0] eq $ARGV[1];
|
|
|
|
|
|
|
|
$verbose1 = $Detailed ? "--verbose" : "";
|
|
|
|
$verbose2 = $Detailed ? "-v 2" : "-v 0";
|
|
|
|
$system1 = $NTSC ? "" : "--pal";
|
|
|
|
$system2 = $NTSC ? "n" : "p";
|
|
|
|
$framerate = $NTSC ? "30000:1001" : "25:1";
|
|
|
|
$aspect = $Aspect ? "2" : "3";
|
|
|
|
|
|
|
|
# Convert a single file:
|
|
|
|
|
|
|
|
if (-f $ARGV[0]) {
|
|
|
|
die "$0: mixed file and directory ('$ARGV[0]' <-> '$ARGV[1]')\n" unless !-e $ARGV[1] || -f $ARGV[1];
|
|
|
|
ConvertFile($ARGV[0], $ARGV[1]);
|
|
|
|
exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
die "$0: mixed directory and file ('$ARGV[0]' <-> '$ARGV[1]')\n" unless !-e $ARGV[1] || -d $ARGV[1];
|
|
|
|
|
2008-02-29 14:38:44 +01:00
|
|
|
$PICDIR = File::Spec->rel2abs($ARGV[0]);
|
|
|
|
$MPGDIR = File::Spec->rel2abs($ARGV[1]);
|
2008-01-13 11:44:38 +01:00
|
|
|
|
|
|
|
# Convert pictures to mpegs:
|
|
|
|
|
|
|
|
chdir($PICDIR) || die "$PICDIR: $!\n";
|
|
|
|
|
|
|
|
@Pictures = `find -type f`;
|
|
|
|
chomp(@Pictures);
|
|
|
|
|
|
|
|
for $pic (@Pictures) {
|
|
|
|
my $mpg = "$MPGDIR/$pic.mpg";
|
|
|
|
if ($Force || !-e $mpg || -M $mpg > -M $pic) {
|
|
|
|
(my $dir = $mpg) =~ s/\/[^\/]*$//;
|
|
|
|
mkpath($dir);
|
|
|
|
ConvertFile($pic, $mpg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# Remove mpegs without pictures:
|
|
|
|
|
|
|
|
chdir($MPGDIR) || die "$MPGDIR: $!\n";
|
|
|
|
|
|
|
|
@Mpegs = `find -type f`;
|
|
|
|
chomp(@Mpegs);
|
|
|
|
|
|
|
|
for $mpg (@Mpegs) {
|
|
|
|
my $pic = "$PICDIR/$mpg";
|
|
|
|
$pic =~ s/\.mpg$//;
|
|
|
|
if (!-e $pic) {
|
|
|
|
print "removing $mpg\n";
|
|
|
|
unlink($mpg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# Remove empty directories:
|
|
|
|
|
|
|
|
chdir($MPGDIR) || die "$MPGDIR: $!\n";
|
|
|
|
|
|
|
|
for ($i = 0; $i < 10; $i++) { # dirs might become empty when removing empty subdirs
|
|
|
|
@Dirs = `find -type d -empty`;
|
|
|
|
chomp(@Dirs);
|
|
|
|
last unless @Dirs;
|
|
|
|
|
|
|
|
for $dir (@Dirs) {
|
|
|
|
$dir = EscapeMeta($dir);
|
|
|
|
print "removing $dir\n";
|
|
|
|
!system("rm -rf $dir") || die "$dir: $!\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# Actual file conversion:
|
|
|
|
|
|
|
|
sub ConvertFile
|
|
|
|
{
|
|
|
|
my ($Pict, $Mpeg) = @_;
|
2008-02-25 18:11:33 +01:00
|
|
|
(my $Type) = lc($Pict) =~ /\.([^\.]*)$/;
|
2008-02-02 11:34:43 +01:00
|
|
|
if (!defined $PNMCONV{$Type}) {
|
|
|
|
return if ($Ignore);
|
|
|
|
die "unknown file type '$Type': '$Pict'\n";
|
|
|
|
}
|
2008-01-13 11:44:38 +01:00
|
|
|
my ($w, $h) = imgsize($Pict);
|
|
|
|
print "image size is $w x $h\n" if ($Detailed);
|
|
|
|
if ($w / $h <= $ScreenRatio) {
|
|
|
|
$w = $h * $ScreenRatio;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$h = $w / $ScreenRatio;
|
|
|
|
}
|
|
|
|
my $ScaleW = $SW / $w * (100 - 2 * $OverscanX) / 100;
|
|
|
|
my $ScaleH = $SH / $h * (100 - 2 * $OverscanY) / 100;
|
|
|
|
$Pict = EscapeMeta($Pict);
|
|
|
|
$Mpeg = EscapeMeta($Mpeg);
|
|
|
|
print "$Pict -> $Mpeg\n" if $ListFiles;
|
|
|
|
my $Cmd = "$PNMCONV{$Type} $Pict 2> /dev/null |"
|
|
|
|
. "pnmscale $verbose1 --xscale=$ScaleW --yscale=$ScaleH |"
|
|
|
|
. "pnmpad $verbose1 --black --width $SW --height $SH |"
|
|
|
|
. "ppmntsc $verbose1 $system1 |"
|
|
|
|
. "ppmtoy4m $verbose2 -F $framerate -I p -S 420mpeg2 |"
|
|
|
|
. "mpeg2enc $verbose2 -f 3 -b 12500 -a $aspect -q 1 -n $system2 -o $Mpeg";
|
|
|
|
!system($Cmd) || die "$Cmd: $!\n";
|
|
|
|
$Cmd = "touch -r $Pict $Mpeg";
|
|
|
|
!system($Cmd) || die "$Cmd: $!\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
sub EscapeMeta
|
|
|
|
{
|
|
|
|
my $META = ' !"#$%&\'()*;<>?[\\]`{|}~';
|
|
|
|
my $s = shift;
|
|
|
|
$s =~ s/([$META])/\\$1/g;
|
|
|
|
return $s;
|
|
|
|
}
|