Files
lazarus-ccr/components/fpexif/tests/multiread/common/mrtmain.pas

541 lines
15 KiB
ObjectPascal

unit mrtmain;
{$IFDEF FPC}
{$mode objfpc}{$H+}
{$ENDIF}
interface
uses
{$IFDEF FPC}
FileUtil,
{$ELSE}
Windows, ImgList, {$IFDEF UNICODE}ImageList,{$ENDIF}
{$ENDIF}
Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls,
ComCtrls, ExtCtrls,
fpeMetaData;
type
{ TMainForm }
TMainForm = class(TForm)
Bevel1: TBevel;
Bevel2: TBevel;
BtnReadFiles: TButton;
BtnCreateTxtFiles: TButton;
BtnRunTest: TButton;
BtnUncheckAll: TButton;
BtnCheckAll: TButton;
EdImageDir: TEdit;
StateImages: TImageList;
MismatchInfo: TLabel;
Memo: TMemo;
Panel1: TPanel;
Panel2: TPanel;
Panel3: TPanel;
FileTreeView: TTreeView;
Panel4: TPanel;
Panel5: TPanel;
Splitter1: TSplitter;
ImageList1: TImageList;
BtnInfo: TButton;
procedure BtnInfoClick(Sender: TObject);
procedure BtnReadFilesClick(Sender: TObject);
procedure BtnRunTestClick(Sender: TObject);
procedure BtnCreateTxtFilesClick(Sender: TObject);
procedure BtnUncheckAllClick(Sender: TObject);
procedure InfoClick(Sender: TObject);
procedure FileTreeViewClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
FTotalCount: Integer;
FMismatchCount: Integer;
function CreateRefTags(ANode: TTreeNode; AFileName: String): Boolean;
function ExtractRefTags(ANode: TTreeNode; AList: TStringList): Boolean;
function GetImageDir: String;
procedure Log(AMsg: String);
procedure RunTest(ANode: TTreeNode);
public
end;
var
MainForm: TMainForm;
implementation
{$IFDEF FPC}
{$R *.lfm}
{$ELSE}
{$R *.dfm}
{$ENDIF}
uses
{$IFDEF FPC}
Process, StrUtils,
{$ELSE}
ShellApi,
{$ENDIF}
fpeGlobal, fpeUtils, fpeTags, fpeExifData;
{ TMainForm }
const
EXIFTOOL_CMD = '..\..\tools\exiftool.exe';
IMG_INDEX_WORKING = 0;
IMG_INDEX_FAIL = 1;
IMG_INDEX_IGNORE = 1;
IMG_INDEX_EXIF = 2;
IMG_INDEX_SUCCESS = 3;
IMG_UNCHECKED = 2; //0;
IMG_CHECKED = 1;
{ Finds all image files in the image folder (--> GetImageDir). For every image
there is a text file containing the meta data written by ExifTool. Reads this
reference file and stores the meta data in the nodes af the FileTreeView. }
procedure TMainForm.BtnReadFilesClick(Sender: TObject);
var
info: TSearchRec;
imgDir: String;
node: TTreeNode;
tagFile: String;
L: TStringList;
s: String;
begin
FileTreeView.Items.Clear;
imgDir := GetImageDir;
if FindFirst(imgDir + '*.jpg', faAnyFile and faDirectory, info) = 0 then
begin
repeat
if (info.Name <> '.') and (info.Name <> '..') and (info.Attr and faDirectory = 0) then
begin
node := FileTreeview.Items.AddChild(nil, ExtractFileName(info.Name));
node.ImageIndex := IMG_INDEX_IGNORE;
tagFile := ChangeFileExt(imgDir + info.Name, '.txt');
if FileExists(tagFile) then
begin
L := TStringList.Create;
try
L.LoadFromFile(tagFile);
// Note: ExifTool wrote the file in UTF8 --> We must convert this for Delphi
{$IFNDEF FPC}
{$IFDEF UNICODE}
L.Text := UTF8Decode(L.Text);
{$ELSE}
s := L.Text;
// s := UTF8Decode(s);
s := fpeUtils.UTF8ToAnsi(s);
L.Text := s;
{$ENDIF}
{$ENDIF}
if ExtractRefTags(node, L) then begin
node.ImageIndex := IMG_INDEX_EXIF;
node.StateIndex := IMG_CHECKED;
end;
finally
L.Free;
end;
end;
node.SelectedIndex := node.ImageIndex;
end;
until FindNext(info) <> 0;
end;
FindClose(info);
end;
procedure TMainForm.BtnRunTestClick(Sender: TObject);
var
node: TTreeNode;
begin
Memo.Lines.Clear;
FMismatchCount := 0;
FTotalCount := 0;
node := FileTreeView.Items.GetFirstNode;
while node <> nil do begin
RunTest(node);
node := node.GetNextSibling;
end;
MismatchInfo.Caption := Format('%d mismatches out of %d tests (%.0f%%)', [
FMismatchCount, FTotalCount, FMismatchCount/FTotalCount*100]);
MismatchInfo.Show;
end;
procedure TMainForm.BtnUncheckAllClick(Sender: TObject);
var
node: TTreeNode;
checkNode: Boolean;
begin
checkNode := (Sender = BtnCheckAll);
node := FileTreeView.Items.GetFirstNode;
while node <> nil do begin
if checkNode and (node.StateIndex = IMG_UNCHECKED) then
node.StateIndex := IMG_CHECKED
else if not checkNode and (node.StateIndex = IMG_CHECKED) then
node.StateIndex := IMG_UNCHECKED;
node := node.GetNextSibling;
end;
end;
procedure TMainForm.InfoClick(Sender: TObject);
begin
Memo.Lines.LoadfromFile('readme.txt');
end;
procedure TMainForm.BtnCreateTxtFilesClick(Sender: TObject);
var
imgDir: String;
node: TTreeNode;
begin
if not FileExists(EXIFTOOL_CMD) then
begin
MessageDlg(Format('Program "ExifTool" not found in folder "%s".', [
ExtractFileDir(ExpandFilename(EXIFTOOL_CMD))
]), mtError, [mbOK], 0
);
exit;
end;
imgDir := GetImageDir;
node := FileTreeView.Items.GetFirstNode;
while (node <> nil) do begin
node.DeleteChildren;
node.ImageIndex := -1;
node := node.GetNextSibling;
end;
node := FileTreeView.Items.GetFirstNode;
while (node <> nil) do begin
node.ImageIndex := IMG_INDEX_WORKING;
Application.ProcessMessages;
if not CreateRefTags(node, imgDir + node.Text) then begin
node.ImageIndex := IMG_INDEX_IGNORE;
end else
node.ImageIndex := IMG_INDEX_EXIF;
node.SelectedIndex := node.ImageIndex;
node.StateIndex := IMG_CHECKED;
node := node.GetNextSibling;
end;
end;
procedure TMainForm.BtnInfoClick(Sender: TObject);
begin
Memo.Lines.LoadFromFile('readme.txt');
end;
function TMainForm.CreateRefTags(ANode: TTreeNode; AFileName: String): Boolean;
var
destFile: String;
output: String;
L: TStringList;
{$IFNDEF FPC}
params: String;
res: Integer;
s: String;
const
DEG_SYMBOL: ansistring = #176;
{$ENDIF}
begin
Result := false;
destFile := ChangeFileExt(AFileName, '.txt');
{$IFDEF FPC}
if RunCommand(EXIFTOOL_CMD, ['-a', '-H', '-s', '-G', '-c', '"%d° %d'' %.2f"\"', AFileName], output) then
// -a ... extract all tags, also duplicates.
// -H ... extract hex tag id if possible
// -s ... short tag name (hopefully this is the dExif tag name)
// -G ... print group name for each tag
// -c ... format for GPS coordinates
begin
if (output = '') then
exit;
L := TStringList.Create;
try
L.Text := output;
if ExtractReftags(ANode, L) then
ANode.ImageIndex := IMG_INDEX_EXIF else
ANode.ImageIndex := IMG_INDEX_IGNORE;
ANode.SelectedIndex := ANode.ImageIndex;
L.SaveToFile(destFile);
Result := true;
finally
L.Free;
end;
end;
{$ELSE}
// params := '/c ' + EXIFTOOL_CMD + ' -a -H -s -G -c "%d' + DEG_SYMBOL + ' %d'' %.2f"\"' + AFileName + ' > ' + destFile;
params := '/c ' + EXIFTOOL_CMD + ' -a -H -s -G -c "%d° %d'' %.2f"\"' + AFileName + ' > ' + destFile;
res := ShellExecute(Application.Handle, 'open', PChar('cmd'), PChar(params), '', SW_HIDE);
if (res <= 32) or not FileExists(destFile) then
exit;
L := TStringList.Create;
try
L.LoadFromFile(destFile);
// Note: ExifTool wrote the file in UTF8 --> We must convert this for Delphi
{$IFDEF UNICODE}
L.Text := UTF8Decode(L.Text);
{$ELSE}
s := UTF8ToAnsi(L.Text);
L.Text := s;
{$ENDIF}
if ExtractRefTags(ANode, L) then
ANode.ImageIndex := IMG_INDEX_EXIF else
ANode.ImageIndex := IMG_INDEX_IGNORE;
ANode.SelectedIndex := ANode.ImageIndex;
Result := true;
finally
L.Free;
end;
{$ENDIF}
end;
function TMainForm.ExtractRefTags(ANode: TTreeNode; AList: TStringList): Boolean;
const
GROUP_START = 1;
GROUP_LEN = 15;
TAGID_START = 19;
TAGID_LEN = 4;
NAME_START = 24;
NAME_LEN = 32;
VALUE_START = 58;
var
i: Integer;
p: Integer;
s: String;
sGroup: String;
sTagID: String;
sTagName: String;
sTagValue: String;
tagID: Word;
node: TTreeNode;
begin
Result := false;
for i:=0 to AList.Count-1 do begin
s := AList[i];
sGroup := trim(Copy(s, GROUP_START, GROUP_LEN));
sTagID := trim(Copy(s, TAGID_START, TAGID_LEN));
sTagName := trim(Copy(s, NAME_START, NAME_LEN));
sTagValue := trim(Copy(s, VALUE_START, MaxInt));
if sTagID = '-' then
Continue;
// So far, consider only EXIF-Tag
if sGroup <> '[EXIF]' then
Continue;
tagID := StrToInt('$' + sTagID);
node := ANode.Owner.AddChild(ANode, sTagName + ': ' + sTagValue);
node.Data := Pointer(PtrInt(tagID));
end;
Result := ANode.Count > 0;
end;
procedure TMainForm.FileTreeViewClick(Sender: TObject);
var
P: TPoint;
ht: THitTests;
node: TTreeNode;
begin
P := FileTreeView.ScreenToClient(Mouse.CursorPos);
ht := FileTreeView.GetHitTestInfoAt(P.X, P.Y);
if htOnStateIcon in ht then begin
node := FileTreeView.GetNodeAt(P.X, P.Y);
if node.StateIndex = IMG_CHECKED then
node.StateIndex := IMG_UNCHECKED else
node.StateIndex := IMG_CHECKED;
end;
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
fpExifFmtSettings.ListSeparator := ' ';
if EdImageDir.Text <> '' then
BtnReadFilesClick(nil);
end;
function TMainForm.GetImageDir: String;
begin
Result := IncludeTrailingPathDelimiter(ExpandFilename(EdImageDir.Text));
Caption := Result;
end;
procedure TMainForm.Log(AMsg: String);
begin
Memo.Lines.Add(AMsg);
Memo.SelStart := Length(Memo.Lines.Text);
end;
{ Loads the image file represented by the specified node, reads the meta data,
and compares with the reference file. }
procedure TMainForm.RunTest(ANode: TTreeNode);
const
{$IFDEF FPC}
GPS_MASK = '%0:.0f° %1:.0f'' %2:.2f"';
{$ELSE}
{$IFDEF UNICODE}
GPS_MASK = '%0:.0f° %1:.0f'' %2:.2f"';
{$ELSE}
GPS_MASK = '%0:.0f'#176' %1:.0f'' %2:.2f"';
{$ENDIF}
{$ENDIF}
var
imgInfo: TImgInfo;
tagName: String;
uctagname: String;
expectedTagValue: String;
currTagValue: String;
s: String;
p: Integer;
node: TTreeNode;
tagID: TTagID;
lTag: TTag;
lTagDef: TTagDef;
// v: Variant;
offs: Int64;
localMismatchCount: Integer;
begin
if ANode.StateIndex = IMG_UNCHECKED then
exit;
if ANode.Count = 0 then begin
Log('Skipping image "' + ANode.Text + '":');
Log(' No EXIF data found by ExifTool.');
Log('');
exit;
end;
localMismatchCount := 0;
Log('Testing image "' + ANode.Text + '":');
imgInfo := TImgInfo.Create;
try
imgInfo.LoadFromFile(GetImageDir + ANode.Text);
if not imgInfo.HasExif then begin
Log('Skipping "' + ANode.Text + '":');
Log(' No EXIF data found by fpExif.');
Log('');
exit;
end;
node := ANode.GetFirstChild;
while node <> nil do begin
s := node.Text;
p := pos(':', s);
if p = 0 then begin
node := node.GetNextSibling;
Log(' Skipping tag "' + s + '": Has no value');
continue;
end;
tagName := trim(Copy(s, 1, p-1));
uctagName := Uppercase(tagName);
lTagDef := FindExifTagDefWithoutParent(PtrInt(node.Data));
if lTagDef = nil then begin
Log(' Skipping tag "' + tagName + '": tag definition not found.');
node := node.GetNextSibling;
Continue;
end;
tagID := lTagDef.TagID;
if (tagID = TAGPARENT_EXIF + $EA1C) then begin // "Padding"
Log(Format(' Skipping tag "%s" ($%.4x): no useful data', [tagName, TTagIDRec(tagID).Tag]));
node := node.GetNextSibling;
Continue;
end;
lTag := imgInfo.ExifData.FindTagByID(tagID);
if lTag = nil then begin
Log(Format('Tag "%s (ID $%.04x) not found.', [tagName, TTagIDRec(tagID).Tag]));
node := node.GetNextSibling;
continue;
end;
// Modify fpExif's tag format to match that used by ExifTool.
case lTag.TagID of
TAGPARENT_GPS + $0000: // GPSVersionID
lTag.ListSeparator := '.';
TAGPARENT_EXIF + $9102, // CompressedBitsPerPixel
TAGPARENT_EXIF + $A20E, // FocalPlaneXResolution
TAGPARENT_EXIF + $A20F: // FocalPlaneYResolution
if lTag is TFloatTag then TFloatTag(lTag).FormatStr := '%2:.3f';
TAGPARENT_EXIF + $A405: // FocalLengthIn35mmFilm
if lTag is TIntegerTag then TFloatTag(lTag).FormatStr := '%d mm';
else
if lTag is TDateTimeTag then
TDateTimeTag(lTag).FormatStr := EXIF_DATETIME_FORMAT
else
if lTag is TGpsPositionTag then
TGpsPositionTag(lTag).FormatStr := GPS_MASK
else
if ltag is TExposureTimeTag then
TExposureTimeTag(ltag).FormatStr := '1/%.0f;%.0f' // to do: use rational values
else
if (lTag is TFloatTag) and
((ucTagName = 'FNUMBER') or (pos('APERTURE', ucTagName) > 0))
then
TFloatTag(lTag).FormatStr := '%2:.1f';
end;
currTagValue := trim(lTag.AsString);
expectedTagvalue := Copy(s, p+1, MaxInt);
p := pos(' -->', expectedTagValue);
if p > 0 then SetLength(expectedTagValue, p);
expectedTagValue := trim(expectedTagValue);
case lTag.TagID of
TAGPARENT_INTEROP + $0001: // InteropIndex
if pos('INTEROP', ucTagName) <> 0 then
expectedTagValue := FirstWord(expectedTagValue);
TAGPARENT_EXIF + $9101: // ComponentsConfiguration
expectedTagValue := LettersOnly(expectedTagValue);
TAGPARENT_EXIF + $9102, // CompressedBitsPerPixel
TAGPARENT_EXIF + $A20E, // FocalPlaneXResolution
TAGPARENT_EXIF + $A20F: // FocalPlaneYResolution
expectedTagValue := Format('%.3f', [StrToFloat(expectedTagValue, fpExifFmtSettings)], fpExifFmtSettings);
else
if (lTag is TIntegerTag) and (pos(';', currTagValue) > 0) then
// currTagValue := ReplaceText(currTagValue, ';', ',')
currTagValue := StringReplace(currTagValue, ';', ',', [rfReplaceAll])
else
if (lTag is TOffsetTag) then begin
offs := StrToInt(currTagValue);
currTagValue := IntToStr(offs + TOffsetTag(lTag).TiffHeaderOffset);
end;
end;
if SameText(expectedTagValue, currTagValue) then
node.ImageIndex := IMG_INDEX_SUCCESS
else begin
Log(' Tag mismatch "' + Format('[$%.4x] %s', [TTagIDRec(tagID).Tag, tagName]) + '"');
Log(' expected: ' + expectedTagValue);
Log(' found: ' + currTagValue);
node.ImageIndex := IMG_INDEX_FAIL;
node.Text := tagname + ': ' + expectedTagValue + ' --> found: ' + currTagValue;
inc(FMismatchCount);
inc(localMismatchCount);
end;
node.SelectedIndex := node.ImageIndex;
node := node.GetNextSibling;
inc(FTotalCount);
end;
if localMismatchCount = 0 then
Log(' All tags matching');
finally
Log('');
imgInfo.Free;
end;
FileTreeView.Invalidate;
end;
end.