Resource

Resource Basics

Resource Name Compare XMP in File and Catalog
Resource Version 1.1
Category Metadata
Downloads 740
Release Date 6/28/2024 10:02:50 AM
 

Resource Details

Description This script will load the XMP for a file from the file system as well as from the catalog. It then shows these side-by-side and allows you to show either All, Identical, or Differences in the XMP.
Installation Instructions n.a.
Usage Instructions 1. Select one or more thumbs
2. Right click on the thumb and select Run Script from Repository -> Metadata -> Compare XMP in File and Catalog
Changes in this version v1.1; Active selection (all, identical, diff, is now highlighted)
Special Requirements 2024.2.0.6516 or higher
 

Author Information

Author Name IDimager
 

Available Attachments

Attachment #1 CompareXMP.png
 

Script

const
  cShowAll   = 0;
  cShowInter = 1;
  cShowDiff  = 2;

var
  AGrid: TvxStringGrid;
  row: Integer;

  ATabList: TStringList;
  tabcount: Integer;
  activetab: Integer;

  show: Integer;

  showAllButton: TvxRectangle;
  showInterButton: TvxRectangle;
  showDiffButton: TvxRectangle;

  function TabButtonAtIndex(AIndex: Integer): TvxRectangle;
  begin
    result := ATabList.Objects[AIndex];
  end;

  function IndexedDataPointerFromString(AStr: WideString; AIndex: Integer): TTntStringList;
  var
    ASl: TTntStringList;
  begin
    ASl := TTntStringList.Create;
    try
      ASl.CommaText := AStr;
      result := StrToPtrInt(ASl.Strings[AIndex])
    finally
      ASl.Free;
    end;
  end;

  function FileDataPointerFromString(AStr: WideString): TTntStringList;
  begin
    result := IndexedDataPointerFromString(AStr, 0);
  end;

  function CatDataPointerFromString(AStr: WideString): TTntStringList;
  begin
    result := IndexedDataPointerFromString(AStr, 1);
  end;

  function InterDataPointerFromString(AStr: WideString): TTntStringList;
  begin
    result := IndexedDataPointerFromString(AStr, 2);
  end;

  function DiffDataPointerFromString(AStr: WideString): TTntStringList;
  begin
    result := IndexedDataPointerFromString(AStr, 3);
  end;

  function TabFileDataAtIndex(AIndex: Integer): TTntStringList;
  begin
    result := StrToPtrInt(FileDataPointerFromString(TvxRectangle(TabButtonAtIndex(AIndex)).TagString));
  end;

  function TabCatDataAtIndex(AIndex: Integer): TTntStringList;
  begin
    result := StrToPtrInt(CatDataPointerFromString(TvxRectangle(TabButtonAtIndex(AIndex)).TagString));
  end;

  function TabInterDataAtIndex(AIndex: Integer): TTntStringList;
  begin
    result := StrToPtrInt(InterDataPointerFromString(TvxRectangle(TabButtonAtIndex(AIndex)).TagString));
  end;

  function TabDiffDataAtIndex(AIndex: Integer): TTntStringList;
  begin
    result := StrToPtrInt(DiffDataPointerFromString(TvxRectangle(TabButtonAtIndex(AIndex)).TagString));
  end;

  function TabImageAtIndex(AIndex: Integer): TImageItem;
  begin
    result := TImageItem(TvxRectangle(TabButtonAtIndex(AIndex)).TagObject);
  end;

  function ActiveTabButton: TvxRectangle;
  begin
    result := TabButtonAtIndex(activetab);
  end;

  function ActiveTabFileData: TTntStringList;
  begin
    result := TabFileDataAtIndex(activetab);
  end;

  function ActiveTabCatData: TTntStringList;
  begin
    result := TabCatDataAtIndex(activetab);
  end;

  function ActiveTabInterData: TTntStringList;
  begin
    result := TabInterDataAtIndex(activetab);
  end;

  function ActiveTabDiffData: TTntStringList;
  begin
    result := TabDiffDataAtIndex(activetab);
  end;

  function ActiveTabImage: TImageItem;
  begin
    result := TabImageAtIndex(activetab);
  end;

  procedure DumpXmp(AImg: TImageItem; AGrid: TvxStringGrid; AFileSl, ACatSl, AInterSl, ADiffSl: TTntStrings);
  var
    i, AIdx: Integer;
    AFileXmp, ACatXmp, ATempXmp: TXMP;
    ASize: Int64;
    ALeadSl: TTntStringList;
    ACatItem: TCatalogItem;
  begin
    ACatItem := TCatalogItem.Create(nil);

    ACatXmp := TXMP.Create(False);
    AFileXmp := TXMP.Create(False);
    ATempXmp := TXMP.Create(False);
    try
      PublicCatalog.FindItemCombinedByGUID(AImg.GUID, ACatItem);

      AFileSl.Clear;
      ACatSl.Clear;
      AInterSl.Clear;
      ADiffSl.Clear;

      AFileXmp.Load(AImg.XMPFileName, AImg.ExifFileName, False);
      AFileXmp.DumpToStrings(AFileSl);

      PublicCatalog.LoadXMPForImage(AImg, ACatXmp, PublicOptions.CachedXMP);
      ACatXmp.DumpToStrings(ACatSl);

      ATempXmp.AppendXMP(AFileXmp);
      ATempXmp.IntersectWithXMP(ACatXmp);
      ATempXmp.DumpToStrings(AInterSl);

      ACatXmp.DifferenceWithXMP(AFileXmp);
      ACatXmp.DumpToStrings(ADiffSl);
    finally
      ATempXmp.Free;
      AFileXmp.Free;
      ACatXmp.Free;
    end;

    AGrid.Cells[0, row] := 'FileName';
    AGrid.Cells[1, row] := WideExtractFileName(AImg.FileName);
    AGrid.Cells[2, row] := WideExtractFileName(ACatItem.FileName);

    Inc(row);
    AGrid.Cells[0, row] := 'FilePath';
    AGrid.Cells[1, row] := AImg.FileName;
    AGrid.Cells[2, row] := ACatItem.FileName;

    Inc(row);
    AGrid.Cells[0, row] := 'FileSize';
    ASize := SizeOfFile(AImg.FileName);
    AGrid.Cells[1, row] := IntToStr(ASize) + ' bytes (' + SizeString(ASize) + ')';
    ASize := ACatItem.FileSize;
    AGrid.Cells[2, row] := IntToStr(ASize) + ' bytes (' + SizeString(ASize) + ')';

    Inc(row);
    AGrid.Cells[0, row] := 'FileStamp';
    AGrid.Cells[1, row] := DateTimeToStr(FileStamp(AImg.FileName));
    AGrid.Cells[2, row] := DateTimeToStr(ACatItem.FileStamp);

    Inc(row);
    AGrid.Cells[0, row] := '';
    AGrid.Cells[1, row] := '';
    AGrid.Cells[2, row] := '';

    if show = cShowInter then
      ALeadSl := AInterSl
    else if show = cShowDiff then
      ALeadSl := ADiffSl
    else
      ALeadSl := AFileSl;

    for i := 0 to ALeadSl.Count - 1 do
    begin
      if ALeadSl = AFileSl then
        AIdx := i
      else
        AIdx := AFileSl.IndexOfName(ALeadSl.Names[i]);

      if AIdx <> -1 then
      begin
        Inc(row);

        AGrid.Cells[0, row] := AFileSl.Names[AIdx];
        AGrid.Cells[1, row] := AFileSl.ValueFromIndex[AIdx];

        AIdx := ACatSl.IndexOfName(ALeadSl.Names[i]);
        if AIdx <> -1 then
          AGrid.Cells[2, row] := ACatSl.ValueFromIndex[AIdx]
        else
          AGrid.Cells[2, row] := '';
      end;
    end;

    if ALeadSl = AFileSl then
    begin
      for i := 0 to ACatSl.Count - 1 do
      begin
        AIdx := ALeadSl.IndexOfName(ACatSl.Names[i]);

        if AIdx = -1 then
        begin
          Inc(row);
          AGrid.Cells[0, row] := ACatSl.Names[i];
          AGrid.Cells[1, row] := '';
          AGrid.Cells[2, row] := ACatSl.ValueFromIndex[i]
        end;
      end;
    end;

    Inc(row);
    AGrid.Cells[0, row] := '';
    AGrid.Cells[1, row] := '';
    AGrid.Cells[2, row] := '';

    ACatItem.Free;
  end;

  procedure DumpGridToStringList(ASl: TTntStringList);
  var
    i: Integer;
    ALine: TTntStringList;
  begin
    // add header
    ALine := TTntStringList.Create;
    ALine.Add('Tag');
    ALine.Add('In File');
    ALine.Add('In Catalog');
    ASl.Add(ALine.CommaText);
    ALine.Free;

    for i := 0 to AGrid.RowCount - 1 do
    begin
      ALine := TTntStringList.Create;
      ALine.Add(AGrid.Cells[0, i]);
      ALine.Add(AGrid.Cells[1, i]);
      ALine.Add(AGrid.Cells[2, i]);
      ASl.Add(ALine.CommaText);
      ALine.Free;
    end;
  end;

  function CheckVersion41: Boolean;
  var
    ATokens: TTntStringList;
  begin
    result := False;
    ATokens := TTntStringList.Create;
    Tokenize(xomProductVersion, '.', ATokens);
    if ATokens.Count = 4 then
    begin
      if (IntToStr(ATokens.Strings[0]) > 4) or
         (
           (IntToStr(ATokens.Strings[0]) = 4) and
           (IntToStr(ATokens.Strings[1]) >= 1)
         )
      then
        result := True;
    end;
    ATokens.Free;
  end;

  procedure CheckButtonShowState;
  begin
    showAllButton.Fill.Style   := iif(show = cShowAll, vgBrushSolid, vgBrushNone);
    showInterButton.Fill.Style := iif(show = cShowInter, vgBrushSolid, vgBrushNone);
    showDiffButton.Fill.Style  := iif(show = cShowDiff, vgBrushSolid, vgBrushNone);
  end;

  procedure ActivateTab(ATabIndex: Integer);
  begin
    TvxRectangle(ActiveTabButton).Fill.Style := vgBrushNone;

    // dump the first selected file to the grid
    AGrid.RowCount := 0;
    row := 0;
    activetab := ATabIndex;
    DumpXmp(ActiveTabImage, AGrid, ActiveTabFileData, ActiveTabCatData, ActiveTabInterData, ActiveTabDiffData);

    TvxRectangle(ActiveTabButton).Fill.Style := vgBrushSolid;
  end;

  procedure _SaveToFileClick(Sender: TObject);
  var
    ASl: TTntStringList;
  begin
    //SaveDialog.FileName := WideChangeFileExt(TImageItem(ActiveTabImage).FileNameOnly, '.txt');
    SaveDialog.FileName := TImageItem(ActiveTabImage).FileNameOnly + '.txt';
    if SaveDialog.Execute then
    begin
      ASl := TTntStringList.Create;
      try
        DumpGridToStringList(ASl);
        ASl.SaveToFile(SaveDialog.FileName);
      finally
        ASl.Free;
      end;

      Say('Saved as ' + SaveDialog.FileName);
    end;
  end;

  procedure _ShowAllClick(Sender: TObject);
  begin
    show := cShowAll;
    ActivateTab(activetab);
    CheckButtonShowState;
  end;

  procedure _ShowInterClick(Sender: TObject);
  begin
    show := cShowInter;
    ActivateTab(activetab);
    CheckButtonShowState;
  end;

  procedure _ShowDiffClick(Sender: TObject);
  begin
    show := cShowDiff;
    ActivateTab(activetab);
    CheckButtonShowState;
  end;

  procedure _TabButtonClick(Sender: TObject);
  var
    ATabButton: TvxRectangle;
  begin
    ATabButton := TvxRectangle(Sender);

    ActivateTab(ATabButton.Tag);
  end;

  function CreateTab(AImg: TImageItem; ATabBar: TvxScrollBox): TvxRectangle;
  var
    ATabButton: TvxRectangle;
    ALabel: TvxLabel;
  begin
    ATabButton := TvxRectangle.Create(ATabBar);
    ATabButton.Align := vaLeft;
    ATabButton.Padding.Rect := vgRect(0, 5, 0, 5);
    ALabel := TvxLabel.Create(ATabButton);
    ALabel.Align := vaClient;
    ALabel.Text := AImg.FileNameOnly;
    ATabButton.AddObject(ALabel);

    ATabButton.xRadius := 5;
    ATabButton.yRadius := ATabButton.xRadius;
    ATabButton.Corners := [vgCornerTopLeft, vgCornerTopRight];

    ATabButton.Fill.Color := '#80' + MidStr(GUIDesign.HighlightFill.Color, 4, Length(GUIDesign.HighlightFill.Color));
    ATabButton.Fill.Style := vgBrushNone;
    ATabButton.Stroke.Color := GUIDesign.DarkFill.Color;
    ATabButton.Stroke.Style := vgBrushSolid;

    ATabButton.Width := vgTextWidth(ALabel.Text, ALabel.Font, vgNormal) + 16;

    ATabButton.OnClick := '_TabButtonClick';

    ATabButton.TagString := PtrIntToStr(TTntStringList.Create) + ',' +
                            PtrIntToStr(TTntStringList.Create) + ',' +
                            PtrIntToStr(TTntStringList.Create) + ',' +
                            PtrIntToStr(TTntStringList.Create) + ',' +
                            '';
    ATabButton.TagObject := AImg;
    ATabButton.Tag := tabcount;
    Inc(tabcount);

    ATabBar.AddObject(ATabButton);

    result := ATabButton;
  end;

  function CreateOptionButton(AParent: TvxObject; AText: WideString; AOnClick: WideString): TvxVisualObject;
  var
    ALabel: TvxLabel;
  begin
    result := TvxRectangle.Create(AParent)
    result.Align := vaLeft;
    result.Width := 110;
    result.Padding.Rect := vxRect(1, 8, 5, 8);
    result.OnClick := AOnClick;
    result.xRadius := 5;
    result.yRadius := result.xRadius;
    AParent.AddObject(result);

    result.Fill.Color := '#80' + MidStr(GUIDesign.HighlightFill.Color, 4, Length(GUIDesign.HighlightFill.Color));
    result.Fill.Style := vgBrushNone;
    result.Stroke.Color := '#40' + MidStr(GUIDesign.MidDarkFontFill.Color, 4, Length(GUIDesign.MidDarkFontFill.Color));
    result.Stroke.Style := vgBrushSolid;


    ALabel := TvxLabel.Create(result);
    ALabel.Align := vaClient;
    ALabel.text := AText;
    result.AddObject(ALabel);
  end;


// MAIN
var
  i: Integer;
  AForm: TvxSceneForm;
  ACol: TvxStringColumn;
  AButton: TvxButton;

  ATabBar: TvxScrollBox;
  ATab: TvxRectangle;
begin
  if not CheckVersion41 then
  begin
    Say('This script requires Photo Supreme 4.1 or higher');
    exit;
  end;

  if Selected.Count = 0 then
  begin
    Say ('No image selected');
    exit;
  end;

  AForm := TvxSceneForm.Create(nil);
  if Selected.Count = 1 then
    AForm.Caption := UTF8Encode(WideFormat('Compare XMP [%s]', [WideExtractFileName(Selected.Items[0].ExifFileName)]))
  else
    AForm.Caption := 'Compare XMP: ' + IntToStr(Selected.Count) + ' images';
  AForm.Width := 900 * AForm.DPIFactor;
  AForm.Height := Trunc(Screen.Height * 0.6);
  AForm.BorderStyle := bsSizeable;

  ATabBar := TvxScrollBox.Create(AForm.Root);
  ATabBar.Height := 40;
  ATabBar.Align := vaTop;
  ATabBar.UseSmallScrollBars := True;
  AForm.Root.AddObject(ATabBar);

  AGrid := TvxStringGrid.Create(AForm.Root);
  AForm.Root.AddObject(AGrid);
  AGrid.Align := vaClient;
  AGrid.RowCount := 0;

  ACol := TvxStringColumn.Create(AGrid);
  ACol.Header := 'Tag';
  ACol.Width := 200;
  AGrid.AddObject(ACol);
  ACol := TvxStringColumn.Create(AGrid);
  ACol.Header := 'In File';
  ACol.Width := 300;
  AGrid.AddObject(ACol);
  ACol := TvxStringColumn.Create(AGrid);
  ACol.Header := 'In Catalog';
  ACol.Width := 300;
  AGrid.AddObject(ACol);

  ATabList := TStringList.Create;

  for i := 0 to Selected.Count - 1 do
  begin
    ATab := CreateTab(Selected.Items[i], ATabBar);
    ATabList.AddObject(Selected.Items[i].GUID, ATab);
  end;

  tabcount := 0;
  activetab := 0;
  show := cShowAll;

  ActivateTab(activetab);

  AForm.PrepareModal([mbOK]);

  showAllButton   := CreateOptionButton(AForm.ModalPanel, 'Show All', '_ShowAllClick');
  showInterButton := CreateOptionButton(AForm.ModalPanel, 'Show Identical', '_ShowInterClick');
  showDiffButton  := CreateOptionButton(AForm.ModalPanel, 'Show Differences', '_ShowDiffClick');
  CheckButtonShowState;

  AButton := TvxButton.Create(AForm.ModalPanel)
  AButton.Text := 'Save as text file';
  AButton.Align := vaLeft;
  AButton.Width := 110;
  AButton.Padding.Rect := vxRect(25, 8, 5, 8);
  AButton.OnClick := '_SaveToFileClick';
  AForm.ModalPanel.AddObject(AButton);

  AForm.ShowModal;

  for i := 0 to ATabList.Count - 1 do
  begin
    TTntStringList(TabFileDataAtIndex(i)).Free;
    TTntStringList(TabCatDataAtIndex(i)).Free;
    TTntStringList(TabInterDataAtIndex(i)).Free;
    TTntStringList(TabDiffDataAtIndex(i)).Free;
  end;

  ATabList.Free;

  AForm.Free;
end;