fpexif: Add support of XMP meta data (reading).

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@8201 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2022-03-07 12:10:52 +00:00
parent a788601c66
commit f39aad84fe
9 changed files with 831 additions and 34 deletions

View File

@ -25,13 +25,19 @@
<Mode0 Name="default"/> <Mode0 Name="default"/>
</Modes> </Modes>
</RunParams> </RunParams>
<RequiredPackages Count="2"> <RequiredPackages Count="4">
<Item1> <Item1>
<PackageName Value="fpexif_pkg"/> <PackageName Value="SynEditDsgn"/>
</Item1> </Item1>
<Item2> <Item2>
<PackageName Value="LCL"/> <PackageName Value="SynEdit"/>
</Item2> </Item2>
<Item3>
<PackageName Value="fpexif_pkg"/>
</Item3>
<Item4>
<PackageName Value="LCL"/>
</Item4>
</RequiredPackages> </RequiredPackages>
<Units Count="3"> <Units Count="3">
<Unit0> <Unit0>
@ -70,6 +76,7 @@
</CodeGeneration> </CodeGeneration>
<Linking> <Linking>
<Debugging> <Debugging>
<DebugInfoType Value="dsDwarf2Set"/>
<UseExternalDbgSyms Value="True"/> <UseExternalDbgSyms Value="True"/>
</Debugging> </Debugging>
<Options> <Options>

View File

@ -147,7 +147,7 @@ object MainForm: TMainForm
TabOrder = 1 TabOrder = 1
OnChange = PageControl1Change OnChange = PageControl1Change
object PgMetadata: TTabSheet object PgMetadata: TTabSheet
Caption = 'Meta data' Caption = 'EXIF, IPTC'
ClientHeight = 511 ClientHeight = 511
ClientWidth = 639 ClientWidth = 639
object TagListView: TListView object TagListView: TListView
@ -243,6 +243,539 @@ object MainForm: TMainForm
end end
end end
end end
object PgXMP: TTabSheet
Caption = 'XMP'
ClientHeight = 511
ClientWidth = 639
inline SynEdit: TSynEdit
Left = 0
Height = 231
Top = 280
Width = 639
Align = alClient
Font.Height = -13
Font.Name = 'Courier New'
Font.Pitch = fpFixed
Font.Quality = fqNonAntialiased
ParentColor = False
ParentFont = False
TabOrder = 0
Gutter.Width = 57
Gutter.MouseActions = <>
RightGutter.Width = 0
RightGutter.MouseActions = <>
Highlighter = SynXMLSyn
Keystrokes = <
item
Command = ecUp
ShortCut = 38
end
item
Command = ecSelUp
ShortCut = 8230
end
item
Command = ecScrollUp
ShortCut = 16422
end
item
Command = ecDown
ShortCut = 40
end
item
Command = ecSelDown
ShortCut = 8232
end
item
Command = ecScrollDown
ShortCut = 16424
end
item
Command = ecLeft
ShortCut = 37
end
item
Command = ecSelLeft
ShortCut = 8229
end
item
Command = ecWordLeft
ShortCut = 16421
end
item
Command = ecSelWordLeft
ShortCut = 24613
end
item
Command = ecRight
ShortCut = 39
end
item
Command = ecSelRight
ShortCut = 8231
end
item
Command = ecWordRight
ShortCut = 16423
end
item
Command = ecSelWordRight
ShortCut = 24615
end
item
Command = ecPageDown
ShortCut = 34
end
item
Command = ecSelPageDown
ShortCut = 8226
end
item
Command = ecPageBottom
ShortCut = 16418
end
item
Command = ecSelPageBottom
ShortCut = 24610
end
item
Command = ecPageUp
ShortCut = 33
end
item
Command = ecSelPageUp
ShortCut = 8225
end
item
Command = ecPageTop
ShortCut = 16417
end
item
Command = ecSelPageTop
ShortCut = 24609
end
item
Command = ecLineStart
ShortCut = 36
end
item
Command = ecSelLineStart
ShortCut = 8228
end
item
Command = ecEditorTop
ShortCut = 16420
end
item
Command = ecSelEditorTop
ShortCut = 24612
end
item
Command = ecLineEnd
ShortCut = 35
end
item
Command = ecSelLineEnd
ShortCut = 8227
end
item
Command = ecEditorBottom
ShortCut = 16419
end
item
Command = ecSelEditorBottom
ShortCut = 24611
end
item
Command = ecToggleMode
ShortCut = 45
end
item
Command = ecCopy
ShortCut = 16429
end
item
Command = ecPaste
ShortCut = 8237
end
item
Command = ecDeleteChar
ShortCut = 46
end
item
Command = ecCut
ShortCut = 8238
end
item
Command = ecDeleteLastChar
ShortCut = 8
end
item
Command = ecDeleteLastChar
ShortCut = 8200
end
item
Command = ecDeleteLastWord
ShortCut = 16392
end
item
Command = ecUndo
ShortCut = 32776
end
item
Command = ecRedo
ShortCut = 40968
end
item
Command = ecLineBreak
ShortCut = 13
end
item
Command = ecSelectAll
ShortCut = 16449
end
item
Command = ecCopy
ShortCut = 16451
end
item
Command = ecBlockIndent
ShortCut = 24649
end
item
Command = ecLineBreak
ShortCut = 16461
end
item
Command = ecInsertLine
ShortCut = 16462
end
item
Command = ecDeleteWord
ShortCut = 16468
end
item
Command = ecBlockUnindent
ShortCut = 24661
end
item
Command = ecPaste
ShortCut = 16470
end
item
Command = ecCut
ShortCut = 16472
end
item
Command = ecDeleteLine
ShortCut = 16473
end
item
Command = ecDeleteEOL
ShortCut = 24665
end
item
Command = ecUndo
ShortCut = 16474
end
item
Command = ecRedo
ShortCut = 24666
end
item
Command = ecGotoMarker0
ShortCut = 16432
end
item
Command = ecGotoMarker1
ShortCut = 16433
end
item
Command = ecGotoMarker2
ShortCut = 16434
end
item
Command = ecGotoMarker3
ShortCut = 16435
end
item
Command = ecGotoMarker4
ShortCut = 16436
end
item
Command = ecGotoMarker5
ShortCut = 16437
end
item
Command = ecGotoMarker6
ShortCut = 16438
end
item
Command = ecGotoMarker7
ShortCut = 16439
end
item
Command = ecGotoMarker8
ShortCut = 16440
end
item
Command = ecGotoMarker9
ShortCut = 16441
end
item
Command = ecSetMarker0
ShortCut = 24624
end
item
Command = ecSetMarker1
ShortCut = 24625
end
item
Command = ecSetMarker2
ShortCut = 24626
end
item
Command = ecSetMarker3
ShortCut = 24627
end
item
Command = ecSetMarker4
ShortCut = 24628
end
item
Command = ecSetMarker5
ShortCut = 24629
end
item
Command = ecSetMarker6
ShortCut = 24630
end
item
Command = ecSetMarker7
ShortCut = 24631
end
item
Command = ecSetMarker8
ShortCut = 24632
end
item
Command = ecSetMarker9
ShortCut = 24633
end
item
Command = EcFoldLevel1
ShortCut = 41009
end
item
Command = EcFoldLevel2
ShortCut = 41010
end
item
Command = EcFoldLevel3
ShortCut = 41011
end
item
Command = EcFoldLevel4
ShortCut = 41012
end
item
Command = EcFoldLevel5
ShortCut = 41013
end
item
Command = EcFoldLevel6
ShortCut = 41014
end
item
Command = EcFoldLevel7
ShortCut = 41015
end
item
Command = EcFoldLevel8
ShortCut = 41016
end
item
Command = EcFoldLevel9
ShortCut = 41017
end
item
Command = EcFoldLevel0
ShortCut = 41008
end
item
Command = EcFoldCurrent
ShortCut = 41005
end
item
Command = EcUnFoldCurrent
ShortCut = 41003
end
item
Command = EcToggleMarkupWord
ShortCut = 32845
end
item
Command = ecNormalSelect
ShortCut = 24654
end
item
Command = ecColumnSelect
ShortCut = 24643
end
item
Command = ecLineSelect
ShortCut = 24652
end
item
Command = ecTab
ShortCut = 9
end
item
Command = ecShiftTab
ShortCut = 8201
end
item
Command = ecMatchBracket
ShortCut = 24642
end
item
Command = ecColSelUp
ShortCut = 40998
end
item
Command = ecColSelDown
ShortCut = 41000
end
item
Command = ecColSelLeft
ShortCut = 40997
end
item
Command = ecColSelRight
ShortCut = 40999
end
item
Command = ecColSelPageDown
ShortCut = 40994
end
item
Command = ecColSelPageBottom
ShortCut = 57378
end
item
Command = ecColSelPageUp
ShortCut = 40993
end
item
Command = ecColSelPageTop
ShortCut = 57377
end
item
Command = ecColSelLineStart
ShortCut = 40996
end
item
Command = ecColSelLineEnd
ShortCut = 40995
end
item
Command = ecColSelEditorTop
ShortCut = 57380
end
item
Command = ecColSelEditorBottom
ShortCut = 57379
end>
MouseActions = <>
MouseTextActions = <>
MouseSelActions = <>
Lines.Strings = (
'SynEdit'
)
VisibleSpecialChars = [vscSpace, vscTabAtLast]
SelectedColor.BackPriority = 50
SelectedColor.ForePriority = 50
SelectedColor.FramePriority = 50
SelectedColor.BoldPriority = 50
SelectedColor.ItalicPriority = 50
SelectedColor.UnderlinePriority = 50
SelectedColor.StrikeOutPriority = 50
BracketHighlightStyle = sbhsBoth
BracketMatchColor.Background = clNone
BracketMatchColor.Foreground = clNone
BracketMatchColor.Style = [fsBold]
FoldedCodeColor.Background = clNone
FoldedCodeColor.Foreground = clGray
FoldedCodeColor.FrameColor = clGray
MouseLinkColor.Background = clNone
MouseLinkColor.Foreground = clBlue
LineHighlightColor.Background = clNone
LineHighlightColor.Foreground = clNone
inline SynLeftGutterPartList1: TSynGutterPartList
object SynGutterMarks1: TSynGutterMarks
Width = 24
MouseActions = <>
end
object SynGutterLineNumber1: TSynGutterLineNumber
Width = 17
MouseActions = <>
MarkupInfo.Background = clBtnFace
MarkupInfo.Foreground = clNone
DigitCount = 2
ShowOnlyLineNumbersMultiplesOf = 1
ZeroStart = False
LeadingZeros = False
end
object SynGutterChanges1: TSynGutterChanges
Width = 4
MouseActions = <>
ModifiedColor = 59900
SavedColor = clGreen
end
object SynGutterSeparator1: TSynGutterSeparator
Width = 2
MouseActions = <>
MarkupInfo.Background = clWhite
MarkupInfo.Foreground = clGray
end
object SynGutterCodeFolding1: TSynGutterCodeFolding
MouseActions = <>
MarkupInfo.Background = clNone
MarkupInfo.Foreground = clGray
MouseActionsExpanded = <>
MouseActionsCollapsed = <>
end
end
end
object XMPListView: TListView
Left = 0
Height = 275
Top = 0
Width = 639
Align = alTop
AutoWidthLastColumn = True
Columns = <
item
Caption = 'Description'
Width = 200
end
item
Caption = 'Value'
Width = 435
end>
TabOrder = 1
ViewStyle = vsReport
end
object Splitter4: TSplitter
Cursor = crVSplit
Left = 0
Height = 5
Top = 275
Width = 639
Align = alTop
ResizeAnchor = akTop
end
end
object PgImage: TTabSheet object PgImage: TTabSheet
Caption = 'Image' Caption = 'Image'
ClientHeight = 511 ClientHeight = 511
@ -385,4 +918,11 @@ object MainForm: TMainForm
1FDF9F1F363F97D9C744ECB29398DFDF7A8BC172 1FDF9F1F363F97D9C744ECB29398DFDF7A8BC172
} }
end end
object SynXMLSyn: TSynXMLSyn
DefaultFilter = 'XML Document (*.xml,*.xsd,*.xsl,*.xslt,*.dtd)|*.xml;*.xsd;*.xsl;*.xslt;*.dtd'
Enabled = False
WantBracesParsed = False
Left = 480
Top = 145
end
end end

View File

@ -5,9 +5,9 @@ unit mdvMain;
interface interface
uses uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ShellCtrls, Classes, SysUtils, FileUtil, SynEdit, SynHighlighterXML, Forms, Controls,
ExtCtrls, ComCtrls, StdCtrls, Graphics, Dialogs, ShellCtrls, ExtCtrls, ComCtrls, StdCtrls, fpeGlobal,
fpeGlobal, fpeMetadata; fpeMetadata;
type type
@ -23,6 +23,7 @@ type
Image: TImage; Image: TImage;
Label1: TLabel; Label1: TLabel;
LblChangeDate: TLabel; LblChangeDate: TLabel;
XMPListView: TListView;
Messages: TMemo; Messages: TMemo;
PageControl1: TPageControl; PageControl1: TPageControl;
Panel1: TPanel; Panel1: TPanel;
@ -33,9 +34,13 @@ type
PreviewImage: TImage; PreviewImage: TImage;
ImageList: TImageList; ImageList: TImageList;
Splitter3: TSplitter; Splitter3: TSplitter;
Splitter4: TSplitter;
StatusBar1: TStatusBar; StatusBar1: TStatusBar;
PgMetadata: TTabSheet; PgMetadata: TTabSheet;
PgImage: TTabSheet; PgImage: TTabSheet;
PgXMP: TTabSheet;
SynEdit: TSynEdit;
SynXMLSyn: TSynXMLSyn;
TagListView: TListView; TagListView: TListView;
ShellPanel: TPanel; ShellPanel: TPanel;
ShellListView: TShellListView; ShellListView: TShellListView;
@ -182,7 +187,8 @@ begin
ShellListView.SmallImages := ImageList; ShellListView.SmallImages := ImageList;
ShellTreeView.Images := ImageList; ShellTreeView.Images := ImageList;
{$ENDIF} {$ENDIF}
//ShellListView.Parent.DoubleBuffered := true;
// XMPListView.ControlStyle:= XMPListView.Controlstyle + [csOpaque];
end; end;
procedure TMainForm.BtnChangeDateClick(Sender: TObject); procedure TMainForm.BtnChangeDateClick(Sender: TObject);
@ -248,6 +254,7 @@ end;
procedure TMainForm.LoadFile(const AFileName: String); procedure TMainForm.LoadFile(const AFileName: String);
var var
lTag: TTag; lTag: TTag;
xmpTag: String;
item: TListItem; item: TListItem;
i: Integer; i: Integer;
ms: TMemoryStream; ms: TMemoryStream;
@ -315,6 +322,21 @@ begin
end else end else
DateTimePanel.Hide; DateTimePanel.Hide;
if FImgInfo.HasXMP then begin
ms := TMemoryStream.Create;
FImgInfo.XMPData.SaveToStream(ms);
ms.Position := 0;
SynEdit.Lines.LoadFromStream(ms);
XMPListView.Clear;
for i := 0 to FImgInfo.XMPData.TagCount-1 do begin
xmpTag := FImgInfo.XMPData.TagByIndex[i];
item := XMPListView.Items.Add;
item.Caption := Copy(xmptag, 1, pos('=', xmptag)-1);
item.SubItems.Add(copy(xmptag, pos('=', xmptag)+1));
end;
end;
if FImgInfo.HasIptc then begin if FImgInfo.HasIptc then begin
for i := 0 to FImgInfo.IptcData.TagCount-1 do begin for i := 0 to FImgInfo.IptcData.TagCount-1 do begin
lTag := FImgInfo.IptcData.TagByIndex[i]; lTag := FImgInfo.IptcData.TagByIndex[i];

View File

@ -63,7 +63,7 @@ type
procedure ReadIFD(AStream: TStream; AParent: TTagID); override; procedure ReadIFD(AStream: TStream; AParent: TTagID); override;
public public
constructor Create(AImgInfo: TImgInfo); override; constructor Create(AImgInfo: TImgInfo); override;
function ReadExifHeader(AStream: TStream): Boolean; class function ReadExifHeader(AStream: TStream): Boolean;
procedure ReadFromStream(AStream: TStream; AImgFormat: TImgFormat); override; procedure ReadFromStream(AStream: TStream; AImgFormat: TImgFormat); override;
function ReadTiffHeader(AStream: TStream; out ABigEndian: Boolean): Boolean; function ReadTiffHeader(AStream: TStream; out ABigEndian: Boolean): Boolean;
property BigEndian: Boolean read FBigEndian; property BigEndian: Boolean read FBigEndian;
@ -485,14 +485,19 @@ end;
// Reads the header of the APP1 jpeg segment ("EXIF segment") // Reads the header of the APP1 jpeg segment ("EXIF segment")
// Note that the segment marker and the segment size already have been read. // Note that the segment marker and the segment size already have been read.
// The function returns FALSE if the header is not valid. // The function returns FALSE if the header is not valid.
// Call ReadFromStream immediately afterwards // In this case, the stream position is reset to where it started.
// Otherwise ReadFromStream can be called immediately afterwards.
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
function TExifReader.ReadExifHeader(AStream: TStream): Boolean; class function TExifReader.ReadExifHeader(AStream: TStream): Boolean;
var var
hdr: array[0..5] of ansichar; hdr: array[0..5] of ansichar;
p: Int64;
begin begin
p := AStream.Position;
AStream.Read({%H-}hdr[0], SizeOf(hdr)); AStream.Read({%H-}hdr[0], SizeOf(hdr));
Result := CompareMem(@hdr[0], @EXIF_SIGNATURE[0], SizeOf(hdr)); Result := CompareMem(@hdr[0], @EXIF_SIGNATURE[0], SizeOf(hdr));
if not Result then
AStream.Position := p;
end; end;
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------

View File

@ -16,13 +16,14 @@ type
TMetaDataKind = ( TMetaDataKind = (
mdkExif, // Complete Exif (incl MakerNotes) mdkExif, // Complete Exif (incl MakerNotes)
mdkExifNoMakerNotes, // Exif without MakerNotes (instead of mdkExif) mdkExifNoMakerNotes, // Exif without MakerNotes (instead of mdkExif)
mdkXMP, // XMP
mdkIPTC, // IPTC mdkIPTC, // IPTC
mdkComment // Comment segment mdkComment // Comment segment
); );
TMetaDataKinds = set of TMetaDataKind; TMetaDataKinds = set of TMetaDataKind;
const const
mdkAll = [mdkExif, mdkIPTC, mdkComment]; mdkAll = [mdkExif, mdkXMP, mdkIPTC, mdkComment];
type type
{$IFNDEF FPC} {$IFNDEF FPC}

View File

@ -14,7 +14,7 @@ uses
LazUTF8, LazUTF8,
{$ENDIF} {$ENDIF}
fpeGlobal, fpeGlobal,
fpeExifData, fpeIptcData; fpeExifData, fpeIptcData, fpeXmpData;
type type
TImgInfo = class; TImgInfo = class;
@ -64,6 +64,7 @@ type
private private
FExifData: TExifData; FExifData: TExifData;
FIptcData: TIptcData; FIptcData: TIptcData;
FXmpData: TXmpData;
function GetComment: String; function GetComment: String;
function GetWarnings: String; function GetWarnings: String;
procedure SetComment(const AValue: String); procedure SetComment(const AValue: String);
@ -86,12 +87,14 @@ type
function CreateExifData(ABigEndian: Boolean = false): TExifData; function CreateExifData(ABigEndian: Boolean = false): TExifData;
function CreateIptcData: TIptcData; function CreateIptcData: TIptcData;
function CreateXmpData: TXmpData;
function HasComment: Boolean; function HasComment: Boolean;
function HasExif: Boolean; function HasExif: Boolean;
function HasIptc: Boolean; function HasIptc: Boolean;
function HasThumbnail: Boolean; function HasThumbnail: Boolean;
function HasWarnings: boolean; function HasWarnings: boolean;
function HasXMP: Boolean;
{ Comment stored in the Jpeg COM segment } { Comment stored in the Jpeg COM segment }
property Comment: String read GetComment write SetComment; property Comment: String read GetComment write SetComment;
@ -115,7 +118,8 @@ type
property WriteJFIFandEXIF: Boolean read FWriteJFIFandEXIF write FWriteJFIFandEXIF; property WriteJFIFandEXIF: Boolean read FWriteJFIFandEXIF write FWriteJFIFandEXIF;
property ExifData: TExifData read FExifData; property ExifData: TExifData read FExifData;
property IptcData: TIptcData read FIptcData; // to do: rename to IptcData property IptcData: TIptcData read FIptcData;
property XmpData: TXmpData read FXmpData;
end; end;
@ -260,6 +264,7 @@ begin
FWarnings.Free; FWarnings.Free;
FExifData.Free; FExifData.Free;
FIptcData.Free; FIptcData.Free;
FXmpData.Free;
inherited; inherited;
end; end;
@ -279,6 +284,14 @@ begin
Result := FIptcData; Result := FIptcData;
end; end;
function TImgInfo.CreateXmpData: TXmpData;
begin
FWarnings.Clear;
FXmpData.Free;
FXmpData := TXmpData.Create;
Result := FXmpData;
end;
procedure TImgInfo.Error(const AMsg: String); procedure TImgInfo.Error(const AMsg: String);
begin begin
raise EFpExif.Create(AMsg); raise EFpExif.Create(AMsg);
@ -352,6 +365,11 @@ begin
Result := FWarnings.Count > 0; Result := FWarnings.Count > 0;
end; end;
function TImgInfo.HasXMP: Boolean;
begin
Result := FXmpData <> nil;
end;
procedure TImgInfo.LoadFromFile(const AFileName: String); procedure TImgInfo.LoadFromFile(const AFileName: String);
var var
stream: TStream; stream: TStream;
@ -527,11 +545,12 @@ begin
p := AStream.Position; p := AStream.Position;
case marker of case marker of
M_EXIF: M_EXIF:
if FMetaDataKinds * [mdkExif, mdkExifNoMakerNotes] <> [] then begin if FMetaDataKinds * [mdkExif, mdkExifNoMakerNotes, mdkXMP] <> [] then begin
if TExifReader.ReadExifHeader(AStream) then
begin
reader := TExifReader.Create(self); reader := TExifReader.Create(self);
try try
if TExifReader(reader).ReadExifHeader(AStream) and if TExifReader(reader).ReadTiffHeader(AStream, bigEndian) then
TExifReader(reader).ReadTiffHeader(AStream, bigEndian) then
begin begin
FExifData := CreateExifData(bigEndian); FExifData := CreateExifData(bigEndian);
try try
@ -544,6 +563,17 @@ begin
finally finally
reader.Free; reader.Free;
end; end;
end else
if HasXMPHeader(AStream) then
begin
FXmpData := CreateXMPData;
try
FXmpData.ReadFromStream(AStream, size - Length(XMP_KEY));
except
FreeAndNil(FXmpData);
raise;
end;
end;
end; end;
M_IPTC: M_IPTC:
if (mdkIPTC in FMetadataKinds) then begin if (mdkIPTC in FMetadataKinds) then begin

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<CONFIG> <CONFIG>
<Package Version="4"> <Package Version="5">
<Name Value="fpexif_pkg"/> <Name Value="fpexif_pkg"/>
<Author Value="Werner Pamler"/> <Author Value="Werner Pamler"/>
<CompilerOptions> <CompilerOptions>
@ -12,7 +12,7 @@
<Description Value="Library for displaying and editing of meta data (EXIF, IPTC) in images."/> <Description Value="Library for displaying and editing of meta data (EXIF, IPTC) in images."/>
<License Value="LGPL with linking exception (like Lazarus)"/> <License Value="LGPL with linking exception (like Lazarus)"/>
<Version Minor="1"/> <Version Minor="1"/>
<Files Count="20"> <Files Count="21">
<Item1> <Item1>
<Filename Value="fpeglobal.pas"/> <Filename Value="fpeglobal.pas"/>
<UnitName Value="fpeGlobal"/> <UnitName Value="fpeGlobal"/>
@ -83,17 +83,22 @@
</Item17> </Item17>
<Item18> <Item18>
<Filename Value="fpemakernotesanyo.pas"/> <Filename Value="fpemakernotesanyo.pas"/>
<UnitName Value="fpemakernotesanyo"/> <UnitName Value="fpeMakerNoteSanyo"/>
</Item18> </Item18>
<Item19> <Item19>
<Filename Value="fpemakernotecasio.pas"/> <Filename Value="fpemakernotecasio.pas"/>
<UnitName Value="fpemakernotecasio"/> <UnitName Value="fpeMakerNoteCasio"/>
</Item19> </Item19>
<Item20> <Item20>
<Filename Value="fpemakernotecanon.pas"/> <Filename Value="fpemakernotecanon.pas"/>
<UnitName Value="fpeMakerNoteCanon"/> <UnitName Value="fpeMakerNoteCanon"/>
</Item20> </Item20>
<Item21>
<Filename Value="fpexmpdata.pas"/>
<UnitName Value="fpeXMPData"/>
</Item21>
</Files> </Files>
<CompatibilityMode Value="True"/>
<i18n> <i18n>
<EnableI18N Value="True"/> <EnableI18N Value="True"/>
<OutDir Value="languages"/> <OutDir Value="languages"/>

View File

@ -12,7 +12,7 @@ uses
fpeIptcReadWrite, fpeExifData, fpeIptcData, fpeStrConsts, fpeMakerNote, fpeIptcReadWrite, fpeExifData, fpeIptcData, fpeStrConsts, fpeMakerNote,
fpeMakerNoteNikon, fpeMakerNoteMinolta, fpeMakerNoteOlympus, fpeMakerNoteNikon, fpeMakerNoteMinolta, fpeMakerNoteOlympus,
fpeMakerNoteEpson, fpeMakerNoteFuji, fpeMakerNoteSanyo, fpeMakerNoteCasio, fpeMakerNoteEpson, fpeMakerNoteFuji, fpeMakerNoteSanyo, fpeMakerNoteCasio,
fpeMakerNoteCanon; fpeMakerNoteCanon, fpeXMPData;
implementation implementation

View File

@ -0,0 +1,187 @@
unit fpeXMPData;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, contnrs,
{$IFDEF FPC}
laz2_dom, laz2_xmlread,
{$ENDIF}
fpeGlobal, fpeTags;
const
XMP_BASE_KEY = 'http://ns.adobe.com/xap/1.0/';
XMP_KEY = XMP_BASE_KEY + #0;
type
TXMPData = class
private
FData: String;
FDoc: TXMLDocument;
FTags: TStringList;
// FTags: TTagList;
function GetTagByIndex(AIndex: Integer): String;
function GetTagByName(ATagName: String): String;
function GetTagCount: Integer;
protected
{$IFDEF FPC}
procedure Create_RDFDescription_Tags(ANode: TDOMNode);
procedure CreateTags;
{$ENDIF}
public
constructor Create;
destructor Destroy; override;
procedure ReadFromStream(AStream: TStream; ASize: Integer = -1);
procedure SaveToStream(AStream: TStream);
property TagByIndex[AIndex: Integer]: String read GetTagByIndex;
property TagByName[ATagName: String]: String read GetTagByName;
property TagCount: Integer read GetTagCount;
end;
function HasXMPHeader(AStream: TStream): Boolean;
implementation
function HasXMPHeader(AStream: TStream): Boolean;
var
p: Int64;
hdr: array of ansichar;
begin
p := AStream.Position;
SetLength(hdr, Length(XMP_KEY));
AStream.Read(hdr[0], Length(XMP_KEY));
Result := CompareMem(@hdr[0], @XMP_KEY[1], Length(XMP_KEY));
if not Result then
AStream.Position := p;
end;
{ TXMPData }
constructor TXMPData.Create;
begin
inherited;
FTags := TStringList.Create; //TTagList.Create;
end;
destructor TXMPData.Destroy;
begin
{$IFDEF FPC}
FDoc.Free;
{$ENDIF}
FTags.Free;
inherited;
end;
{$IFDEF FPC}
procedure TXMPData.Create_RDFDescription_Tags(ANode: TDOMNode);
var
node: TDOMNode;
nodeName: String;
i: Integer;
attr: TDOMNode;
tagName, tagValue: String;
lTag: TTag;
begin
while ANode <> nil do begin
nodeName := ANode.NodeName;
for i := 0 to ANode.Attributes.Length-1 do
begin
attr := ANode.Attributes.Item[i];
tagName := attr.NodeName;
tagValue := attr.NodeValue;
FTags.Add(tagName + '=' + tagValue);
end;
if ANode.HasChildNodes then
begin
node := ANode.FirstChild;
while node <> nil do
begin
nodeName := node.NodeName;
tagValue := node.TextContent;
if tagName <> '' then
FTags.Add(nodeName + '=' + tagValue);
node := node.NextSibling;
end;
end;
ANode := ANode.NextSibling;
end;
end;
procedure TXMPData.CreateTags;
var
stream: TStringStream;
node: TDOMNode;
nodeName: String;
begin
FDoc.Free;
stream := TStringStream.Create(FData);
try
ReadXMLFile(FDoc, stream);
finally
stream.Free;
end;
FTags.Clear;
try
node := FDoc.DocumentElement.FindNode('rdf:RDF');
if node = nil then exit;
node := node.FirstChild;
while node <> nil do
begin
nodeName := node.NodeName;
if nodeName = 'rdf:Description' then
Create_RDFDescription_Tags(node);
node := node.NextSibling;
end;
except
FTags.Clear;
FreeAndNil(FDoc);
raise;
end;
end;
{$ENDIF}
function TXMPData.GetTagByIndex(AIndex: Integer): String;
begin
Result := FTags[AIndex];
end;
function TXMPData.GetTagByName(ATagName: String): String;
begin
Result := FTags.Values[ATagName];;
end;
function TXMPData.GetTagCount: Integer;
begin
Result := FTags.Count;
end;
procedure TXMPData.ReadFromStream(AStream: TStream; ASize: Integer = -1);
var
p: Int64;
begin
if ASize = -1 then
ASize := AStream.Size;
SetLength(FData, ASize);
p := AStream.Position;
AStream.Read(FData[1], ASize);
AStream.Position := p;
{$IFDEF FPC}
CreateTags;
{$ENDIF}
end;
procedure TXMPData.SaveToStream(AStream: TStream);
begin
if FData <> '' then
AStream.Write(FData[1], Length(FData));
end;
end.