[Originally posted by geve on 2007-08-16 22:21:11-07]
Hi Phil,
In your example pictures you replace the large picture with a 1 pixel image.
I try to do the same:
I Created a 1_pixel.jpg image
then
c:\..\perl.exe exiftool -TagsFromFile Canon-PS-S50-3827.jpg 1_pixel.jpg
I get some extra things:
- extra tags in JPEG-APP01: ISO-speed / Contrast / Saturation / Sharpness
- an extra JPEG-APP00
- an extra JPEP-XMP part with adobe stuff.
What did I do wrong?
Ger
[Originally posted by exiftool on 2007-08-16 23:40:03-07]Hi Ger,
Try this (adding "-all:all"):
c:\..\perl.exe exiftool -TagsFromFile Canon-PS-S50-3827.jpg -all:all 1_pixel.jpg
This will force tags to be written to their original group. Otherwise, by
default, tags are written to the preferred group.
- Phil
[Originally posted by geve on 2007-08-18 00:12:29-07]
Hi Phil,
This works much better, I got rid off the XMP. And all the Exif seems copied.
But looking closer, the content has changed not the outcome.
'Shutter speed value' was '-118/32' which is 13 seconds is changed to '-11791/3196' this is also 13 seconds.
Maybe I have to explain what I was trying: I have a huge pile of photo's. I'am not interested in the actual photo's. So I thought of saving disk space, if I could minimize the picture without changing any EXIF data.
Is this possible?
--Ger.
[Originally posted by exiftool on 2007-08-18 11:27:30-07]
Hi Ger,
ExifTool writes tags individually, so the result will usually differ on a
byte level from the original file. You can't use ExifTool to replace an
image and leave the metadata exactly the same. To do this, I have
written a custom script that may be of use to you:
I use this script to generate JPEG images for my meta information
repository. Currently the script is written to insert the image
from "t/images/Writer.jpg" into the specified file(s), but you can
edit the script to change this to whatever you want.
- Phil
#!/usr/bin/perl -w
my $help = <<_END_;
#
# File: swap_image
#
# Description: Swap image in JPEG file, preserving meta information
#
# Syntax: swap_image [OPTIONS] FILE/DIR [...]
#
# Options: -d - dummy mode (doesn't actually change the file)
# -r - recursively process sub-directories
# -i DIR - ignore specified directory
# -f - fix truncated jpg
# -v - verbose
#
# Revisions: 01/25/06 - PH Created from clean_jpg
# 01/31/07 - PH Send warnings to stderr
#
_END_
#
# Legal: Copyright (c) 2004-2005 Phil Harvey
# You are free to use/modify this script for non-profit
# purposes. Not responsible for loss or damages.
#
# Mail problems/comments to philharvey66 at gmail.com
#
use strict;
require 5.002;
sub SwapDir($);
sub SwapJpg($);
my $newData;
open FILE, "t/images/Writer.jpg" or die "error opening t/images/Writer.jpg\n";
seek FILE, 2, 0 or die "Error seeking\n";
read FILE, $newData, 65536 or die "Error reading from file\n";
close FILE;
my @files;
my $recurse = 0;
my $verbose;
my $dummy;
my @ignore;
my $count = 0;
my $count_ok = 0;
my $count_dir = 0;
my $cleaned_bytes = 0;
my $fix_truncated_jpg;
my %jpegMarker = (
0x01 => 'TEM',
0xc0 => 'SOF0', # to SOF15, with a few exceptions below
0xc4 => 'DHT',
0xc8 => 'JPGA',
0xcc => 'DAC',
0xd0 => 'RST0',
0xd8 => 'SOI',
0xd9 => 'EOI',
0xda => 'SOS',
0xdb => 'DQT',
0xdc => 'DNL',
0xdd => 'DRI',
0xde => 'DHP',
0xdf => 'EXP',
0xe0 => 'APP0', # to APP15
0xf0 => 'JPG0',
0xfe => 'COM',
);
my $arg;
while ($arg = shift)
{
if ($arg =~ /^-/) {
for (my $i=1; ($_=substr $arg, $i, 1); ++$i) {
/d/ and $dummy = 1, next;
/f/ and $fix_truncated_jpg = 1, next;
/i/ and push(@ignore,shift), next;
/r/ and $recurse = 1, next;
/v/ and $verbose = 1, next;
print "Unknown option $_\n";
exit 1;
}
} else {
push @files, $arg;
}
}
unless (@files) {
print $help;
exit 1;
}
my $filename;
foreach $filename (@files) {
if (-d $filename) {
SwapDir($filename);
} elsif (-e $filename) {
my $result = SwapJpg($filename);
if ($result > 1) {
++$count;
} elsif ($result > 0) {
++$count_ok;
}
} else {
die "Can't open $filename\n";
}
}
if ($count or $count_ok or $count_dir>1) {
printf("%5d directories scanned\n", $count_dir) if $count_dir > 1;
printf("%5d JPG image swapped\n", $count) if $count;
printf("%5d JPG files already swapped\n", $count_ok) if $count_ok;
if ($cleaned_bytes >= 102400000) {
printf("%5d MB saved\n", $cleaned_bytes / (1024*1024));
} elsif ($cleaned_bytes >= 100000) {
printf("%5d KB saved\n", $cleaned_bytes / 1024);
} else {
printf("%5d bytes saved\n", $cleaned_bytes);
}
print "But this was dummy mode, so nothing was changed\n" if $dummy;
} else {
print "Nothing to do.\n";
}
exit($count ? 0 : 1);
#------------------------------------------------------------------------------
# Get JPEG marker name
# Inputs: 0) Jpeg number
# Returns: marker name
sub JpegMarkerName($)
{
my $marker = shift;
my $markerName = $jpegMarker{$marker};
unless ($markerName) {
$markerName = $jpegMarker{$marker & 0xf0};
if ($markerName and $markerName =~ /^([A-Z]+)\d+$/) {
$markerName = $1 . ($marker & 0x0f);
} else {
$markerName = sprintf("0x%.2x", $marker);
}
}
return $markerName;
}
sub SwapDir($)
{
my $dir = shift;
opendir(DIR_HANDLE, $dir) or die "Error opening directory $dir\n";
my @file_list = readdir(DIR_HANDLE);
closedir(DIR_HANDLE);
++$count_dir;
my $file;
foreach $file (@file_list) {
my $path = "$dir/$file";
if (-d $path) {
next if $file =~ /^\./; # ignore dirs starting with "."
next if grep /^$file$/, @ignore;
$recurse and SwapDir($path);
next;
}
if ($file =~ /\.jpg$/i) {
my $result = SwapJpg($path);
if ($result > 1) {
++$count;
} elsif ($result > 0) {
++$count_ok;
}
}
}
}
# rewrite a jpg file, swapping image while preserving all meta information
# Inputs: 0) file name
# - returns 0 on error, 1 if nothing done, 2 if any junk was removed
sub SwapJpg($)
{
my $infile = shift;
my $outfile = '';
my $success = 0;
my $saved = 0;
my ($s,$length,$buff);
my ($ch,$data,$ord_ch,$done_meta);
$verbose and print "File: $infile\n";
open(JPG_IN,$infile) or return $success;
binmode( JPG_IN );
# create name of temporary file in same directory
if ($infile =~ /(.*\/)/) {
$outfile = $1;
}
$outfile .= "icat_CleanJpg.tmp";
unless (open(JPG_OUT,">$outfile")) {
close(JPG_IN);
return $success;
}
binmode( JPG_OUT );
# set input record separator to 0xff (the JPEG marker) to make reading quicker
my $oldsep = $/;
$/ = "\xff";
if (read(JPG_IN,$s,2)==2 and $s eq "\xff\xd8" and print JPG_OUT $s) {
# read file until we reach an end of image (EOI)
Marker: for (;;) {
# Find next marker (JPEG markers begin with 0xff)
$data = <JPG_IN>;
defined($data) or last;
chomp $data; # remove 0xff
$saved += length($data);
# print JPG_OUT $data or last;
# JPEG markers can be padded with unlimited 0xff's
for (;;) {
read(JPG_IN, $ch, 1) or last Marker;
$ord_ch = ord($ch);
last if $ord_ch != 0xff; # exit loop before printing marker
++$saved;
}
my $hdr = "\xff" . $ch; # segment header
# Now, $ord_ch is the value of the marker.
if (($ord_ch >= 0xc0) && ($ord_ch <= 0xc3)) {
# this is an image block
read(JPG_IN, $buff, 7) == 7 or last;
# print JPG_OUT $hdr,$buff or last;
$saved += 9;
# handle stand-alone markers 0x01 and 0xd0-0xd7
# (and the non-marker 0x00, which follows an 0xff if it exists in data)
} elsif ($ord_ch==0x00 or $ord_ch==0x01 or ($ord_ch>=0xd0 and $ord_ch<=0xd7)) {
# print JPG_OUT $hdr or last;
$saved += 2;
} elsif ($ord_ch==0xd9) { # end of image (EOI)
$success = 1;
# write our substitute image
print JPG_OUT $newData or $success = 0;
$saved += 2 - length $newData;
# copy over anything after EOI
my $preview_size = 0;
while (read(JPG_IN, $buff, 65536)) {
print JPG_OUT $buff or $success = 0;
$preview_size += length $buff;
}
if ($verbose and $preview_size) {
printf(" Preview image : %6d bytes -- Kept\n", $preview_size);
}
last;
} else {
# We **MUST** skip variables, since FF's within variable names are
# NOT valid JPEG markers
read(JPG_IN, $s, 2) == 2 or last;
$length = unpack("n",$s);
last if $length < 2;
read(JPG_IN, $buff, $length-2) == $length-2 or last;
# is this meta information (APP or COM segment)?
my $is_meta;
if (($ord_ch >= 0xe0 and $ord_ch <= 0xef) or $ord_ch == 0xfe) {
my $mrkr = JpegMarkerName($ord_ch);
$done_meta and warn " >>>> Segments out of order! - $infile\n";
$is_meta = 1;
} else {
$done_meta = 1;
}
my $mrkr;
if ($verbose) {
$mrkr = '(' . JpegMarkerName($ord_ch) . ')';
$mrkr .= ' ' if length($mrkr) < 6;
}
if ($is_meta) {
$verbose and printf(" Marker 0x%x $mrkr: %6d bytes -- Kept\n", $ord_ch, $length);
print JPG_OUT $hdr,$s,$buff or last;
} else {
if ($fix_truncated_jpg) {
$success = 1;
print JPG_OUT $newData;
my $pos = tell JPG_IN;
seek JPG_IN, 0, 2;
$saved += 2 + tell(JPG_IN) - $pos - length $newData;
$saved = 1 if $saved < 0;
last;
}
$verbose and printf(" Marker 0x%x $mrkr: %6d bytes -- Cleaned\n", $ord_ch, $length);
$saved += $length + 2;
}
}
}
}
$/ = $oldsep; # return separator to original value
close(JPG_IN);
close(JPG_OUT) or $success = 0;
if ($success and $saved > 0 and not $dummy) {
unless (rename($outfile,$infile)) {
$success = 0;
unlink($outfile);
}
} else {
unlink($outfile);
warn " >>>>>>>>>> Error in $infile\n" unless $success;
}
$success and $verbose and printf(" Total saved :%7d bytes\n", $saved);
if ($success and $saved > 0) {
$cleaned_bytes += $saved;
$saved = 1;
} else {
$saved = 0;
}
return $success + $saved;
}
# end