You've already forked lazarus-ccr
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@7269 8e941d3f-bd1b-0410-a28a-d453659cc2b4
9162 lines
252 KiB
ObjectPascal
9162 lines
252 KiB
ObjectPascal
{-----------------------------------------------------------------------------
|
|
The contents of this file are subject to the Mozilla Public License
|
|
Version 1.1 (the "License"); you may not use this file except in compliance
|
|
with the License. You may obtain a copy of the License at
|
|
http://www.mozilla.org/MPL/MPL-1.1.html
|
|
|
|
Software distributed under the License is distributed on an "AS IS" basis,
|
|
WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for
|
|
the specific language governing rights and limitations under the License.
|
|
|
|
The Original Code is: JvID3v2Base.PAS, released on 2003-04-16.
|
|
|
|
The Initial Developer of the Original Code is Remko Bonte [remkobonte att myrealbox dott com]
|
|
Portions created by Remko Bonte are Copyright (C) 2003 Remko Bonte.
|
|
All Rights Reserved.
|
|
|
|
Contributor(s):
|
|
|
|
You may retrieve the latest version of this file at the Project JEDI's JVCL home page,
|
|
located at http://jvcl.delphi-jedi.org
|
|
|
|
Known Issues:
|
|
* Encryption, compression not supported
|
|
* Footer in v2.4 tags not supported
|
|
* Some tags are not supported, see var DefaultFrameClasses. Values nil in that
|
|
list indicate not supported frames.
|
|
-----------------------------------------------------------------------------}
|
|
// $Id$
|
|
|
|
unit JvId3v2Base;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
(*
|
|
{$IFDEF MSWINDOWS}
|
|
Windows,
|
|
{$ENDIF MSWINDOWS}
|
|
{$IFDEF HAS_UNIT_TYPES}
|
|
Types,
|
|
{$ENDIF HAS_UNIT_TYPES}
|
|
*)
|
|
SysUtils, Classes,
|
|
//JclUnicode,
|
|
//JvComponentBase,
|
|
JvId3v2Types, JvId3v1;
|
|
|
|
const
|
|
{ Only v2.2, v2.3 and v2.4 are supported }
|
|
CSupportedVersions = [ive2_2, ive2_3, ive2_4];
|
|
|
|
type
|
|
EJvID3Error = class(Exception);
|
|
|
|
TJvID3ActivateChangeEvent = procedure(Sender: TObject; Activated: Boolean) of object;
|
|
|
|
TJvID3HandleError = (heAutoCorrect, heRaise, heBoolean);
|
|
|
|
TJvMPEGLayer = (mlNotDefined, mlLayerIII, mlLayerII, mlLayerI);
|
|
TJvMPEGVersion = (mvVersion25, mvReserved, mvVersion2, mvVersion1);
|
|
TJvMPEGChannelMode = (mcStereo, mcJointStereo, mcDualChannel, mcSingleChannel);
|
|
TJvMPEGBit = (mbProtection, mbPrivate, mbCopyrighted, mbOriginal);
|
|
TJvMPEGBits = set of TJvMPEGBit;
|
|
TJvMPEGEmphasis = (meNone, me5015ms, meReserved, meCCITJ17);
|
|
TJvMPEGModeExtension = (meModeExt0, meModeExt1, meModeExt2, meModeExt3);
|
|
|
|
TJvID3ControllerOption = (coAutoCorrect, coRemoveEmptyFrames);
|
|
TJvID3ControllerOptions = set of TJvID3ControllerOption;
|
|
|
|
TJvID3Event = (
|
|
{ Fired when the content of 1 or more frames in a tag changes }
|
|
ideFrameChange,
|
|
{ Fired when the whole tag has changed, because of reading/writing }
|
|
ideID3Change,
|
|
{ Fired when frames are added, deleted etc. }
|
|
ideFrameListChange);
|
|
|
|
TJvID3Controller = class;
|
|
|
|
TJvID3Stream = class(TMemoryStream)
|
|
private
|
|
FReadingFrame: Boolean;
|
|
FWritingFrame: Boolean;
|
|
FSourceEncoding: TJvID3Encoding;
|
|
FDestEncoding: TJvID3Encoding;
|
|
|
|
FAllowedEncodings: TJvID3Encodings;
|
|
|
|
FStartPosition: Integer;
|
|
FCurrentFrameSize: Integer;
|
|
procedure MoveToNextFrame;
|
|
function GetBytesTillEndOfTag: Longint;
|
|
function GetBytesTillEndOfFrame: Longint;
|
|
procedure UpdateDestEncoding;
|
|
procedure SetSourceEncoding(const Value: TJvID3Encoding);
|
|
protected
|
|
{ ISO-8859-1 }
|
|
function ReadStringA(var SA: AnsiString): Longint;
|
|
function ReadUserStringA(var SA1, SA2: AnsiString): Longint;
|
|
function WriteStringA(const SA: AnsiString): Longint;
|
|
function WriteUserStringA(const SA1, SA2: AnsiString): Longint;
|
|
function WriteTerminatorA: Longint;
|
|
{ UTF-16 & UTF-16BE }
|
|
function ReadStringW(var SW: WideString): Longint;
|
|
function ReadUserStringW(var SW1, SW2: WideString): Longint;
|
|
function WriteStringW(const SW: WideString): Longint;
|
|
function WriteUserStringW(const SW1, SW2: WideString): Longint;
|
|
function WriteTerminatorW: Longint;
|
|
{ UTF-8 }
|
|
//function ReadStringUTF8(var SW: WideString): Longint;
|
|
function ReadStringUTF8(var SA: String): LongInt;
|
|
//function ReadUserStringUTF8(var SW1, SW2: WideString): Longint;
|
|
function ReadUserStringUTF8(var SA1, SA2: String): LongInt;
|
|
//function WriteStringUTF8(const SW: WideString): Longint;
|
|
function WriteStringUTF8(const SA: String): LongInt;
|
|
//function WriteUserStringUTF8(const SW1, SW2: WideString): Longint;
|
|
function WriteUserStringUTF8(const SA1, SA2: String): LongInt;
|
|
public
|
|
procedure BeginReadFrame(const AFrameSize: Integer);
|
|
procedure BeginWriteFrame(const AFrameSize: Integer);
|
|
|
|
procedure EndReadFrame;
|
|
procedure EndWriteFrame;
|
|
|
|
{ Inits FAllowedEncodings depending on the wanted version and encoding }
|
|
procedure InitAllowedEncodings(const AVersion: TJvID3Version;
|
|
const AEncoding: TJvID3ForceEncoding);
|
|
|
|
{ Checks whether ACount bytes can be read }
|
|
function CanRead(const ACount: Cardinal): Boolean;
|
|
{ Checks whether we are still in the frame }
|
|
function InFrame(P: Pointer): Boolean;
|
|
|
|
{ Read }
|
|
function ReadDate(var ADate: TDateTime): Longint;
|
|
function ReadLanguage(var Language: AnsiString): Longint;
|
|
function ReadNumber(var AValue: Cardinal): Longint;
|
|
function ReadEnc(var AEncoding: TJvID3Encoding): Longint;
|
|
//function ReadStringEnc(var S: WideString): Longint;
|
|
function ReadStringEnc(var S: String): LongInt;
|
|
// function ReadUserString(var S1, S2: WideString): Longint;
|
|
function ReadUserString(var S1, S2: String): LongInt;
|
|
{ Only for v2.2 }
|
|
function ReadFixedNumber3(var AValue: Cardinal): Longint;
|
|
{ Only for v2.3 }
|
|
function ReadFixedNumber(var AValue: Cardinal): Longint;
|
|
{ Only for v2.4 }
|
|
function ReadSyncSafeInteger(var AInt: Cardinal): Longint; overload;
|
|
function ReadSyncSafeInteger(var AInt: Cardinal; const ASize: Byte): Longint; overload;
|
|
function ReadSyncSafeInteger(var AInt: Int64; const ASize: Byte = 4): Longint; overload;
|
|
|
|
procedure ReadFromStream(AStream: TStream; const ASize: PtrInt);
|
|
|
|
{ Write }
|
|
function WriteDate(const ADate: TDateTime): Longint;
|
|
function WriteLanguage(const Language: AnsiString): Longint;
|
|
function WriteNumber(AValue: Cardinal): Longint;
|
|
function WriteEnc: Longint;
|
|
function WritePadding(const Count: Longint): Longint;
|
|
//function WriteStringEnc(const S: WideString): Longint;
|
|
function WriteStringEnc(const S: String): LongInt;
|
|
function WriteUserString(const S1, S2: String): Longint;
|
|
//function WriteUserString(const S1, S2: WideString): Longint;
|
|
function WriteTerminatorEnc: Longint;
|
|
{ Only for v2.2 }
|
|
function WriteFixedNumber3(AValue: Cardinal): Longint;
|
|
{ Only for v2.3 }
|
|
function WriteFixedNumber(AValue: Cardinal): Longint;
|
|
{ Only for v2.4 }
|
|
function WriteSyncSafeInteger(const AInt: Int64; const ASize: Byte = 4): Longint; overload;
|
|
function WriteSyncSafeInteger(const AInt: Cardinal; const ASize: Byte): Longint; overload;
|
|
function WriteSyncSafeInteger(const AInt: Cardinal): Longint; overload;
|
|
|
|
property BytesTillEndOfFrame: Longint read GetBytesTillEndOfFrame;
|
|
property BytesTillEndOfTag: Longint read GetBytesTillEndOfTag;
|
|
|
|
{ SourceEncoding =
|
|
- When reading: encoding of the ID3 stream
|
|
- When writing: encoding of current frame in the TJvID3Controller }
|
|
property SourceEncoding: TJvID3Encoding read FSourceEncoding write SetSourceEncoding;
|
|
{ DestEncoding =
|
|
- When reading: encoding of current frame in the TJvID3Controller
|
|
- When writing: encoding of the ID3 stream }
|
|
property DestEncoding: TJvID3Encoding read FDestEncoding;
|
|
property AllowedEncodings: TJvID3Encodings read FAllowedEncodings;
|
|
end;
|
|
|
|
TJvID3Frame = class;
|
|
TJvID3Frames = class;
|
|
|
|
TJvID3FrameClass = class of TJvID3Frame;
|
|
|
|
{ Base component for TJvID3Header & TJvID3ExtendedHeader }
|
|
TJvID3Base = class(TPersistent)
|
|
private
|
|
FController: TJvID3Controller;
|
|
function GetStream: TJvID3Stream;
|
|
protected
|
|
procedure Read; virtual; abstract;
|
|
procedure Write; virtual; abstract;
|
|
procedure Reset; virtual; abstract;
|
|
|
|
property Stream: TJvID3Stream read GetStream;
|
|
public
|
|
constructor Create(AController: TJvID3Controller); virtual;
|
|
procedure AfterConstruction; override;
|
|
procedure ChangeToVersion(const ANewVersion: TJvID3Version); virtual; abstract;
|
|
procedure Assign(Source: TPersistent); override;
|
|
property Controller: TJvID3Controller read FController;
|
|
end;
|
|
|
|
TJvID3Header = class(TJvID3Base)
|
|
private
|
|
FFlags: TJvID3HeaderFlags;
|
|
FHasTag: Boolean;
|
|
FMajorVersion: Byte;
|
|
FRevisionNumber: Byte;
|
|
FSize: Cardinal;
|
|
procedure SetFlags(const Value: TJvID3HeaderFlags);
|
|
protected
|
|
procedure Read; override;
|
|
procedure Write; override;
|
|
procedure Reset; override;
|
|
public
|
|
procedure Assign(Source: TPersistent); override;
|
|
procedure ChangeToVersion(const ANewVersion: TJvID3Version); override;
|
|
published
|
|
property MajorVersion: Byte read FMajorVersion;
|
|
property RevisionNumber: Byte read FRevisionNumber;
|
|
property HasTag: Boolean read FHasTag;
|
|
property Flags: TJvID3HeaderFlags read FFlags write SetFlags;
|
|
property Size: Cardinal read FSize;
|
|
end;
|
|
|
|
TJvID3ExtendedHeader = class(TJvID3Base)
|
|
private
|
|
FFlags: TJvID3HeaderExtendedFlags;
|
|
FRestrictions: TJvID3Restrictions;
|
|
FSizeOfPadding: Cardinal;
|
|
FTotalFrameCRC: Cardinal;
|
|
function GetSize: Cardinal;
|
|
function GetSizeForVersion(const AVersion: TJvID3Version): Cardinal;
|
|
procedure SetFlags(const Value: TJvID3HeaderExtendedFlags);
|
|
protected
|
|
procedure Read; override;
|
|
procedure Write; override;
|
|
procedure Reset; override;
|
|
public
|
|
procedure Assign(Source: TPersistent); override;
|
|
procedure ChangeToVersion(const ANewVersion: TJvID3Version); override;
|
|
property Size: Cardinal read GetSize;
|
|
published
|
|
property TotalFrameCRC: Cardinal read FTotalFrameCRC write FTotalFrameCRC;
|
|
property SizeOfPadding: Cardinal read FSizeOfPadding;
|
|
property Flags: TJvID3HeaderExtendedFlags read FFlags write SetFlags;
|
|
end;
|
|
|
|
{ Base class for all frames }
|
|
{ TODO : Change to TPersistent? }
|
|
TJvID3Frame = class(TComponent)
|
|
private
|
|
FController: TJvID3Controller;
|
|
FFrames: TJvID3Frames;
|
|
FFrameID: TJvID3FrameID;
|
|
FFrameIDStr: AnsiString;
|
|
FFrameSize: Cardinal;
|
|
|
|
FDataLengthIndicator: Cardinal; { v2.4 }
|
|
FDecompressedSize: Cardinal;
|
|
FEncoding: TJvID3Encoding;
|
|
FEncryptionID: Byte;
|
|
FFlags: TJvID3FrameHeaderFlags;
|
|
FGroupID: Byte;
|
|
|
|
function GetFrameName: AnsiString;
|
|
function GetFrameIDStrForVersion(const Version: TJvID3Version): AnsiString;
|
|
function GetIndex: Integer;
|
|
function GetStream: TJvID3Stream;
|
|
procedure SetController(const AController: TJvID3Controller);
|
|
procedure SetEncoding(const Value: TJvID3Encoding);
|
|
procedure SetFlags(const Value: TJvID3FrameHeaderFlags);
|
|
procedure SetFrameID(const Value: TJvID3FrameID);
|
|
procedure SetFrameName(NewFrameName: AnsiString);
|
|
procedure SetIndex(const Value: Integer);
|
|
protected
|
|
procedure Read;
|
|
procedure Write;
|
|
|
|
procedure ReadEncoding;
|
|
procedure ReadFrame; virtual; abstract;
|
|
procedure ReadFrameHeader;
|
|
procedure WriteEncoding;
|
|
procedure WriteFrame; virtual; abstract;
|
|
procedure WriteFrameHeader(const AFrameSize: Cardinal);
|
|
procedure WriteID;
|
|
|
|
procedure ChangeToVersion(const {%H-}ANewVersion: TJvID3Version); virtual;
|
|
function SupportsVersion(const AVersion: TJvID3Version): Boolean; virtual;
|
|
|
|
{ Checks whether this frame is empty, thus can be removed }
|
|
function GetIsEmpty: Boolean; virtual;
|
|
|
|
{ Checks whether there are no other frames with the same unique
|
|
identifier as this frame }
|
|
function CheckIsUnique: Boolean;
|
|
|
|
procedure CheckFrameID(const AFrameID: TJvID3FrameID);
|
|
procedure CheckFrameIDStr(const S: AnsiString);
|
|
|
|
{ Checks whether Frame has the same unique identifier as this frame }
|
|
function SameUniqueIDAs(const {%H-}Frame: TJvID3Frame): Boolean; virtual;
|
|
|
|
function MustWriteAsUTF: Boolean; virtual;
|
|
|
|
function GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal; virtual; abstract;
|
|
procedure UpdateFrameSize;
|
|
|
|
procedure DataChanged;
|
|
procedure Changed; virtual;
|
|
|
|
procedure Error(const Msg: string);
|
|
procedure ErrorFmt(const Msg: string; const Args: array of const);
|
|
|
|
property Stream: TJvID3Stream read GetStream;
|
|
public
|
|
constructor Create(AOwner: TComponent; const AFrameID: TJvID3FrameID;
|
|
const AFrameIDStr: AnsiString = ''); reintroduce; virtual;
|
|
destructor Destroy; override;
|
|
|
|
class function CanAddFrame({%H-}AController: TJvID3Controller; {%H-}AFrameID: TJvID3FrameID): Boolean; virtual;
|
|
function CheckFrame(const {%H-}HandleError: TJvID3HandleError): Boolean; virtual;
|
|
|
|
procedure Assign(Source: TPersistent); override;
|
|
procedure Clear; virtual;
|
|
property Controller: TJvID3Controller read FController write SetController stored False;
|
|
property FrameSize: Cardinal read FFrameSize;
|
|
|
|
property IsEmpty: Boolean read GetIsEmpty;
|
|
published
|
|
property Encoding: TJvID3Encoding read FEncoding write SetEncoding;
|
|
property EncryptionID: Byte read FEncryptionID write FEncryptionID;
|
|
property Flags: TJvID3FrameHeaderFlags read FFlags write SetFlags;
|
|
property FrameID: TJvID3FrameID read FFrameID write SetFrameID;
|
|
property FrameName: AnsiString read GetFrameName write SetFrameName;
|
|
property GroupID: Byte read FGroupID write FGroupID;
|
|
property Index: Integer read GetIndex write SetIndex stored False;
|
|
end;
|
|
|
|
TJvID3Frames = class(TJvID3Base)
|
|
private
|
|
FList: TList;
|
|
protected
|
|
procedure Changed;
|
|
|
|
procedure CheckCanAddFrame(FrameID: TJvID3FrameID);
|
|
|
|
procedure Read; override;
|
|
procedure Write; override;
|
|
procedure Reset; override;
|
|
|
|
function GetCount: Integer;
|
|
function GetFrame(Index: Integer): TJvID3Frame;
|
|
procedure SetFrame(Index: Integer; Value: TJvID3Frame);
|
|
procedure SetFrameIndex(Frame: TJvID3Frame; Value: Integer);
|
|
public
|
|
procedure AfterConstruction; override;
|
|
procedure BeforeDestruction; override;
|
|
|
|
procedure Assign(Source: TPersistent); override;
|
|
|
|
procedure Add(Frame: TJvID3Frame);
|
|
procedure Clear;
|
|
function FindFrame(const FrameName: AnsiString): TJvID3Frame; overload;
|
|
function FindFrame(const FrameID: TJvID3FrameID): TJvID3Frame; overload;
|
|
function FrameByName(const FrameName: AnsiString): TJvID3Frame;
|
|
function FrameByID(const FrameID: TJvID3FrameID): TJvID3Frame;
|
|
procedure GetFrameNames(List: TStrings);
|
|
function GetFrameIDs: TJvID3FrameIDs;
|
|
|
|
procedure ChangeToVersion(const ANewVersion: TJvID3Version); override;
|
|
function IndexOf(Frame: TJvID3Frame): Integer;
|
|
function CheckIsUnique(Frame: TJvID3Frame): Boolean;
|
|
function CheckFrames(const HandleError: TJvID3HandleError): Boolean;
|
|
procedure RemoveEmptyFrames;
|
|
procedure Remove(Frame: TJvID3Frame);
|
|
property Count: Integer read GetCount;
|
|
property Frames[Index: Integer]: TJvID3Frame read GetFrame write SetFrame; default;
|
|
end;
|
|
|
|
{ MCDI - fiCDID - Music CD identifier
|
|
There may only be one 'MCDI' frame in each tag. }
|
|
TJvID3BinaryFrame = class(TJvID3Frame)
|
|
private
|
|
FData: PByte;
|
|
FDataSize: Cardinal;
|
|
protected
|
|
procedure ReadData(ASize: Cardinal); virtual;
|
|
procedure WriteData; virtual;
|
|
|
|
procedure ReadFrame; override;
|
|
procedure WriteFrame; override;
|
|
|
|
function GetFrameSize(const {%H-}ToEncoding: TJvID3Encoding): Cardinal; override;
|
|
function GetIsEmpty: Boolean; override;
|
|
|
|
function SameUniqueIDAs(const Frame: TJvID3Frame): Boolean; override;
|
|
public
|
|
class function CanAddFrame(AController: TJvID3Controller; AFrameID: TJvID3FrameID): Boolean; override;
|
|
function CheckFrame(const {%H-}HandleError: TJvID3HandleError): Boolean; override;
|
|
|
|
procedure Assign(Source: TPersistent); override;
|
|
procedure Clear; override;
|
|
|
|
class function Find(AController: TJvID3Controller; const AFrameID: TJvID3FrameID): TJvID3BinaryFrame;
|
|
class function FindOrCreate(AController: TJvID3Controller; const AFrameID: TJvID3FrameID): TJvID3BinaryFrame;
|
|
|
|
procedure AfterConstruction; override;
|
|
procedure BeforeDestruction; override;
|
|
|
|
function SetData(P: Pointer; const Size: Cardinal): Boolean;
|
|
function GetData(P: Pointer; const Size: Cardinal): Boolean;
|
|
|
|
procedure LoadFromFile(const AFileName: string); virtual;
|
|
procedure SaveToFile(const AFileName: string); virtual;
|
|
procedure LoadFromStream(AStream: TStream); virtual;
|
|
procedure SaveToStream(AStream: TStream); virtual;
|
|
|
|
property DataSize: Cardinal read FDataSize;
|
|
end;
|
|
|
|
TJvID3SkipFrame = class(TJvID3BinaryFrame)
|
|
protected
|
|
procedure ChangeToVersion(const ANewVersion: TJvID3Version); override;
|
|
end;
|
|
|
|
{ IPLS - fiInvolvedPeople - Involved people list
|
|
|
|
There may only be one "IPLS" frame in each tag.
|
|
|
|
TIPL - fiInvolvedPeople2 - Involved people list
|
|
TMCL - fiMusicianCreditList - Musician credits list
|
|
|
|
There may only be one text information frame of its kind in an tag }
|
|
TJvID3DoubleListFrame = class(TJvID3Frame)
|
|
private
|
|
FList: TStrings;
|
|
//FList: {$IFDEF COMPILER12_UP}TStrings{$ELSE}TWideStrings{$ENDIF COMPILER12_UP};
|
|
procedure ListChanged(Sender: TObject);
|
|
procedure SetList(Value: TStrings);
|
|
//procedure SetList(Value: {$IFDEF COMPILER12_UP}TStrings{$ELSE}TWideStrings{$ENDIF COMPILER12_UP});
|
|
//function GetNames(const Index: Integer): WideString;
|
|
//function GetValues(const Index: Integer): WideString;
|
|
function GetNames(const AIndex: Integer): String;
|
|
function GetValues(const AIndex: Integer): String;
|
|
protected
|
|
procedure ReadFrame; override;
|
|
procedure WriteFrame; override;
|
|
|
|
function GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal; override;
|
|
function GetIsEmpty: Boolean; override;
|
|
function MustWriteAsUTF: Boolean; override;
|
|
|
|
procedure ChangeToVersion(const ANewVersion: TJvID3Version); override;
|
|
function SupportsVersion(const AVersion: TJvID3Version): Boolean; override;
|
|
function SameUniqueIDAs(const Frame: TJvID3Frame): Boolean; override;
|
|
|
|
public
|
|
class function CanAddFrame(AController: TJvID3Controller; AFrameID: TJvID3FrameID): Boolean; override;
|
|
function CheckFrame(const {%H-}HandleError: TJvID3HandleError): Boolean; override;
|
|
|
|
procedure Assign(Source: TPersistent); override;
|
|
procedure Clear; override;
|
|
|
|
class function Find(AController: TJvID3Controller; const AFrameID: TJvID3FrameID): TJvID3DoubleListFrame;
|
|
class function FindOrCreate(AController: TJvID3Controller; const AFrameID: TJvID3FrameID): TJvID3DoubleListFrame;
|
|
|
|
procedure AfterConstruction; override;
|
|
procedure BeforeDestruction; override;
|
|
|
|
property Names[const AIndex: Integer]: String read GetNames;
|
|
property Values[const AIndex: Integer]: String read GetValues;
|
|
//property Names[const Index: Integer]: WideString read GetNames;
|
|
//property Values[const Index: Integer]: WideString read GetValues;
|
|
published
|
|
property List: TStrings read FList write SetList;
|
|
//property List: {$IFDEF COMPILER12_UP}TStrings{$ELSE}TWideStrings{$ENDIF COMPILER12_UP} read FList write SetList;
|
|
end;
|
|
|
|
{ COMM - fiComment - Comments
|
|
|
|
There may be more than one comment frame in each tag, but only one with
|
|
the same language and content descriptor.
|
|
|
|
USLT - fiUnsyncedLyrics - Unsynchronized lyric/text transcription
|
|
|
|
There may be more than one 'Unsynchronised lyrics/text transcription' frame
|
|
in each tag, but only one with the same language and content descriptor. }
|
|
TJvID3ContentFrame = class(TJvID3Frame)
|
|
private
|
|
FLanguage: AnsiString;
|
|
FText: String;
|
|
FDescription: String;
|
|
//FText: WideString;
|
|
//FDescription: WideString;
|
|
procedure SetDescription(const Value: String);
|
|
// procedure SetDescription(const Value: WideString);
|
|
procedure SetLanguage(const Value: AnsiString);
|
|
procedure SetText(const Value: String);
|
|
//procedure SetText(const Value: WideString);
|
|
protected
|
|
procedure ReadFrame; override;
|
|
procedure WriteFrame; override;
|
|
|
|
function GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal; override;
|
|
function GetIsEmpty: Boolean; override;
|
|
function MustWriteAsUTF: Boolean; override;
|
|
|
|
function SameUniqueIDAs(const Frame: TJvID3Frame): Boolean; override;
|
|
public
|
|
class function CanAddFrame(AController: TJvID3Controller; AFrameID: TJvID3FrameID): Boolean; override;
|
|
function CheckFrame(const HandleError: TJvID3HandleError): Boolean; override;
|
|
|
|
procedure Assign(Source: TPersistent); override;
|
|
procedure Clear; override;
|
|
|
|
class function Find(AController: TJvID3Controller; const AFrameID: TJvID3FrameID): TJvID3ContentFrame;
|
|
class function FindOrCreate(AController: TJvID3Controller; const AFrameID: TJvID3FrameID): TJvID3ContentFrame;
|
|
published
|
|
property Language: AnsiString read FLanguage write SetLanguage;
|
|
property Description: String read FDescription write SetDescription;
|
|
property Text: String read FText write SetText;
|
|
//property Description: WideString read FDescription write SetDescription;
|
|
//property Text: WideString read FText write SetText;
|
|
end;
|
|
|
|
{ GEOB - fiGeneralObject - General encapsulated object
|
|
|
|
There may be more than one "GEOB" frame in each tag, but only one with the
|
|
same content descriptor }
|
|
TJvID3GeneralObjFrame = class(TJvID3BinaryFrame)
|
|
private
|
|
//FContentDescription: WideString;
|
|
FContentDescription: String;
|
|
FMIMEType: AnsiString;
|
|
//FFileName: WideString;
|
|
FFileName: String;
|
|
procedure SetContentDescription(const Value: String);
|
|
//procedure SetContentDescription(const Value: WideString);
|
|
//procedure SetFileName(const Value: WideString);
|
|
procedure SetFileName(const Value: String);
|
|
procedure SetMIMEType(const Value: AnsiString);
|
|
protected
|
|
procedure ReadFrame; override;
|
|
procedure WriteFrame; override;
|
|
|
|
function GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal; override;
|
|
function GetIsEmpty: Boolean; override;
|
|
function MustWriteAsUTF: Boolean; override;
|
|
|
|
function SameUniqueIDAs(const Frame: TJvID3Frame): Boolean; override;
|
|
public
|
|
class function CanAddFrame(AController: TJvID3Controller; AFrameID: TJvID3FrameID): Boolean; override;
|
|
function CheckFrame(const {%H-}HandleError: TJvID3HandleError): Boolean; override;
|
|
|
|
procedure Assign(Source: TPersistent); override;
|
|
procedure Clear; override;
|
|
|
|
class function Find(AController: TJvID3Controller): TJvID3GeneralObjFrame; overload;
|
|
// class function Find(AController: TJvID3Controller; const AContentDescription: WideString): TJvID3GeneralObjFrame; overload;
|
|
class function Find(AController: TJvID3Controller;
|
|
const AContentDescription: String): TJvID3GeneralObjFrame; overload;
|
|
class function FindOrCreate(AController: TJvID3Controller): TJvID3GeneralObjFrame; overload;
|
|
//class function FindOrCreate(AController: TJvID3Controller; const AContentDescription: WideString): TJvID3GeneralObjFrame; overload;
|
|
class function FindOrCreate(AController: TJvID3Controller;
|
|
const AContentDescription: String): TJvID3GeneralObjFrame; overload;
|
|
published
|
|
property MIMEType: AnsiString read FMIMEType write SetMIMEType;
|
|
//property FileName: WideString read FFileName write SetFileName;
|
|
property FileName: String read FFileName write SetFileName;
|
|
property ContentDescription: String read FContentDescription write SetContentDescription;
|
|
//property ContentDescription: WideString read FContentDescription write SetContentDescription;
|
|
end;
|
|
|
|
{ POPM - fiPopularimeter - Popularimeter
|
|
|
|
There may be more than one "POPM" frame in each tag, but only one with the
|
|
same email address. }
|
|
TJvID3PopularimeterFrame = class(TJvID3Frame)
|
|
private
|
|
FRating: Byte;
|
|
FCounter: Cardinal;
|
|
FEMailAddress: AnsiString;
|
|
procedure SetCounter(const Value: Cardinal);
|
|
procedure SetEMailAddress(const Value: AnsiString);
|
|
procedure SetRating(const Value: Byte);
|
|
protected
|
|
procedure ReadFrame; override;
|
|
procedure WriteFrame; override;
|
|
|
|
function GetFrameSize(const {%H-}ToEncoding: TJvID3Encoding): Cardinal; override;
|
|
function GetIsEmpty: Boolean; override;
|
|
|
|
function SameUniqueIDAs(const Frame: TJvID3Frame): Boolean; override;
|
|
public
|
|
class function CanAddFrame(AController: TJvID3Controller; AFrameID: TJvID3FrameID): Boolean; override;
|
|
function CheckFrame(const {%H-}HandleError: TJvID3HandleError): Boolean; override;
|
|
|
|
procedure Assign(Source: TPersistent); override;
|
|
procedure Clear; override;
|
|
|
|
class function Find(AController: TJvID3Controller): TJvID3PopularimeterFrame; overload;
|
|
class function Find(AController: TJvID3Controller;
|
|
const AEmailAddress: AnsiString): TJvID3PopularimeterFrame; overload;
|
|
class function FindOrCreate(AController: TJvID3Controller): TJvID3PopularimeterFrame; overload;
|
|
class function FindOrCreate(AController: TJvID3Controller;
|
|
const AEmailAddress: AnsiString): TJvID3PopularimeterFrame; overload;
|
|
published
|
|
property EMailAddress: AnsiString read FEMailAddress write SetEMailAddress;
|
|
property Rating: Byte read FRating write SetRating;
|
|
property Counter: Cardinal read FCounter write SetCounter;
|
|
end;
|
|
|
|
{ PCNT - fiPlayCounter - Play counter
|
|
|
|
There may only be one "PCNT" frame in each tag. }
|
|
TJvID3PlayCounterFrame = class(TJvID3Frame)
|
|
private
|
|
FCounter: Cardinal;
|
|
procedure SetCounter(const Value: Cardinal);
|
|
protected
|
|
procedure ReadFrame; override;
|
|
procedure WriteFrame; override;
|
|
|
|
function GetFrameSize(const {%H-}ToEncoding: TJvID3Encoding): Cardinal; override;
|
|
function GetIsEmpty: Boolean; override;
|
|
|
|
function SameUniqueIDAs(const Frame: TJvID3Frame): Boolean; override;
|
|
public
|
|
class function CanAddFrame(AController: TJvID3Controller;
|
|
AFrameID: TJvID3FrameID): Boolean; override;
|
|
function CheckFrame(const {%H-}HandleError: TJvID3HandleError): Boolean; override;
|
|
|
|
procedure Assign(Source: TPersistent); override;
|
|
procedure Clear; override;
|
|
|
|
class function Find(AController: TJvID3Controller): TJvID3PlayCounterFrame;
|
|
class function FindOrCreate(AController: TJvID3Controller): TJvID3PlayCounterFrame;
|
|
published
|
|
property Counter: Cardinal read FCounter write SetCounter;
|
|
end;
|
|
|
|
{ AENC - fiAudioCrypto - Audio encryption
|
|
|
|
There may be more than one "AENC" frames in a tag, but only one with
|
|
the same 'Owner identifier'. }
|
|
TJvID3AudioEncryptionFrame = class(TJvID3BinaryFrame)
|
|
private
|
|
FOwnerID: AnsiString;
|
|
FPreviewStart: Word;
|
|
FPreviewLength: Word;
|
|
procedure SetOwnerID(const Value: AnsiString);
|
|
procedure SetPreviewLength(const Value: Word);
|
|
procedure SetPreviewStart(const Value: Word);
|
|
protected
|
|
procedure ReadFrame; override;
|
|
procedure WriteFrame; override;
|
|
|
|
function GetFrameSize(const {%H-}ToEncoding: TJvID3Encoding): Cardinal; override;
|
|
function GetIsEmpty: Boolean; override;
|
|
|
|
function SameUniqueIDAs(const Frame: TJvID3Frame): Boolean; override;
|
|
public
|
|
class function CanAddFrame(AController: TJvID3Controller; AFrameID: TJvID3FrameID): Boolean; override;
|
|
function CheckFrame(const HandleError: TJvID3HandleError): Boolean; override;
|
|
|
|
procedure Assign(Source: TPersistent); override;
|
|
procedure Clear; override;
|
|
|
|
class function Find(AController: TJvID3Controller; const AOwnerID: AnsiString): TJvID3AudioEncryptionFrame;
|
|
class function FindOrCreate(AController: TJvID3Controller; const AOwnerID: AnsiString): TJvID3AudioEncryptionFrame;
|
|
published
|
|
property OwnerID: AnsiString read FOwnerID write SetOwnerID;
|
|
property PreviewStart: Word read FPreviewStart write SetPreviewStart;
|
|
property PreviewLength: Word read FPreviewLength write SetPreviewLength;
|
|
end;
|
|
|
|
{ USER - fiTermsOfUse - Terms of use
|
|
|
|
There may only be one "USER" frame in a tag. }
|
|
TJvID3TermsOfUseFrame = class(TJvID3Frame)
|
|
private
|
|
//FText: WideString;
|
|
FText: String;
|
|
FLanguage: AnsiString;
|
|
procedure SetLanguage(const Value: AnsiString);
|
|
procedure SetText(const Value: String);
|
|
//procedure SetText(const Value: WideString);
|
|
protected
|
|
procedure ReadFrame; override;
|
|
procedure WriteFrame; override;
|
|
|
|
function GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal; override;
|
|
function GetIsEmpty: Boolean; override;
|
|
function MustWriteAsUTF: Boolean; override;
|
|
|
|
function SupportsVersion(const AVersion: TJvID3Version): Boolean; override;
|
|
function SameUniqueIDAs(const Frame: TJvID3Frame): Boolean; override;
|
|
public
|
|
class function CanAddFrame(AController: TJvID3Controller; AFrameID: TJvID3FrameID): Boolean; override;
|
|
function CheckFrame(const HandleError: TJvID3HandleError): Boolean; override;
|
|
|
|
procedure Assign(Source: TPersistent); override;
|
|
procedure Clear; override;
|
|
|
|
class function Find(AController: TJvID3Controller): TJvID3TermsOfUseFrame;
|
|
class function FindOrCreate(AController: TJvID3Controller): TJvID3TermsOfUseFrame;
|
|
published
|
|
property Language: AnsiString read FLanguage write SetLanguage;
|
|
property Text: String read FText write SetText;
|
|
//property Text: WideString read FText write SetText;
|
|
end;
|
|
|
|
{ OWNE - fiOwnership - Ownership frame
|
|
|
|
There may only be one "OWNE" frame in a tag. }
|
|
TJvID3OwnershipFrame = class(TJvID3Frame)
|
|
private
|
|
FPricePayed: AnsiString;
|
|
//FSeller: WideString;
|
|
FSeller: String;
|
|
FDateOfPurch: TDateTime;
|
|
procedure SetDateOfPurch(const Value: TDateTime);
|
|
procedure SetPricePayed(const Value: AnsiString);
|
|
procedure SetSeller(const Value: String);
|
|
//procedure SetSeller(const Value: WideString);
|
|
protected
|
|
procedure ReadFrame; override;
|
|
procedure WriteFrame; override;
|
|
|
|
function GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal; override;
|
|
function GetIsEmpty: Boolean; override;
|
|
function MustWriteAsUTF: Boolean; override;
|
|
|
|
function SupportsVersion(const AVersion: TJvID3Version): Boolean; override;
|
|
function SameUniqueIDAs(const Frame: TJvID3Frame): Boolean; override;
|
|
public
|
|
class function CanAddFrame(AController: TJvID3Controller; AFrameID: TJvID3FrameID): Boolean; override;
|
|
function CheckFrame(const {%H-}HandleError: TJvID3HandleError): Boolean; override;
|
|
|
|
procedure Assign(Source: TPersistent); override;
|
|
procedure Clear; override;
|
|
|
|
class function Find(AController: TJvID3Controller): TJvID3OwnershipFrame;
|
|
class function FindOrCreate(AController: TJvID3Controller): TJvID3OwnershipFrame;
|
|
published
|
|
property PricePayed: AnsiString read FPricePayed write SetPricePayed;
|
|
property DateOfPurch: TDateTime read FDateOfPurch write SetDateOfPurch;
|
|
property Seller: String read FSeller write SetSeller;
|
|
//property Seller: WideString read FSeller write SetSeller;
|
|
end;
|
|
|
|
{ APIC - fiPicture - Attached picture
|
|
|
|
There may be several pictures attached to one file, each in their individual
|
|
"APIC" frame, but only one with the same content descriptor ( * ). There may only
|
|
be one picture with the picture type declared as picture type $01 and $02 ( ** )
|
|
respectively.
|
|
|
|
( * ) content descriptor = FPictureType, FDescription
|
|
( ** ) $01 = ptFileIcon; $02 = ptOtherFileIcon }
|
|
TJvID3PictureFrame = class(TJvID3BinaryFrame)
|
|
private
|
|
FMIMEType: AnsiString;
|
|
FPictureType: TJvID3PictureType;
|
|
//FDescription: WideString;
|
|
FDescription: String;
|
|
FURL: AnsiString;
|
|
//procedure SetDescription(const Value: WideString);
|
|
procedure SetDescription(const Value: String);
|
|
procedure SetMIMEType(const Value: AnsiString);
|
|
procedure SetURL(const Value: AnsiString);
|
|
function GetHasOnlyURL: Boolean;
|
|
protected
|
|
procedure ReadFrame; override;
|
|
procedure WriteFrame; override;
|
|
|
|
function GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal; override;
|
|
function GetIsEmpty: Boolean; override;
|
|
function MustWriteAsUTF: Boolean; override;
|
|
|
|
function SameUniqueIDAs(const Frame: TJvID3Frame): Boolean; override;
|
|
|
|
procedure AssignTo(Dest: TPersistent); override;
|
|
|
|
{ There is the possibility to put only a link to the image file by using the 'MIME
|
|
type' "-->" and having a complete URL [URL] instead of picture data.
|
|
The use of linked files should however be used sparingly since there
|
|
is the risk of separation of files: }
|
|
property HasOnlyURL: Boolean read GetHasOnlyURL;
|
|
public
|
|
class function CanAddFrame(AController: TJvID3Controller; AFrameID: TJvID3FrameID): Boolean; override;
|
|
function CheckFrame(const HandleError: TJvID3HandleError): Boolean; override;
|
|
|
|
procedure Assign(Source: TPersistent); override;
|
|
procedure Clear; override;
|
|
|
|
class function Find(AController: TJvID3Controller; const AType: TJvID3PictureType): TJvID3PictureFrame;
|
|
class function FindOrCreate(AController: TJvID3Controller; const AType: TJvID3PictureType): TJvID3PictureFrame;
|
|
published
|
|
property MIMEType: AnsiString read FMIMEType write SetMIMEType;
|
|
property PictureType: TJvID3PictureType read FPictureType write FPictureType;
|
|
property Description: String read FDescription write SetDescription;
|
|
//property Description: WideString read FDescription write SetDescription;
|
|
{ Only used when MIMEType = '-->' }
|
|
property URL: AnsiString read FURL write SetURL;
|
|
end;
|
|
|
|
TJvID3CustomTextFrame = class(TJvID3Frame)
|
|
protected
|
|
function GetText: String; virtual; abstract;
|
|
procedure SetText(const ANewText: String); virtual; abstract;
|
|
{
|
|
function GetText: WideString; virtual; abstract;
|
|
procedure SetText(const ANewText: WideString); virtual; abstract;
|
|
}
|
|
procedure ReadFrame; override;
|
|
procedure WriteFrame; override;
|
|
|
|
function GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal; override;
|
|
function GetIsEmpty: Boolean; override;
|
|
function MustWriteAsUTF: Boolean; override;
|
|
|
|
function SupportsVersion(const AVersion: TJvID3Version): Boolean; override;
|
|
function SameUniqueIDAs(const Frame: TJvID3Frame): Boolean; override;
|
|
public
|
|
class function CanAddFrame(AController: TJvID3Controller; AFrameID: TJvID3FrameID): Boolean; override;
|
|
|
|
procedure Assign(Source: TPersistent); override;
|
|
procedure Clear; override;
|
|
|
|
//property Text: WideString read GetText write SetText;
|
|
property Text: String read GetText write SetText;
|
|
end;
|
|
|
|
TJvID3SimpleListFrame = class(TJvID3CustomTextFrame)
|
|
private
|
|
FList: TStrings;
|
|
// FList: {$IFDEF COMPILER12_UP}TStrings{$ELSE}TWideStrings{$ENDIF COMPILER12_UP};
|
|
// procedure SetList(Value: {$IFDEF COMPILER12_UP}TStrings{$ELSE}TWideStrings{$ENDIF COMPILER12_UP});
|
|
procedure SetList(Value: TStrings);
|
|
//function GetSeparator: WideChar;
|
|
function GetSeparator: Char;
|
|
function GetFixedStringLength: Integer;
|
|
procedure ListChanged(Sender: TObject);
|
|
function GetIsNullSeparator: Boolean;
|
|
protected
|
|
function GetText: String; override;
|
|
//function GetText: WideString; override;
|
|
//procedure SetText(const ANewText: WideString); override;
|
|
procedure SetText(const ANewText: String); override;
|
|
|
|
procedure ReadFrame; override;
|
|
procedure WriteFrame; override;
|
|
|
|
function GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal; override;
|
|
public
|
|
function CheckFrame(const HandleError: TJvID3HandleError): Boolean; override;
|
|
class function Find(AController: TJvID3Controller;
|
|
const AFrameID: TJvID3FrameID): TJvID3SimpleListFrame;
|
|
class function FindOrCreate(AController: TJvID3Controller;
|
|
const AFrameID: TJvID3FrameID): TJvID3SimpleListFrame;
|
|
|
|
procedure AfterConstruction; override;
|
|
procedure BeforeDestruction; override;
|
|
|
|
property FixedStringLength: Integer read GetFixedStringLength;
|
|
property Separator: Char read GetSeparator;
|
|
//property Separator: WideChar read GetSeparator; /// ???? WideChar ????
|
|
property IsNullSeparator: Boolean read GetIsNullSeparator;
|
|
published
|
|
//property List: {$IFDEF COMPILER12_UP}TStrings{$ELSE}TWideStrings{$ENDIF COMPILER12_UP} read FList write SetList;
|
|
property List: TStrings read FList write Setlist;
|
|
end;
|
|
|
|
TJvID3NumberFrame = class(TJvID3CustomTextFrame)
|
|
private
|
|
FValue: Cardinal;
|
|
procedure SetValue(const AValue: Cardinal);
|
|
protected
|
|
function GetText: String; override;
|
|
procedure SetText(const ANewText: String); override;
|
|
//function GetText: WideString; override;
|
|
//procedure SetText(const ANewText: WideString); override;
|
|
procedure ChangeToVersion(const ANewVersion: TJvID3Version); override;
|
|
function GetIsEmpty: Boolean; override;
|
|
public
|
|
function CheckFrame(const HandleError: TJvID3HandleError): Boolean; override;
|
|
class function Find(AController: TJvID3Controller; const AFrameID: TJvID3FrameID): TJvID3NumberFrame;
|
|
class function FindOrCreate(AController: TJvID3Controller; const AFrameID: TJvID3FrameID): TJvID3NumberFrame;
|
|
published
|
|
property Value: Cardinal read FValue write SetValue;
|
|
end;
|
|
|
|
TJvID3TimestampFrame = class(TJvID3CustomTextFrame)
|
|
private
|
|
FValue: TDateTime;
|
|
procedure SetValue(const AValue: TDateTime);
|
|
protected
|
|
function GetText: String; override;
|
|
//function GetText: WideString; override;
|
|
procedure SetText(const ANewText: String); override;
|
|
//procedure SetText(const ANewText: WideString); override;
|
|
procedure ChangeToVersion(const ANewVersion: TJvID3Version); override;
|
|
public
|
|
function CheckFrame(const {%H-}HandleError: TJvID3HandleError): Boolean; override;
|
|
class function Find(AController: TJvID3Controller; const AFrameID: TJvID3FrameID): TJvID3TimestampFrame;
|
|
class function FindOrCreate(AController: TJvID3Controller; const AFrameID: TJvID3FrameID): TJvID3TimestampFrame;
|
|
published
|
|
property Value: TDateTime read FValue write SetValue;
|
|
end;
|
|
|
|
TJvID3TextFrame = class(TJvID3CustomTextFrame)
|
|
private
|
|
//FText: WideString;
|
|
FText: String;
|
|
protected
|
|
function GetText: String; override;
|
|
//function GetText: WideString; override;
|
|
//procedure SetText(const ANewText: WideString); override;
|
|
procedure SetText(const ANewText: String); override;
|
|
procedure ChangeToVersion(const ANewVersion: TJvID3Version); override;
|
|
public
|
|
function CheckFrame(const HandleError: TJvID3HandleError): Boolean; override;
|
|
class function Find(AController: TJvID3Controller; const AFrameID: TJvID3FrameID): TJvID3TextFrame;
|
|
class function FindOrCreate(AController: TJvID3Controller; const AFrameID: TJvID3FrameID): TJvID3TextFrame;
|
|
published
|
|
property Text;
|
|
end;
|
|
|
|
TJvID3URLFrame = class(TJvID3Frame)
|
|
private
|
|
FURL: AnsiString;
|
|
procedure SetURL(const Value: AnsiString);
|
|
protected
|
|
procedure ReadFrame; override;
|
|
procedure WriteFrame; override;
|
|
|
|
function GetFrameSize(const {%H-}ToEncoding: TJvID3Encoding): Cardinal; override;
|
|
function GetIsEmpty: Boolean; override;
|
|
|
|
function SameUniqueIDAs(const Frame: TJvID3Frame): Boolean; override;
|
|
public
|
|
class function CanAddFrame(AController: TJvID3Controller; AFrameID: TJvID3FrameID): Boolean; override;
|
|
function CheckFrame(const HandleError: TJvID3HandleError): Boolean; override;
|
|
|
|
procedure Assign(Source: TPersistent); override;
|
|
procedure Clear; override;
|
|
|
|
class function Find(AController: TJvID3Controller; const AFrameID: TJvID3FrameID): TJvID3URLFrame;
|
|
class function FindOrCreate(AController: TJvID3Controller; const AFrameID: TJvID3FrameID): TJvID3URLFrame;
|
|
published
|
|
property URL: AnsiString read FURL write SetURL;
|
|
end;
|
|
|
|
{ TXXX - fiUserText - User defined text information }
|
|
TJvID3UserFrame = class(TJvID3Frame)
|
|
private
|
|
FValue: String;
|
|
FDescription: String;
|
|
//FValue: WideString;
|
|
//FDescription: WideString;
|
|
//procedure SetDescription(const AValue: WideString);
|
|
//procedure SetValue(const AValue: WideString);
|
|
procedure SetDescription(const AValue: String);
|
|
procedure SetValue(const AValue: String);
|
|
protected
|
|
procedure ReadFrame; override;
|
|
procedure WriteFrame; override;
|
|
|
|
function GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal; override;
|
|
function GetIsEmpty: Boolean; override;
|
|
function MustWriteAsUTF: Boolean; override;
|
|
public
|
|
class function CanAddFrame(AController: TJvID3Controller; AFrameID: TJvID3FrameID): Boolean; override;
|
|
function CheckFrame(const {%H-}HandleError: TJvID3HandleError): Boolean; override;
|
|
|
|
procedure Assign(Source: TPersistent); override;
|
|
procedure Clear; override;
|
|
|
|
class function Find(AController: TJvID3Controller; const AIndex: Integer): TJvID3UserFrame;
|
|
class function FindOrCreate(AController: TJvID3Controller; const AIndex: Integer): TJvID3UserFrame;
|
|
published
|
|
property Description: String read FDescription write SetDescription;
|
|
property Value: String read FValue write SetValue;
|
|
//property Description: WideString read FDescription write SetDescription;
|
|
//property Value: WideString read FValue write SetValue;
|
|
end;
|
|
|
|
{ WXXX - fiWWWUser - User defined URL link }
|
|
TJvID3URLUserFrame = class(TJvID3Frame)
|
|
private
|
|
//FDescription: WideString;
|
|
FDescription: String;
|
|
FURL: AnsiString;
|
|
//procedure SetDescription(const Value: WideString);
|
|
procedure SetDescription(const Value: String);
|
|
procedure SetURL(const Value: AnsiString);
|
|
protected
|
|
procedure ReadFrame; override;
|
|
procedure WriteFrame; override;
|
|
|
|
function GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal; override;
|
|
function GetIsEmpty: Boolean; override;
|
|
function MustWriteAsUTF: Boolean; override;
|
|
public
|
|
function CheckFrame(const HandleError: TJvID3HandleError): Boolean; override;
|
|
procedure Assign(Source: TPersistent); override;
|
|
procedure Clear; override;
|
|
|
|
class function CanAddFrame(AController: TJvID3Controller;
|
|
AFrameID: TJvID3FrameID): Boolean; override;
|
|
class function Find(AController: TJvID3Controller;
|
|
const AIndex: Integer): TJvID3URLUserFrame;
|
|
class function FindOrCreate(AController: TJvID3Controller;
|
|
const AIndex: Integer): TJvID3URLUserFrame;
|
|
|
|
published
|
|
//property Description: WideString read FDescription write SetDescription;
|
|
property Description: String read FDescription write SetDescription;
|
|
property URL: AnsiString read FURL write SetURL;
|
|
end;
|
|
|
|
TJvID3FileInfo = class(TPersistent)
|
|
private
|
|
FAudioSize: Int64;
|
|
FBitrate: Integer;
|
|
FBits: TJvMPEGBits;
|
|
FChannelMode: TJvMPEGChannelMode;
|
|
FEmphasis: TJvMPEGEmphasis;
|
|
FFileSize: Int64;
|
|
FFrameCount: Integer;
|
|
FFrameLengthInBytes: Integer;
|
|
FHasID3v1Tag: Boolean;
|
|
FHeaderFoundAt: Int64;
|
|
FIsVBR: Boolean;
|
|
FLayer: TJvMPEGLayer;
|
|
FLengthInSec: Integer;
|
|
FModeExtension: TJvMPEGModeExtension;
|
|
FPaddingLength: Integer;
|
|
FSamplingRateFrequency: Integer;
|
|
FVersion: TJvMPEGVersion;
|
|
function GetIsValid: Boolean;
|
|
protected
|
|
procedure Calc;
|
|
procedure ParseMPEGTag(AMPEGTag: PAnsiChar);
|
|
procedure ParseVbrTag(AMPEGTag: PAnsiChar);
|
|
procedure Reset;
|
|
public
|
|
procedure Read(AStream: TStream; const Offset: Int64);
|
|
|
|
property Bitrate: Integer read FBitrate;
|
|
property Bits: TJvMPEGBits read FBits;
|
|
property ChannelMode: TJvMPEGChannelMode read FChannelMode;
|
|
property Emphasis: TJvMPEGEmphasis read FEmphasis;
|
|
property FileSize: Int64 read FFileSize;
|
|
property FrameCount: Integer read FFrameCount;
|
|
property FrameLengthInBytes: Integer read FFrameLengthInBytes;
|
|
property HeaderFoundAt: Int64 read FHeaderFoundAt;
|
|
property IsValid: Boolean read GetIsValid;
|
|
property IsVBR: Boolean read FIsVBR;
|
|
property Layer: TJvMPEGLayer read FLayer;
|
|
property LengthInSec: Integer read FLengthInSec;
|
|
property ModeExtension: TJvMPEGModeExtension read FModeExtension;
|
|
property SamplingRateFrequency: Integer read FSamplingRateFrequency;
|
|
property Version: TJvMPEGVersion read FVersion;
|
|
end;
|
|
|
|
TJvID3ControllerDesigner = class(TObject)
|
|
private
|
|
FController: TJvID3Controller;
|
|
public
|
|
constructor Create(Controller: TJvID3Controller);
|
|
destructor Destroy; override;
|
|
procedure BeginDesign;
|
|
procedure ID3Event({%H-}Event: TJvID3Event; {%H-}Info: Longint); virtual;
|
|
procedure EndDesign;
|
|
property Controller: TJvID3Controller read FController;
|
|
end;
|
|
|
|
TJvID3ControllerState = (icsReading, icsWriting, icsUsingTempStream);
|
|
TJvID3ControllerStates = set of TJvID3ControllerState;
|
|
|
|
TJvID3Controller = class(TComponent) // TJvComponent)
|
|
private
|
|
FState: TJvID3ControllerStates;
|
|
FStream: TJvID3Stream;
|
|
FTempStream: TJvID3Stream;
|
|
FFrames: TJvID3Frames;
|
|
FClients: TList;
|
|
FActivateEvents: TList;
|
|
|
|
FFileInfo: TJvID3FileInfo;
|
|
FHeader: TJvID3Header;
|
|
FExtendedHeader: TJvID3ExtendedHeader;
|
|
FActive: Boolean;
|
|
FStreamedActive: Boolean;
|
|
FFileName: TFileName;
|
|
FDesigner: TJvID3ControllerDesigner;
|
|
FModified: Boolean;
|
|
FOptions: TJvID3ControllerOptions;
|
|
FWriteEncodingAs: TJvID3ForceEncoding;
|
|
FReadEncodingAs: TJvID3ForceEncoding;
|
|
FReadVersionAs: TJvID3ForceVersion;
|
|
FWriteVersionAs: TJvID3ForceVersion;
|
|
FUpdateCount: Integer;
|
|
function GetFrameCount: Integer;
|
|
function GetReadVersion: TJvID3Version;
|
|
function GetTagSize: Cardinal;
|
|
function GetVersion: TJvID3Version;
|
|
function GetWriteVersion: TJvID3Version;
|
|
procedure SetActive(const Value: Boolean);
|
|
procedure SetExtendedHeader(const Value: TJvID3ExtendedHeader);
|
|
procedure SetFileName(const Value: TFileName);
|
|
procedure SetHeader(const Value: TJvID3Header);
|
|
procedure SetReadEncodingAs(const Value: TJvID3ForceEncoding);
|
|
procedure SetReadVersionAs(const Value: TJvID3ForceVersion);
|
|
procedure SetVersion(NewVersion: TJvID3Version);
|
|
procedure SetWriteEncodingAs(const Value: TJvID3ForceEncoding);
|
|
procedure SetWriteVersionAs(const Value: TJvID3ForceVersion);
|
|
protected
|
|
class function GetFrameClass(const FrameID: TJvID3FrameID): TJvID3FrameClass; virtual;
|
|
procedure SetModified(Value: Boolean);
|
|
procedure ChangeToVersion(const ANewVersion: TJvID3Version);
|
|
|
|
procedure CheckFrameClass(FrameClass: TJvID3FrameClass; const AFrameID: TJvID3FrameID);
|
|
|
|
procedure RegisterClient(Client: TObject; Event: TJvID3ActivateChangeEvent = nil); virtual;
|
|
procedure SendActivateEvent(Activated: Boolean);
|
|
procedure UnRegisterClient(Client: TObject); virtual;
|
|
|
|
procedure ID3Event(Event: TJvID3Event; Info: Longint); virtual;
|
|
|
|
procedure BeginReading;
|
|
procedure EndReading;
|
|
procedure BeginWriting;
|
|
procedure EndWriting;
|
|
procedure BeginUseTempStream;
|
|
procedure EndUseTempStream;
|
|
|
|
procedure LoadFromStream(AStream: TStream);
|
|
procedure SaveToFile(const AFileName: string);
|
|
|
|
procedure DoOpen; virtual;
|
|
procedure DoClose; virtual;
|
|
|
|
procedure Loaded; override;
|
|
|
|
procedure ApplyUnsynchronisationSchemeOnCurrentStream;
|
|
|
|
{ Temporary stream functions }
|
|
function GetTempStreamSize: Cardinal;
|
|
procedure RemoveUnsynchronisationSchemeToTempStream(const ASize: Integer);
|
|
procedure WriteTempStream;
|
|
|
|
property Header: TJvID3Header read FHeader write SetHeader stored False;
|
|
property ExtendedHeader: TJvID3ExtendedHeader read FExtendedHeader write SetExtendedHeader stored False;
|
|
property FileInfo: TJvID3FileInfo read FFileInfo;
|
|
property ReadEncodingAs: TJvID3ForceEncoding read FReadEncodingAs write SetReadEncodingAs default ifeAuto;
|
|
property WriteEncodingAs: TJvID3ForceEncoding read FWriteEncodingAs write SetWriteEncodingAs default ifeAuto;
|
|
property ReadVersionAs: TJvID3ForceVersion read FReadVersionAs write SetReadVersionAs default ifvDontCare;
|
|
property WriteVersionAs: TJvID3ForceVersion read FWriteVersionAs write SetWriteVersionAs default ifvDontCare;
|
|
property Options: TJvID3ControllerOptions read FOptions write FOptions default [coAutoCorrect,
|
|
coRemoveEmptyFrames];
|
|
property Version: TJvID3Version read GetVersion write SetVersion stored False;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
|
|
procedure BeginUpdate;
|
|
procedure EndUpdate;
|
|
|
|
procedure Open;
|
|
procedure Commit;
|
|
procedure Erase;
|
|
procedure Close;
|
|
|
|
{ Indicates whether a frame of type AFrameID can be added to the tag. For
|
|
example there may not be more than 1 text frame with the same frame
|
|
id - for example fiAlbum - in the tag. }
|
|
function CanAddFrame(const AFrameID: TJvID3FrameID): Boolean;
|
|
{ Indicates whether tag has has a frame of type AFrameID }
|
|
function HasFrame(const AFrameID: TJvID3FrameID): Boolean;
|
|
{ Adds a frame of type AFrameID to the tag }
|
|
function AddFrame(const AFrameID: TJvID3FrameID): TJvID3Frame;
|
|
function FindFirstFrame(const AFrameID: TJvID3FrameID;
|
|
var Frame: TJvID3Frame): Boolean;
|
|
function FindNextFrame(const AFrameID: TJvID3FrameID; var From: TJvID3Frame): Boolean;
|
|
{ Returns the nr. of frames of type AFrameID in the tag }
|
|
function GetFrameCountFor(const AFrameID: TJvID3FrameID): Cardinal;
|
|
|
|
function CopyToID3v1(const DoOverwrite: Boolean = True): Boolean;
|
|
procedure CopyToID3v1Ctrl(AID3v1: TJvID3v1; const DoOverwrite: Boolean = True);
|
|
function CopyFromID3v1(const DoOverwrite: Boolean = True): Boolean;
|
|
procedure CopyFromID3v1Ctrl(AID3v1: TJvID3v1; const DoOverwrite: Boolean = True);
|
|
|
|
procedure EnsureExists(const FrameIDs: TJvID3FrameIDs);
|
|
|
|
property Designer: TJvID3ControllerDesigner read FDesigner;
|
|
property TagSize: Cardinal read GetTagSize;
|
|
property Modified: Boolean read FModified;
|
|
property FrameCount: Integer read GetFrameCount;
|
|
property Frames: TJvID3Frames read FFrames;
|
|
property WriteVersion: TJvID3Version read GetWriteVersion;
|
|
property ReadVersion: TJvID3Version read GetReadVersion;
|
|
published
|
|
property Active: Boolean read FActive write SetActive;
|
|
property FileName: TFileName read FFileName write SetFileName;
|
|
end;
|
|
|
|
procedure ID3Error(const Msg: string; Component: TComponent = nil);
|
|
procedure ID3ErrorFmt(const Msg: string; const Args: array of const;
|
|
Component: TComponent = nil);
|
|
function CreateUniqueName(AController: TJvID3Controller; const FrameName: AnsiString;
|
|
FrameClass: TJvID3FrameClass; Component: TComponent): string;
|
|
procedure GetID3v2Version(const AFileName: string; var HasTag: Boolean;
|
|
out Version: TJvID3Version);
|
|
function ExtToMIMEType(const Ext: string): string;
|
|
function MIMETypeToExt(const MIMEType: string): string;
|
|
function GenreToNiceGenre(const AGenre: string): string;
|
|
function NiceGenreToGenre(const ANiceGenre: string): string;
|
|
|
|
|
|
implementation
|
|
|
|
uses
|
|
Graphics, Math, LazUTF8, LConvEncoding, LazFileUtils, DateUtils,
|
|
(*
|
|
{$IFDEF HAS_UNIT_ANSISTRINGS}
|
|
AnsiStrings,
|
|
{$ENDIF HAS_UNIT_ANSISTRINGS}
|
|
*)
|
|
JvJCLUtils,
|
|
(*
|
|
JclBase, JclFileUtils, JclLogic, JclDateTime,
|
|
JclStringConversions, JclWideStrings,
|
|
*)
|
|
JvConsts, JvResources;
|
|
|
|
type
|
|
TJvID3StringList = class(TStringList)
|
|
public
|
|
function GetSeparatedText(const Separator: string): string;
|
|
end;
|
|
|
|
const
|
|
CMapBitrate: array [Boolean, TJvMPEGLayer] of Byte = (
|
|
{ ?? - III - II - I }
|
|
( $00, $02, $01, $00), // V1
|
|
( $00, $04, $04, $03) // V2/V3
|
|
);
|
|
|
|
CFreeBitrate = -2;
|
|
|
|
CBadBitrate = -1;
|
|
|
|
CBitrate: array [$00..$04, $00..$0F] of Integer = (
|
|
(CFreeBitrate, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, CBadBitrate),
|
|
(CFreeBitrate, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, CBadBitrate),
|
|
(CFreeBitrate, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, CBadBitrate),
|
|
(CFreeBitrate, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, CBadBitrate),
|
|
(CFreeBitrate, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, CBadBitrate)
|
|
);
|
|
|
|
CSamplingFrequency: array [TJvMPEGVersion, $00..$03] of Integer = (
|
|
(11025, 12000, 8000, -1), // mvVersion25,
|
|
( 0, 0, 0, 0), // mvReserved,
|
|
(22050, 24000, 16000, -1), // mvVersion2,
|
|
(44100, 48000, 32000, -1) // mvVersion1
|
|
);
|
|
|
|
CLayerArray: array [TJvMPEGLayer] of Integer = (
|
|
1, // mlNotDefined,
|
|
144000, // mlLayerIII,
|
|
144000, // mlLayerII,
|
|
48000 // mlLayerI
|
|
);
|
|
|
|
cUnknownLanguage = AnsiString('XXX');
|
|
cID3HeaderId = AnsiString('ID3'); // do not change case
|
|
cChangeTagSizeFileNameTemplate: string = 'ChangeTagSize';
|
|
cPictureFrameFileNameTemplate: string = 'TJvID3PictureFrame';
|
|
cURLArrow = AnsiString('-->');
|
|
|
|
var
|
|
DefaultFrameClasses: array [TJvID3FrameID] of TJvID3FrameClass =
|
|
(
|
|
nil, { fiErrorFrame (special frame) }
|
|
nil, { fiPaddingFrame (special frame) }
|
|
TJvID3SkipFrame, { fiNoFrame (special frame) }
|
|
TJvID3AudioEncryptionFrame, { fiAudioCrypto }
|
|
TJvID3PictureFrame, { fiPicture }
|
|
nil, { fiAudioSeekPoint (new in 2.4) }
|
|
TJvID3ContentFrame, { fiComment }
|
|
nil, { fiCommercial (new in 2.3) }
|
|
nil, { fiCryptoReg (new in 2.3) }
|
|
nil, { fiEqualization2 (new in 2.4) }
|
|
nil, { fiEqualization (deprecated as of 2.4) }
|
|
nil, { fiEventTiming }
|
|
TJvID3GeneralObjFrame, { fiGeneralObject }
|
|
nil, { fiGroupingReg (new in 2.3) }
|
|
TJvID3DoubleListFrame, { fiInvolvedPeople (deprecated as of 2.4) }
|
|
nil, { fiLinkedInfo }
|
|
TJvID3BinaryFrame, { fiCDID }
|
|
nil, { fiMPEGLookup }
|
|
TJvID3OwnershipFrame, { fiOwnership (new in 2.3) }
|
|
nil, { fiPrivate (new in 2.3) }
|
|
TJvID3PlayCounterFrame, { fiPlayCounter }
|
|
TJvID3PopularimeterFrame, { fiPopularimeter }
|
|
nil, { fiPositionsync (new in 2.3) }
|
|
nil, { fiBufferSize }
|
|
nil, { fiVolumeAdj2 (new in 2.4) }
|
|
nil, { fiVolumeAdj (deprecated as of 2.4) }
|
|
nil, { fiReverb }
|
|
nil, { fiSeekFrame (new in 2.4) }
|
|
nil, { fiSignature (new in 2.4) }
|
|
nil, { fiSyncedLyrics }
|
|
nil, { fiSyncedTempo }
|
|
TJvID3TextFrame, { fiAlbum }
|
|
TJvID3TextFrame, { fiBPM } // was NumberFrame changed 03/15/10 DW
|
|
TJvID3SimpleListFrame, { fiComposer }
|
|
TJvID3SimpleListFrame, { fiContentType }
|
|
TJvID3TextFrame, { fiCopyright }
|
|
TJvID3TextFrame, { fiDate (deprecated as of 2.4) }
|
|
TJvID3TimestampFrame, { fiEncodingTime (new in 2.4) }
|
|
TJvID3NumberFrame, { fiPlaylistDelay }
|
|
TJvID3TimestampFrame, { fiOrigReleaseTime (new in 2.4) }
|
|
TJvID3TimestampFrame, { fiRecordingTime (new in 2.4) }
|
|
TJvID3TimestampFrame, { fiReleaseTime (new in 2.4) }
|
|
TJvID3TimestampFrame, { fiTaggingTime (new in 2.4) }
|
|
TJvID3DoubleListFrame, { fiInvolvedPeople2 (new in 2.4) }
|
|
TJvID3TextFrame, { fiEncodedBy }
|
|
TJvID3SimpleListFrame, { fiLyricist }
|
|
TJvID3TextFrame, { fiFileType }
|
|
TJvID3TextFrame, { fiTime (deprecated as of 2.4) }
|
|
TJvID3TextFrame, { fiContentGroup }
|
|
TJvID3TextFrame, { fiTitle }
|
|
TJvID3TextFrame, { fiSubTitle }
|
|
TJvID3TextFrame, { fiInitialKey }
|
|
TJvID3SimpleListFrame, { fiLanguage }
|
|
TJvID3NumberFrame, { fiSongLen }
|
|
TJvID3DoubleListFrame, { fiMusicianCreditList (new in 2.4) }
|
|
TJvID3TextFrame, { fiMediaType }
|
|
TJvID3TextFrame, { fiMood (new in 2.4) }
|
|
TJvID3TextFrame, { fiOrigAlbum }
|
|
TJvID3TextFrame, { fiOrigFileName }
|
|
TJvID3SimpleListFrame, { fiOrigLyricist }
|
|
TJvID3SimpleListFrame, { fiOrigArtist }
|
|
TJvID3NumberFrame, { fiOrigYear (deprecated as of 2.4) }
|
|
TJvID3TextFrame, { fiFileOwner (new in 2.3) }
|
|
TJvID3SimpleListFrame, { fiLeadArtist }
|
|
TJvID3TextFrame, { fiBand }
|
|
TJvID3TextFrame, { fiConductor }
|
|
TJvID3TextFrame, { fiMixArtist }
|
|
TJvID3TextFrame, { fiPartInSet }
|
|
TJvID3TextFrame, { fiProducedNotice (new in 2.4) }
|
|
TJvID3TextFrame, { fiPublisher }
|
|
TJvID3TextFrame, { fiTrackNum }
|
|
TJvID3TextFrame, { fiRecordingDates (deprecated as of 2.4) }
|
|
TJvID3TextFrame, { fiNetRadioStation }
|
|
TJvID3TextFrame, { fiNetRadioOwner }
|
|
TJvID3NumberFrame, { fiSize (deprecated as of 2.4) }
|
|
TJvID3TextFrame, { fiAlbumSortOrder (new in 2.4) }
|
|
TJvID3TextFrame, { fiPerformerSortOrder (new in 2.4) }
|
|
TJvID3TextFrame, { fiTitleSortOrder (new in 2.4) }
|
|
TJvID3TextFrame, { fiISRC }
|
|
TJvID3TextFrame, { fiEncoderSettings (new in 2.3) }
|
|
TJvID3TextFrame, { fiSetSubTitle (new in 2.4) }
|
|
TJvID3UserFrame, { fiUserText }
|
|
TJvID3NumberFrame, { fiYear (deprecated as of 2.4) }
|
|
nil, { fiUniqueFileID }
|
|
TJvID3TermsOfUseFrame, { fiTermsOfUse (new in 2.3) }
|
|
TJvID3ContentFrame, { fiUnsyncedLyrics }
|
|
TJvID3URLFrame, { fiWWWCommercialInfo }
|
|
TJvID3URLFrame, { fiWWWCopyright }
|
|
TJvID3URLFrame, { fiWWWAudioFile }
|
|
TJvID3URLFrame, { fiWWWArtist }
|
|
TJvID3URLFrame, { fiWWWAudioSource }
|
|
TJvID3URLFrame, { fiWWWRadioPage }
|
|
TJvID3URLFrame, { fiWWWPayment }
|
|
TJvID3URLFrame, { fiWWWPublisher }
|
|
TJvID3URLUserFrame, { fiWWWUser }
|
|
nil, { fiMetaCrypto (only in 2.2) }
|
|
nil { fiMetaCompressio (only in 2.2) }
|
|
);
|
|
|
|
//=== Local procedures =======================================================
|
|
(*
|
|
function LengthUTF8Str(const SW: WideString): Integer;
|
|
begin
|
|
Result := Length(WideStringToUTF8(SW));
|
|
end;
|
|
|
|
function CharCount(const S: WideString): Cardinal;
|
|
begin
|
|
Result := Length(S);
|
|
end;
|
|
|
|
{$IFNDEF COMPILER12_UP}
|
|
function SameStr(const S1, S2: WideString): Boolean;
|
|
begin
|
|
Result := StrICompW(PWideChar(S1), PWideChar(S2)) = 0
|
|
end;
|
|
{$ENDIF !COMPILER12_UP}
|
|
*)
|
|
|
|
{ Calculates the length in bytes needed to store a string in a stream encoded as
|
|
ToEnc; the string is encoded as FromEnc in the string pair S;
|
|
Very similar to GetByteCount }
|
|
function LengthEnc(const S: String; const Encoding: TJvID3Encoding): Cardinal;
|
|
var
|
|
L: Integer;
|
|
begin
|
|
L := UTF8Length(S);
|
|
case Encoding of
|
|
ienISO_8859_1:
|
|
Result := L;
|
|
//Result := CharCount(S);
|
|
ienUTF_16:
|
|
Result := 2 + 2 * L;
|
|
ienUTF_16BE:
|
|
Result := 2 * L;
|
|
ienUTF_8:
|
|
Result := Length(S);
|
|
else
|
|
Result := 0;
|
|
ID3Error(RsEID3UnknownEncoding);
|
|
end;
|
|
end;
|
|
|
|
{ Calculates the length in bytes needed to store a terminator in the encoding
|
|
specified by Encoding }
|
|
function LengthTerminatorEnc(const Encoding: TJvID3Encoding): Cardinal;
|
|
begin
|
|
case Encoding of
|
|
ienISO_8859_1, ienUTF_8:
|
|
Result := 1;
|
|
ienUTF_16, ienUTF_16BE:
|
|
Result := 2;
|
|
else
|
|
Result := 0;
|
|
ID3Error(RsEID3UnknownEncoding);
|
|
end;
|
|
end;
|
|
|
|
function CheckIsURL({%H-}Frame: TJvID3Frame; var {%H-}S: AnsiString;
|
|
const {%H-}HandleError: TJvID3HandleError): Boolean;
|
|
begin
|
|
{ Not implemented }
|
|
Result := True;
|
|
end;
|
|
|
|
function CheckIsLanguageA(Frame: TJvID3Frame; var S: AnsiString; const HandleError: TJvID3HandleError): Boolean;
|
|
begin
|
|
{ The three byte language field, present in several frames, is used to
|
|
describe the language of the frame's content, according to ISO-639-2
|
|
[ISO-639-2]. The language should be represented in lower case. If the
|
|
language is not known the string "XXX" should be used.
|
|
}
|
|
|
|
Result := (S = cUnknownLanguage) or ISO_639_2IsCode(S);
|
|
|
|
if not Result then
|
|
case HandleError of
|
|
heAutoCorrect:
|
|
{ Note, don't set Result to True }
|
|
S := cUnknownLanguage;
|
|
heRaise:
|
|
Frame.ErrorFmt(RsEID3InvalidLanguageValue, [S]);
|
|
else
|
|
Exit;
|
|
end
|
|
else
|
|
if HandleError = heAutoCorrect then
|
|
S := AnsiLowerCase(S);
|
|
end;
|
|
|
|
//function CheckIsID3Time(Frame: TJvID3Frame; var S: WideString; const HandleError: TJvID3HandleError): Boolean;
|
|
function CheckIsID3Time(Frame: TJvID3Frame; var S: String; const HandleError: TJvID3HandleError): Boolean;
|
|
var
|
|
I1, I2: Integer;
|
|
begin
|
|
{ S must be in HHMM format (H = Hour; M = Minute), and may not be empty }
|
|
Result := Length(S) = 4;
|
|
|
|
if Result then
|
|
begin
|
|
I1 := StrToIntDef(Copy(S, 1, 2), -1);
|
|
I2 := StrToIntDef(Copy(S, 3, 4), -1);
|
|
Result := (I1 >= 0) and (I1 < 24) and (I2 >= 0) and (I2 < 60);
|
|
end;
|
|
|
|
if not Result then
|
|
case HandleError of
|
|
heAutoCorrect:
|
|
{ Note, don't set Result to True }
|
|
S := '0000';
|
|
heRaise:
|
|
Frame.ErrorFmt(RsEID3InvalidTimeValue, [S]);
|
|
end;
|
|
end;
|
|
|
|
//function CheckIsID3Date(Frame: TJvID3Frame; var S: WideString; const HandleError: TJvID3HandleError): Boolean;
|
|
function CheckIsID3Date(Frame: TJvID3Frame; var S: String; const HandleError: TJvID3HandleError): Boolean;
|
|
var
|
|
I1, I2: Integer;
|
|
begin
|
|
{ S must be in DDMM format (D = Day; M = Month), and may not be empty }
|
|
Result := Length(S) = 4;
|
|
|
|
if Result then
|
|
begin
|
|
I1 := StrToIntDef(Copy(S, 1, 2), -1);
|
|
I2 := StrToIntDef(Copy(S, 3, 4), -1);
|
|
Result := (I1 >= 1) and (I1 < 32) and (I2 >= 1) and (I2 < 13);
|
|
end;
|
|
|
|
if not Result then
|
|
case HandleError of
|
|
heAutoCorrect:
|
|
{ Note, don't set Result to True }
|
|
S := '0101';
|
|
heRaise:
|
|
Frame.ErrorFmt(RsEID3InvalidDateValue, [S]);
|
|
end;
|
|
end;
|
|
|
|
//function CheckMaxCharCount(Frame: TJvID3Frame; var S: WideString;
|
|
function CheckMaxCharCount(Frame: TJvID3Frame; var S: String;
|
|
const MaxCharCount: Cardinal;
|
|
const HandleError: TJvID3HandleError): Boolean;
|
|
begin
|
|
//Result := CharCount(S) <= MaxCharCount;
|
|
Result := Cardinal(UTF8Length(S)) <= MaxCharCount;
|
|
if not Result then
|
|
case HandleError of
|
|
heAutoCorrect:
|
|
SetLength(S, MaxCharCount);
|
|
heRaise:
|
|
Frame.ErrorFmt(RsEID3StringTooLong, [S]);
|
|
end;
|
|
end;
|
|
|
|
{function GetID3Date(const S: WideString; const Encoding: TJvID3Encoding;
|
|
const Year: Word = 0): TDateTime; }
|
|
function GetID3Date(const S: String; const {%H-}Encoding: TJvID3Encoding;
|
|
const Year: Word = 0): TDateTime;
|
|
var
|
|
Day, Month: Word;
|
|
begin
|
|
{ must be DDMM }
|
|
if Length(S) = 4 then
|
|
begin
|
|
Day := StrToIntDef(Copy(S, 1, 2), 1);
|
|
Month := StrToIntDef(Copy(S, 3, 4), 1);
|
|
end
|
|
else
|
|
begin
|
|
Day := 1;
|
|
Month := 1;
|
|
end;
|
|
|
|
if not TryEncodeDate(Year, Month, Day, Result) then
|
|
Result := 0;
|
|
{
|
|
try
|
|
Result := EncodeDate(Year, Month, Day);
|
|
except
|
|
on EConvertError do
|
|
Result := 0;
|
|
end;
|
|
}
|
|
end;
|
|
|
|
function CheckIsLanguageList(Frame: TJvID3Frame; Strings: TStrings;
|
|
const HandleError: TJvID3HandleError): Boolean;
|
|
var
|
|
I: Integer;
|
|
Ok: Boolean;
|
|
S: String;
|
|
begin
|
|
Result := True;
|
|
for I := 0 to Strings.Count - 1 do
|
|
begin
|
|
S := Strings[I];
|
|
Ok := CheckIsLanguageA(Frame, S, HandleError);
|
|
Result := Result and Ok;
|
|
if not Ok then
|
|
if HandleError = heAutoCorrect then
|
|
Strings[I] := string(S)
|
|
else
|
|
Break;
|
|
end;
|
|
end;
|
|
|
|
function CheckList(Frame: TJvID3Frame; Strings: TStrings;
|
|
const ASeparator: WideChar; const HandleError: TJvID3HandleError): Boolean;
|
|
var
|
|
I: Integer;
|
|
S: string;
|
|
LPos: Integer;
|
|
begin
|
|
Result := True;
|
|
// if ASeparator = WideNull then
|
|
if ASeparator = #0 then
|
|
Exit;
|
|
|
|
for I := 0 to Strings.Count - 1 do
|
|
begin
|
|
S := Strings[I];
|
|
LPos := Pos(ASeparator, S);
|
|
Result := Result and (LPos = 0);
|
|
if LPos > 0 then
|
|
case HandleError of
|
|
heAutoCorrect:
|
|
begin
|
|
repeat
|
|
Delete(S, LPos, 1);
|
|
LPos := Pos(ASeparator, S);
|
|
until LPos = 0;
|
|
Strings[I] := S;
|
|
end;
|
|
heRaise:
|
|
Frame.ErrorFmt(RsEID3InvalidCharInList, [ASeparator, S]);
|
|
else
|
|
Break;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{function GetID3Time(const S: WideString; const Encoding: TJvID3Encoding;
|
|
const Sec: Word = 0; MSec: Word = 0): TDateTime; }
|
|
function GetID3Time(const S: String; const {%H-}Encoding: TJvID3Encoding;
|
|
const Sec: Word = 0; MSec: Word = 0): TDateTime;
|
|
var
|
|
Hour, Min: Word;
|
|
begin
|
|
{ must be HHMM }
|
|
if Length(S) = 4 then
|
|
begin
|
|
Hour := StrToIntDef(Copy(S, 1, 2), 0);
|
|
Min := StrToIntDef(Copy(S, 3, 4), 0);
|
|
end
|
|
else
|
|
begin
|
|
Hour := 0;
|
|
Min := 0;
|
|
end;
|
|
|
|
if not TryEncodetime(Hour, Min, Sec, MSec, Result) then
|
|
Result := 0;
|
|
{
|
|
try
|
|
Result := EncodeTime(Hour, Min, Sec, MSec);
|
|
except
|
|
on EConvertError do
|
|
Result := 0;
|
|
end;
|
|
}
|
|
end;
|
|
|
|
//function CheckIsID3PartInSet(Frame: TJvID3Frame; var S: WideString; const HandleError: TJvID3HandleError): Boolean;
|
|
function CheckIsID3PartInSet(Frame: TJvID3Frame; var S: String;
|
|
const HandleError: TJvID3HandleError): Boolean;
|
|
var
|
|
P: Integer;
|
|
I1, I2: Integer;
|
|
begin
|
|
{ S must be in N1/N2 or N format (N, N1, N2 = some number, ie [0..9]*,
|
|
but may be empty }
|
|
|
|
if S = '' then
|
|
begin
|
|
Result := True;
|
|
Exit;
|
|
end;
|
|
|
|
P := Pos('/', S);
|
|
if P > 1 then
|
|
begin
|
|
I1 := StrToIntDef(Copy(S, 1, P - 1), -1);
|
|
I2 := StrToIntDef(Copy(S, P + 1, MaxInt), -1);
|
|
Result := (I1 > -1) and (I2 > -1);
|
|
end
|
|
else
|
|
Result := StrToIntDef(S, -1) > -1;
|
|
|
|
if not Result then
|
|
case HandleError of
|
|
heAutoCorrect:
|
|
{ Note, don't set Result to True }
|
|
S := '';
|
|
heRaise:
|
|
Frame.ErrorFmt(RsEID3InvalidPartInSetValue, [S]);
|
|
end;
|
|
end;
|
|
|
|
|
|
{ Copied from DSDesign.pas }
|
|
|
|
function GenerateName(Controller: TJvID3Controller; FrameName: AnsiString;
|
|
FrameClass: TJvID3FrameClass; Number: Integer): string;
|
|
var
|
|
Fmt: string;
|
|
|
|
procedure CrunchFrameName;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
I := 1;
|
|
while I <= Length(FrameName) do
|
|
begin
|
|
if CharInSet(FrameName[I], IdentifierSymbols) then
|
|
Inc(I)
|
|
else
|
|
if CharInSet(FrameName[I], LeadBytes) then
|
|
Delete(FrameName, I, 2)
|
|
else
|
|
Delete(FrameName, I, 1);
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
CrunchFrameName;
|
|
if (FrameName = '') or CharInSet(FrameName[1], DigitSymbols) then
|
|
begin
|
|
if FrameClass <> nil then
|
|
FrameName := AnsiString(FrameClass.ClassName) + FrameName
|
|
else
|
|
FrameName := 'Frame' + FrameName;
|
|
if FrameName[1] = 'T' then
|
|
Delete(FrameName, 1, 1);
|
|
CrunchFrameName;
|
|
end;
|
|
Fmt := '%s%s%d';
|
|
if Number < 2 then
|
|
Fmt := '%s%s';
|
|
Result := Format(Fmt, [Controller.Name, FrameName, Number]);
|
|
end;
|
|
|
|
procedure SyncSafe(Source: Cardinal; var Dest; const DestSize: Integer); overload;
|
|
type
|
|
TBytes = array [0..MaxInt - 1] of Byte;
|
|
var
|
|
I: Byte;
|
|
begin
|
|
{ Test : Source = 255 -> Dest = $01 $80
|
|
Source = 256 -> Dest = $02 $00
|
|
Source = 257 -> Dest = $02 $01 etc. }
|
|
for I := DestSize - 1 downto 0 do
|
|
begin
|
|
TBytes(Dest)[I] := Source and $7F; // $7F = %01111111
|
|
Source := Source shr 7;
|
|
end;
|
|
end;
|
|
|
|
procedure SyncSafe(Source: Int64; var Dest; const DestSize: Integer); overload;
|
|
type
|
|
TBytes = array [0..MaxInt - 1] of Byte;
|
|
var
|
|
I: Byte;
|
|
begin
|
|
{ Test : Source = 255 -> Dest = $01 $80
|
|
Source = 256 -> Dest = $02 $00
|
|
Source = 257 -> Dest = $02 $01 etc. }
|
|
for I := DestSize - 1 downto 0 do
|
|
begin
|
|
TBytes(Dest)[I] := Source and $7F; // $7F = %01111111
|
|
Source := Source shr 7;
|
|
end;
|
|
end;
|
|
|
|
procedure UnSyncSafe(var Source; const SourceSize: Integer; var Dest: Cardinal); overload;
|
|
type
|
|
TBytes = array [0..MaxInt - 1] of Byte;
|
|
var
|
|
I: Byte;
|
|
begin
|
|
{ Test : Source = $01 $80 -> Dest = 255
|
|
Source = $02 $00 -> Dest = 256
|
|
Source = $02 $01 -> Dest = 257 etc. }
|
|
Dest := 0;
|
|
for I := 0 to SourceSize - 1 do
|
|
begin
|
|
Dest := Dest shl 7;
|
|
Dest := Dest or Cardinal(TBytes(Source)[I] and $7F); // $7F = %01111111
|
|
end;
|
|
end;
|
|
|
|
procedure UnSyncSafe(var Source; const SourceSize: Integer; out Dest: Int64); overload;
|
|
type
|
|
TBytes = array [0..MaxInt - 1] of Byte;
|
|
var
|
|
I: Byte;
|
|
begin
|
|
{ Test : Source = $01 $80 -> Dest = 255
|
|
Source = $02 $00 -> Dest = 256
|
|
Source = $02 $01 -> Dest = 257 etc. }
|
|
Dest := 0;
|
|
for I := 0 to SourceSize - 1 do
|
|
begin
|
|
Dest := Dest shl 7;
|
|
Dest := Dest or (TBytes(Source)[I] and $7F); // $7F = %01111111
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure ExtractFixedStrings(const Content: String; const ALength: Integer;
|
|
Strings: TStrings);
|
|
var
|
|
//P, ContentPtr: PWideChar;
|
|
//S: WideString;
|
|
P, ContentPtr: PChar;
|
|
S: String;
|
|
begin
|
|
//ContentPtr := PWideChar(Content);
|
|
ContentPtr := PChar(Content);
|
|
|
|
// if (ContentPtr = nil) or (ContentPtr^ = WideNull) or (Strings = nil) or (ALength < 1) then
|
|
// Exit;
|
|
if (ContentPtr = nil) or (ContentPtr^ = #0) or (Strings = nil) or (ALength < 1) then
|
|
Exit;
|
|
|
|
Strings.BeginUpdate;
|
|
try
|
|
SetLength(S, ALength);
|
|
|
|
while True do
|
|
begin
|
|
P := ContentPtr;
|
|
|
|
// while (P^ <> WideNull) and (P - ContentPtr < ALength) do
|
|
// Inc(P);
|
|
while (P^ <> #0) and (P - ContentPtr < ALength) do
|
|
Inc(P);
|
|
|
|
if P - ContentPtr = ALength then
|
|
begin
|
|
// Move(ContentPtr[0], S[1], ALength * SizeOf(WideChar));
|
|
Move(ContentPtr[0], S[1], ALength * SizeOf(Char));
|
|
Strings.Add(S);
|
|
end;
|
|
|
|
// if P^ = WideNull then
|
|
// Break;
|
|
if P^ = #0 then
|
|
Break;
|
|
|
|
Inc(ContentPtr, ALength);
|
|
end;
|
|
finally
|
|
Strings.EndUpdate;
|
|
end;
|
|
end;
|
|
|
|
procedure ExtractStrings(Separator: WideChar; const Content: String;
|
|
Strings: TStrings);
|
|
var
|
|
EOS: Boolean;
|
|
Tail: PChar;
|
|
S: String;
|
|
ContentPtr: PChar;
|
|
{
|
|
Tail: PWideChar;
|
|
S: WideString;
|
|
ContentPtr: PWideChar; }
|
|
begin
|
|
//ContentPtr := PWideChar(Content);
|
|
ContentPtr := PChar(Content);
|
|
//if (ContentPtr = nil) or (ContentPtr^ = WideNull) or (Strings = nil) then
|
|
if (ContentPtr = nil) or (ContentPtr^ = #0) or (Strings = nil) then
|
|
Exit;
|
|
|
|
Strings.BeginUpdate;
|
|
try
|
|
Tail := ContentPtr;
|
|
|
|
repeat
|
|
//while (Tail^ <> Separator) and (Tail^ <> WideNull) do
|
|
while (Tail^ <> Separator) and (Tail^ <> #0) do
|
|
Inc(Tail);
|
|
|
|
//EOS := Tail^ = WideNull;
|
|
EOS := (Tail^ = #0);
|
|
|
|
SetLength(S, Tail - ContentPtr);
|
|
//Move(ContentPtr[0], S[1], (Tail - ContentPtr) * SizeOf(WideChar));
|
|
Move(ContentPtr[0], S[1], (Tail - ContentPtr) * SizeOf(Char));
|
|
Strings.Add(S);
|
|
|
|
Inc(Tail);
|
|
ContentPtr := Tail;
|
|
until EOS;
|
|
finally
|
|
Strings.EndUpdate;
|
|
end;
|
|
end;
|
|
|
|
function GetTagSizeInclHeader(AStream: TStream): Cardinal;
|
|
var
|
|
Header: TID3v2HeaderRec;
|
|
begin
|
|
Result := 0;
|
|
if (AStream.Read(Header{%H-}, SizeOf(Header)) = SizeOf(Header)) and
|
|
(Header.Identifier = cID3HeaderId) then
|
|
begin
|
|
UnSyncSafe(Header.Size, 4, Result);
|
|
Inc(Result, 10);
|
|
end;
|
|
end;
|
|
|
|
procedure ChangeTagSize(const SourceFileName: string;
|
|
const DestTagSizeInclHeader: Cardinal);
|
|
var
|
|
DestFileName: string;
|
|
Source, Dest: TFileStream;
|
|
SourceFileSize: Int64;
|
|
SourceTagSizeInclHeader: Cardinal; { size of tag + header size (=10) }
|
|
begin
|
|
{ (rb) Maybe we should copy the file attributes of the source file to
|
|
the dest file? }
|
|
|
|
Source := TFileStream.Create(SourceFileName, fmOpenRead or fmShareExclusive);
|
|
try
|
|
SourceTagSizeInclHeader := GetTagSizeInclHeader(Source);
|
|
|
|
if SourceTagSizeInclHeader = DestTagSizeInclHeader then
|
|
Exit;
|
|
|
|
//DestFileName := JclFileUtils.FileGetTempName(cChangeTagSizeFileNameTemplate);
|
|
DestFileName := GetTempFileName('', cChangeTagSizeFileNameTemplate);
|
|
Dest := TFileStream.Create(DestFileName, fmCreate);
|
|
try
|
|
SourceFileSize := Source.Size;
|
|
Dest.Size := SourceFileSize + DestTagSizeInclHeader - SourceTagSizeInclHeader;
|
|
|
|
Source.Seek(SourceTagSizeInclHeader, soBeginning);
|
|
Dest.Seek(DestTagSizeInclHeader, soBeginning);
|
|
if SourceFileSize > SourceTagSizeInclHeader then
|
|
Dest.CopyFrom(Source, SourceFileSize - SourceTagSizeInclHeader);
|
|
finally
|
|
Dest.Free;
|
|
end;
|
|
finally
|
|
Source.Free;
|
|
end;
|
|
|
|
{ If all went alright, then we now try to copy the dest file to
|
|
the source file }
|
|
if not DeleteFile(PChar(SourceFileName)) then
|
|
RaiseLastOSError;
|
|
|
|
if not RenameFile(DestFileName, SourceFileName) then
|
|
RaiseLastOSError;
|
|
end;
|
|
|
|
function SearchSync(AStream: TStream;
|
|
const BeginOffset: Integer; var Buffer; const BufferSize: Integer): Int64;
|
|
const
|
|
CBufferSize = $0F00;
|
|
var
|
|
LBuffer: array[0..CBufferSize - 1] of Byte;
|
|
I: Integer;
|
|
LastWasFF: Boolean;
|
|
BytesRead: Longint;
|
|
begin
|
|
{ Seek sync point 11111111 111 }
|
|
LastWasFF := False;
|
|
Result := AStream.Seek(BeginOffset, soBeginning);
|
|
|
|
while True do
|
|
begin
|
|
BytesRead := AStream.Read(LBuffer{%H-}, CBufferSize);
|
|
if BytesRead = 0 then
|
|
begin
|
|
Result := -1;
|
|
Break;
|
|
end;
|
|
|
|
for I := 0 to BytesRead - 1 do
|
|
begin
|
|
if LastWasFF and (LBuffer[I] and $E0 = $E0) then
|
|
begin
|
|
Inc(Result, Int64(I) - 1);
|
|
if (I + BufferSize - 1 >= BytesRead) or (I = 0) then
|
|
begin
|
|
AStream.Seek(Result, soBeginning);
|
|
if not AStream.Read(Buffer, BufferSize) = BufferSize then
|
|
Result := -1;
|
|
end
|
|
else
|
|
Move(LBuffer[I - 1], Buffer, BufferSize);
|
|
|
|
Exit;
|
|
end;
|
|
|
|
LastWasFF := LBuffer[I] = $FF;
|
|
end;
|
|
Inc(Result, BytesRead);
|
|
end;
|
|
end;
|
|
|
|
function GetFrameIDLength(const Version: TJvID3Version): Byte;
|
|
begin
|
|
case Version of
|
|
ive2_2:
|
|
Result := 3;
|
|
ive2_3, ive2_4:
|
|
Result := 4;
|
|
else
|
|
Result := 0;
|
|
ID3Error(RsEID3UnknownVersion);
|
|
end;
|
|
end;
|
|
|
|
function MajorVersionToVersion(const MajorVersion: Byte): TJvID3Version;
|
|
begin
|
|
if MajorVersion < 2 then
|
|
Result := iveLowerThan2_2
|
|
else
|
|
if MajorVersion = 2 then
|
|
Result := ive2_2
|
|
else
|
|
if MajorVersion = 3 then
|
|
Result := ive2_3
|
|
else
|
|
if MajorVersion = 4 then
|
|
Result := ive2_4
|
|
else
|
|
Result := iveHigherThan2_4
|
|
end;
|
|
|
|
procedure RemoveUnsynchronisationScheme(Source, Dest: TStream; BytesToRead: Integer);
|
|
const
|
|
MaxBufSize = $F000;
|
|
var
|
|
LastWasFF: Boolean;
|
|
BytesRead: Integer;
|
|
SourcePtr, DestPtr: Integer;
|
|
SourceBuf, DestBuf: array[0..MaxBufSize - 1] of Byte;
|
|
begin
|
|
{ Replace $FF 00 with $FF }
|
|
|
|
LastWasFF := False;
|
|
while BytesToRead > 0 do
|
|
begin
|
|
{ Read at max CBufferSize bytes from the stream }
|
|
BytesRead := Source.Read({%H-}SourceBuf[0], Min(MaxBufSize, BytesToRead));
|
|
if BytesRead = 0 then
|
|
ID3Error(RsECouldNotReadData);
|
|
|
|
Dec(BytesToRead, BytesRead);
|
|
|
|
DestPtr := 0;
|
|
SourcePtr := 0;
|
|
|
|
while SourcePtr < BytesRead do
|
|
begin
|
|
{ If previous was $FF and current is $00 then skip.. }
|
|
if not LastWasFF or (SourceBuf[SourcePtr] <> $00) then
|
|
begin
|
|
{ ..otherwise copy }
|
|
DestBuf[DestPtr] := SourceBuf[SourcePtr];
|
|
Inc(DestPtr);
|
|
end;
|
|
|
|
LastWasFF := SourceBuf[SourcePtr] = $FF;
|
|
Inc(SourcePtr);
|
|
end;
|
|
Dest.Write(DestBuf[0], DestPtr);
|
|
end;
|
|
end;
|
|
|
|
procedure ApplyUnsynchronisationScheme(Source, Dest: TStream; BytesToRead: Integer);
|
|
const
|
|
MaxBufSize = $F000;
|
|
var
|
|
LastWasFF: Boolean;
|
|
BytesRead: Integer;
|
|
SourcePtr, DestPtr: Integer;
|
|
SourceBuf, DestBuf: PAnsiChar;
|
|
begin
|
|
{ Replace $FF 00 with $FF 00 00
|
|
Replace $FF %111xxxxx with $FF 00 %111xxxxx (%11100000 = $E0 = 224 }
|
|
|
|
GetMem(SourceBuf, Min(MaxBufSize div 2, BytesToRead));
|
|
GetMem(DestBuf, 2 * Min(MaxBufSize div 2, BytesToRead));
|
|
try
|
|
LastWasFF := False;
|
|
while BytesToRead > 0 do
|
|
begin
|
|
{ Read at max CBufferSize div 2 bytes from the stream }
|
|
BytesRead := Source.Read(SourceBuf^, Min(MaxBufSize div 2, BytesToRead));
|
|
if BytesRead = 0 then
|
|
ID3Error(RsECouldNotReadData);
|
|
|
|
Dec(BytesToRead, BytesRead);
|
|
|
|
DestPtr := 0;
|
|
SourcePtr := 0;
|
|
|
|
while SourcePtr < BytesRead do
|
|
begin
|
|
{ If previous was $FF and current is $00 or >=$E0 then add space.. }
|
|
if LastWasFF and
|
|
((SourceBuf[SourcePtr] = #$00) or (Byte(SourceBuf[SourcePtr]) and $E0 > 0)) then
|
|
begin
|
|
DestBuf[DestPtr] := #$00;
|
|
Inc(DestPtr);
|
|
end;
|
|
|
|
{ Copy }
|
|
DestBuf[DestPtr] := SourceBuf[SourcePtr];
|
|
Inc(DestPtr);
|
|
|
|
LastWasFF := SourceBuf[SourcePtr] = #$FF;
|
|
Inc(SourcePtr);
|
|
end;
|
|
Dest.Write(DestBuf^, DestPtr);
|
|
end;
|
|
finally
|
|
FreeMem(SourceBuf);
|
|
FreeMem(DestBuf);
|
|
end;
|
|
end;
|
|
|
|
|
|
//=== Global procedures ======================================================
|
|
|
|
{ Copied from DSDesign.pas }
|
|
|
|
function CreateUniqueName(AController: TJvID3Controller; const FrameName: AnsiString;
|
|
FrameClass: TJvID3FrameClass; Component: TComponent): string;
|
|
var
|
|
I: Integer;
|
|
|
|
function IsUnique(const AName: string): Boolean;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
Result := False;
|
|
with AController do
|
|
for I := 0 to ComponentCount - 1 do
|
|
if (Component <> Components[I]) and AnsiSameStr(AName, Components[I].Name) then
|
|
Exit;
|
|
Result := True;
|
|
end;
|
|
|
|
begin
|
|
for I := 1 to MaxInt do
|
|
begin
|
|
Result := GenerateName(AController, FrameName, FrameClass, I);
|
|
if IsUnique(Result) then
|
|
Break;
|
|
end;
|
|
end;
|
|
|
|
function ExtToMIMEType(const Ext: string): string;
|
|
begin
|
|
{ Not a very reliable method }
|
|
if AnsiSameText(Ext, '.jpeg') or AnsiSameText(Ext, '.jpg') then
|
|
Result := 'image/jpeg'
|
|
else if AnsiSameText(Ext, '.tiff') or AnsiSameText(Ext, '.tif') then
|
|
Result := 'image/tif'
|
|
else if AnsiSameText(Ext, '.bmp') then
|
|
Result := 'image/bitmap'
|
|
else if Ext = '' then
|
|
Result := 'image/'
|
|
else
|
|
{ .png, .gif, .jpg etc. }
|
|
Result := 'image/' + Copy(Ext, 2, MaxInt);
|
|
end;
|
|
|
|
{ References to the ID3v1 genres can be made by, as first byte, enter "("
|
|
followed by a number from the genres list (appendix A) and ended with a ")"
|
|
character. This is optionally followed by a refinement, e.g. "(21)" or
|
|
"(4)Eurodisco". Several references can be made in the same frame, e.g.
|
|
"(51)(39)". If the refinement should begin with a "(" character it should
|
|
be replaced with "((", e.g. "((I can figure out any genre)" or
|
|
"(55)((I think...)".
|
|
|
|
The following new content types is defined in ID3v2 and is implemented in
|
|
the same way as the numerig content types, e.g. "(RX)".
|
|
|
|
RX Remix
|
|
CR Cover }
|
|
function GenreToNiceGenre(const AGenre: string): string;
|
|
var
|
|
State: Integer;
|
|
Start: Integer;
|
|
I: Integer;
|
|
|
|
procedure GoState0;
|
|
begin
|
|
State := 0;
|
|
Start := I + 1;
|
|
end;
|
|
|
|
procedure AddString(const S: string);
|
|
begin
|
|
if Result > '' then
|
|
begin
|
|
if (S = '') or (S[1] = ' ') then
|
|
Result := Result + S
|
|
else
|
|
Result := Result + ' ' + S;
|
|
end
|
|
else
|
|
Result := S;
|
|
GoState0;
|
|
end;
|
|
|
|
procedure AddReference(const AReference: string);
|
|
var
|
|
iReference: Integer;
|
|
Genre: string;
|
|
begin
|
|
iReference := StrToIntDef(AReference, -1);
|
|
if iReference < 0 then
|
|
begin
|
|
State := -1;
|
|
Exit;
|
|
end;
|
|
|
|
Genre := ID3_IDToGenre(iReference);
|
|
if Genre = '' then
|
|
begin
|
|
State := -1;
|
|
Exit;
|
|
end;
|
|
|
|
AddString(ID3_IDToGenre(iReference));
|
|
GoState0;
|
|
end;
|
|
|
|
var
|
|
P: PChar;
|
|
begin
|
|
Result := '';
|
|
State := 0;
|
|
I := 1;
|
|
Start := I;
|
|
|
|
while (State >= 0) and (I <= Length(AGenre)) do
|
|
begin
|
|
case State of
|
|
0:
|
|
if AGenre[I] = '(' then
|
|
State := 1
|
|
else
|
|
State := -1;
|
|
1:
|
|
case AGenre[I] of
|
|
'(':
|
|
begin
|
|
Start := I;
|
|
State := -1;
|
|
end;
|
|
'0'..'9':
|
|
State := 2;
|
|
'R':
|
|
State := 3; // expect 'RX' = 'Remix'
|
|
'C':
|
|
State := 5; // expect 'CR' = 'Cover'
|
|
')':
|
|
GoState0;
|
|
else
|
|
State := -1;
|
|
end;
|
|
2:
|
|
case AGenre[I] of
|
|
'0'..'9':
|
|
;
|
|
')':
|
|
AddReference(Copy(AGenre, Start + 1, I - Start - 1));
|
|
else
|
|
State := -1;
|
|
end;
|
|
3:
|
|
if AGenre[I] = 'X' then
|
|
State := 4
|
|
else
|
|
State := -1;
|
|
4:
|
|
if AGenre[I] = ')' then
|
|
AddString('Remix')
|
|
else
|
|
State := -1;
|
|
5:
|
|
if AGenre[I] = 'R' then
|
|
State := 6
|
|
else
|
|
State := -1;
|
|
6:
|
|
if AGenre[I] = ')' then
|
|
AddString('Cover')
|
|
else
|
|
State := -1;
|
|
end;
|
|
Inc(I);
|
|
end;
|
|
|
|
if Start <= Length(AGenre) then
|
|
begin
|
|
{ Workaround for a bug in some taggers }
|
|
P := PChar(AGenre) + Start - 1;
|
|
while P^ = ' ' do
|
|
Inc(P);
|
|
if StrIComp(P, PChar(Result)) <> 0 then
|
|
AddString(Copy(AGenre, Start, MaxInt));
|
|
end;
|
|
end;
|
|
|
|
procedure GetID3v2Version(const AFileName: string; var HasTag: Boolean;
|
|
out Version: TJvID3Version);
|
|
var
|
|
Header: TID3v2HeaderRec;
|
|
begin
|
|
with TFileStream.Create(AFileName, fmOpenRead or fmShareDenyWrite) do
|
|
try
|
|
HasTag := (Read(Header{%H-}, SizeOf(Header)) = SizeOf(Header)) and
|
|
(Header.Identifier = cID3HeaderId);
|
|
if not HasTag then
|
|
Exit;
|
|
|
|
Version := MajorVersionToVersion(Header.MajorVersion);
|
|
finally
|
|
Free;
|
|
end;
|
|
end;
|
|
|
|
procedure ID3Error(const Msg: string; Component: TComponent = nil);
|
|
begin
|
|
if Assigned(Component) and (Component.Name <> '') then
|
|
raise EJvID3Error.CreateResFmt(@RsENameMsgFormat, [Component.Name, Msg])
|
|
else
|
|
raise EJvID3Error.Create(Msg);
|
|
end;
|
|
|
|
procedure ID3ErrorFmt(const Msg: string; const Args: array of const;
|
|
Component: TComponent = nil);
|
|
begin
|
|
ID3Error(Format(Msg, Args), Component);
|
|
end;
|
|
|
|
{ Not a very reliable method; maybe use Indy's TIdMimeTable
|
|
in IdGlobal.pas
|
|
|
|
See: ftp://ftp.isi.edu/in-notes/iana/assignments/media-types/media-types
|
|
|
|
image/jpeg .jpg preferred supported
|
|
image/png .png preferred
|
|
image/gif .gif
|
|
image/tiff .tif
|
|
image/x-pict .pic
|
|
image/bitmap .bmp supported }
|
|
function MIMETypeToExt(const MIMEType: string): string;
|
|
begin
|
|
Result := Copy(MIMEType, Pos('/', MIMEType) + 1, MaxInt);
|
|
|
|
Result := AnsiLowerCase(Result);
|
|
if Result = 'jpeg' then
|
|
Result := '.jpg'
|
|
else
|
|
if Result = 'x-png' then
|
|
Result := '.png'
|
|
else
|
|
if (Result = 'bitmap') or (Result = 'x-ms-bmp') then
|
|
Result := '.bmp'
|
|
else
|
|
if Result = 'tiff' then
|
|
Result := '.tif'
|
|
else
|
|
if Result = 'x-pict' then
|
|
Result := '.pic'
|
|
else
|
|
Result := '.' + Result;
|
|
end;
|
|
|
|
function NiceGenreToGenre(const ANiceGenre: string): string;
|
|
var
|
|
S: string;
|
|
|
|
function IsPrefix(const APrefix: string): Boolean;
|
|
var
|
|
C: Integer;
|
|
begin
|
|
C := Length(APrefix);
|
|
Result := ((C = Length(S)) or ((C < Length(S)) and (S[C + 1] = ' '))) and
|
|
(StrLIComp(PChar(S), PChar(APrefix), C) = 0);
|
|
end;
|
|
|
|
procedure AddAndDelete(const Add: string; const DelCount: Integer);
|
|
begin
|
|
Result := Result + Add;
|
|
Delete(S, 1, DelCount);
|
|
while (S > '') and (S[1] = ' ') do
|
|
Delete(S, 1, 1);
|
|
end;
|
|
|
|
var
|
|
GenreID: Integer;
|
|
begin
|
|
Result := '';
|
|
S := ANiceGenre;
|
|
while S > '' do
|
|
begin
|
|
GenreID := ID3_LongGenreToID(S);
|
|
if GenreID <> 255 then
|
|
AddAndDelete(Format('(%d)', [GenreID]), Length(ID3_IDToGenre(GenreID)))
|
|
else
|
|
{ Specials }
|
|
if IsPrefix('remix') then
|
|
AddAndDelete('(RX)', 5)
|
|
else
|
|
if IsPrefix('cover') then
|
|
AddAndDelete('(CR)', 5)
|
|
else
|
|
Break;
|
|
end;
|
|
|
|
if S > '' then
|
|
begin
|
|
if S[1] = '(' then
|
|
Result := Result + '(' + S
|
|
else
|
|
Result := Result + S;
|
|
end;
|
|
end;
|
|
|
|
|
|
//=== { TJvID3AudioEncryptionFrame } =========================================
|
|
|
|
procedure TJvID3AudioEncryptionFrame.Assign(Source: TPersistent);
|
|
begin
|
|
if Source is TJvID3AudioEncryptionFrame then
|
|
begin
|
|
FOwnerID := TJvID3AudioEncryptionFrame(Source).FOwnerID;
|
|
FPreviewStart := TJvID3AudioEncryptionFrame(Source).FPreviewStart;
|
|
FPreviewLength := TJvID3AudioEncryptionFrame(Source).FPreviewLength;
|
|
end;
|
|
inherited Assign(Source);
|
|
end;
|
|
|
|
class function TJvID3AudioEncryptionFrame.CanAddFrame(
|
|
AController: TJvID3Controller; AFrameID: TJvID3FrameID): Boolean;
|
|
begin
|
|
{ There may be more than one "AENC" frames in a tag, but only one with the
|
|
same 'Owner identifier' }
|
|
Result := (AFrameID = fiAudioCrypto) or
|
|
inherited CanAddFrame(AController, AFrameID);
|
|
end;
|
|
|
|
function TJvID3AudioEncryptionFrame.CheckFrame(
|
|
const HandleError: TJvID3HandleError): Boolean;
|
|
begin
|
|
Result := CheckIsURL(Self, FOwnerID, HandleError);
|
|
|
|
{ If something has changed update the framesize }
|
|
if not Result and (HandleError = heAutoCorrect) then
|
|
begin
|
|
UpdateFrameSize;
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3AudioEncryptionFrame.Clear;
|
|
begin
|
|
FOwnerID := '';
|
|
FPreviewStart := 0;
|
|
FPreviewLength := 0;
|
|
inherited Clear;
|
|
end;
|
|
|
|
class function TJvID3AudioEncryptionFrame.Find(AController: TJvID3Controller;
|
|
const AOwnerID: AnsiString): TJvID3AudioEncryptionFrame;
|
|
var
|
|
Frame: TJvID3Frame = nil;
|
|
begin
|
|
Result := nil;
|
|
if not Assigned(AController) or not AController.Active then
|
|
Exit;
|
|
|
|
if not AController.FindFirstFrame(fiAudioCrypto, Frame) then
|
|
Exit;
|
|
|
|
while (Frame is TJvID3AudioEncryptionFrame) and
|
|
(TJvID3AudioEncryptionFrame(Frame).OwnerID <> AOwnerID) do
|
|
AController.FindNextFrame(fiAudioCrypto, Frame);
|
|
|
|
if Frame is TJvID3AudioEncryptionFrame then
|
|
Result := TJvID3AudioEncryptionFrame(Frame)
|
|
end;
|
|
|
|
class function TJvID3AudioEncryptionFrame.FindOrCreate(AController: TJvID3Controller;
|
|
const AOwnerID: AnsiString): TJvID3AudioEncryptionFrame;
|
|
begin
|
|
if not Assigned(AController) then
|
|
ID3Error(RsEID3NoController);
|
|
|
|
Result := Find(AController, AOwnerID);
|
|
if not Assigned(Result) then
|
|
begin
|
|
Result := TJvID3AudioEncryptionFrame(AController.AddFrame(fiAudioCrypto));
|
|
Result.OwnerID := AOwnerID;
|
|
end;
|
|
end;
|
|
|
|
{ Owner identifier <text string> $00
|
|
Preview start $xx xx
|
|
Preview length $xx xx
|
|
Encryption info <binary data> }
|
|
function TJvID3AudioEncryptionFrame.GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal;
|
|
begin
|
|
Result := Cardinal(Length(FOwnerID)) + 1 + 2 + 2 + DataSize;
|
|
end;
|
|
|
|
function TJvID3AudioEncryptionFrame.GetIsEmpty: Boolean;
|
|
begin
|
|
Result := inherited GetIsEmpty and (Length(FOwnerID) = 0) and
|
|
(FPreviewStart = 0) and (FPreviewLength = 0);
|
|
end;
|
|
|
|
{ Owner identifier <text string> $00
|
|
Preview start $xx xx
|
|
Preview length $xx xx
|
|
Encryption info <binary data> }
|
|
procedure TJvID3AudioEncryptionFrame.ReadFrame;
|
|
begin
|
|
with Stream do
|
|
begin
|
|
ReadStringA(FOwnerID);
|
|
|
|
if not CanRead(4) then
|
|
Exit;
|
|
|
|
Read(FPreviewStart, 2);
|
|
FPreviewStart := ReverseBytes(FPreviewStart);
|
|
|
|
Read(FPreviewLength, 2);
|
|
FPreviewLength := ReverseBytes(FPreviewLength);
|
|
end;
|
|
ReadData(Stream.BytesTillEndOfFrame);
|
|
end;
|
|
|
|
function TJvID3AudioEncryptionFrame.SameUniqueIDAs(
|
|
const Frame: TJvID3Frame): Boolean;
|
|
begin
|
|
{ There may be more than one "AENC" frames in a tag, but only one with the
|
|
same 'Owner identifier' }
|
|
Result := (Frame is TJvID3AudioEncryptionFrame) and
|
|
(Frame.FrameID = FrameID) and (FrameID = fiAudioCrypto);
|
|
|
|
if Result then
|
|
Result := AnsiSameStr(TJvID3AudioEncryptionFrame(Frame).OwnerID, OwnerID)
|
|
else
|
|
Result := inherited SameUniqueIDAs(Frame);
|
|
end;
|
|
|
|
procedure TJvID3AudioEncryptionFrame.SetOwnerID(const Value: AnsiString);
|
|
begin
|
|
if FOwnerID <> Value then
|
|
begin
|
|
FOwnerID := Value;
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3AudioEncryptionFrame.SetPreviewLength(const Value: Word);
|
|
begin
|
|
if FPreviewLength <> Value then
|
|
begin
|
|
FPreviewLength := Value;
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3AudioEncryptionFrame.SetPreviewStart(const Value: Word);
|
|
begin
|
|
if FPreviewStart <> Value then
|
|
begin
|
|
FPreviewStart := Value;
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3AudioEncryptionFrame.WriteFrame;
|
|
var
|
|
TempWord: Word;
|
|
begin
|
|
{ Owner identifier <text string> $00
|
|
Preview start $xx xx
|
|
Preview length $xx xx
|
|
Encryption info <binary data>
|
|
}
|
|
with Stream do
|
|
begin
|
|
WriteStringA(OwnerID);
|
|
WriteTerminatorA;
|
|
|
|
TempWord := ReverseBytes(PreviewStart);
|
|
Write(TempWord, 2);
|
|
|
|
TempWord := ReverseBytes(PreviewLength);
|
|
Write(TempWord, 2);
|
|
end;
|
|
WriteData;
|
|
end;
|
|
|
|
|
|
//=== { TJvID3Base } =========================================================
|
|
|
|
constructor TJvID3Base.Create(AController: TJvID3Controller);
|
|
begin
|
|
inherited Create;
|
|
FController := AController;
|
|
end;
|
|
|
|
procedure TJvID3Base.AfterConstruction;
|
|
begin
|
|
inherited AfterConstruction;
|
|
Reset;
|
|
end;
|
|
|
|
procedure TJvID3Base.Assign(Source: TPersistent);
|
|
begin
|
|
if not Assigned(Source) then
|
|
Reset
|
|
else
|
|
inherited Assign(Source);
|
|
end;
|
|
|
|
function TJvID3Base.GetStream: TJvID3Stream;
|
|
begin
|
|
if not Assigned(FController) then
|
|
ID3Error(RsEID3NoController);
|
|
|
|
if icsUsingTempStream in FController.FState then
|
|
Result := FController.FTempStream
|
|
else
|
|
Result := FController.FStream;
|
|
end;
|
|
|
|
|
|
//=== { TJvID3BinaryFrame } ==================================================
|
|
|
|
procedure TJvID3BinaryFrame.AfterConstruction;
|
|
begin
|
|
inherited AfterConstruction;
|
|
FData := nil;
|
|
FDataSize := 0;
|
|
end;
|
|
|
|
procedure TJvID3BinaryFrame.Assign(Source: TPersistent);
|
|
begin
|
|
if Source is TJvID3BinaryFrame then
|
|
SetData(TJvID3BinaryFrame(Source).FData, TJvID3BinaryFrame(Source).DataSize);
|
|
|
|
inherited Assign(Source);
|
|
end;
|
|
|
|
procedure TJvID3BinaryFrame.BeforeDestruction;
|
|
begin
|
|
inherited BeforeDestruction;
|
|
FreeMem(FData);
|
|
end;
|
|
|
|
class function TJvID3BinaryFrame.CanAddFrame(AController: TJvID3Controller;
|
|
AFrameID: TJvID3FrameID): Boolean;
|
|
begin
|
|
{ There may only be one 'MCDI' frame in each tag. }
|
|
Result := ((AFrameID = fiCDID) and not AController.HasFrame(fiCDID)) or
|
|
(AFrameID <> fiCDID) or inherited CanAddFrame(AController, AFrameID);
|
|
end;
|
|
|
|
function TJvID3BinaryFrame.CheckFrame(const HandleError: TJvID3HandleError): Boolean;
|
|
begin
|
|
Result := True;
|
|
end;
|
|
|
|
procedure TJvID3BinaryFrame.Clear;
|
|
begin
|
|
SetData(nil, 0);
|
|
inherited Clear;
|
|
end;
|
|
|
|
class function TJvID3BinaryFrame.Find(AController: TJvID3Controller;
|
|
const AFrameID: TJvID3FrameID): TJvID3BinaryFrame;
|
|
var
|
|
Frame: TJvID3Frame;
|
|
begin
|
|
Result := nil;
|
|
if not Assigned(AController) or not AController.Active then
|
|
Exit;
|
|
|
|
Frame := AController.Frames.FindFrame(AFrameID);
|
|
if Frame is TJvID3BinaryFrame then
|
|
Result := TJvID3BinaryFrame(Frame);
|
|
end;
|
|
|
|
class function TJvID3BinaryFrame.FindOrCreate(AController: TJvID3Controller;
|
|
const AFrameID: TJvID3FrameID): TJvID3BinaryFrame;
|
|
begin
|
|
if not Assigned(AController) then
|
|
ID3Error(RsEID3NoController);
|
|
|
|
Result := Find(AController, AFrameID);
|
|
if not Assigned(Result) then
|
|
begin
|
|
AController.CheckFrameClass(TJvID3BinaryFrame, AFrameID);
|
|
Result := TJvID3BinaryFrame(AController.AddFrame(AFrameID));
|
|
end;
|
|
end;
|
|
|
|
function TJvID3BinaryFrame.GetData(P: Pointer; const Size: Cardinal): Boolean;
|
|
var
|
|
CopySize: Cardinal;
|
|
begin
|
|
Result := Assigned(P);
|
|
if not Result then
|
|
Exit;
|
|
|
|
CopySize := Min(Size, DataSize);
|
|
if (CopySize > 0) and Assigned(FData) then
|
|
Move(FData^, P^, CopySize);
|
|
end;
|
|
|
|
function TJvID3BinaryFrame.GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal;
|
|
begin
|
|
Result := FDataSize;
|
|
end;
|
|
|
|
function TJvID3BinaryFrame.GetIsEmpty: Boolean;
|
|
begin
|
|
Result := DataSize = 0;
|
|
end;
|
|
|
|
procedure TJvID3BinaryFrame.LoadFromFile(const AFileName: string);
|
|
var
|
|
lStream: TStream;
|
|
begin
|
|
lStream := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyWrite);
|
|
try
|
|
LoadFromStream(lStream);
|
|
finally
|
|
lStream.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3BinaryFrame.LoadFromStream(AStream: TStream);
|
|
begin
|
|
AStream.Position := 0;
|
|
FDataSize := AStream.Size;
|
|
ReallocMem(FData, FDataSize);
|
|
if Assigned(FData) then
|
|
AStream.Read(FData^, FDataSize);
|
|
Changed;
|
|
end;
|
|
|
|
procedure TJvID3BinaryFrame.ReadData(ASize: Cardinal);
|
|
begin
|
|
{if ASize < 0 then
|
|
ASize := 0;}
|
|
|
|
FDataSize := ASize;
|
|
ReallocMem(FData, FDataSize);
|
|
|
|
if Assigned(FData) and (FDataSize > 0) then
|
|
with Stream do
|
|
Read(FData^, FDataSize);
|
|
end;
|
|
|
|
procedure TJvID3BinaryFrame.ReadFrame;
|
|
begin
|
|
ReadData(FFrameSize);
|
|
end;
|
|
|
|
function TJvID3BinaryFrame.SameUniqueIDAs(const Frame: TJvID3Frame): Boolean;
|
|
begin
|
|
{ There may only be one 'MCDI' frame in each tag. }
|
|
Result := (Assigned(Frame) and (Frame.FrameID = FrameID) and
|
|
(FrameID = fiCDID)) or inherited SameUniqueIDAs(Frame);
|
|
end;
|
|
|
|
procedure TJvID3BinaryFrame.SaveToFile(const AFileName: string);
|
|
var
|
|
lStream: TStream;
|
|
begin
|
|
lStream := TFileStream.Create(AFileName, fmCreate);
|
|
try
|
|
SaveToStream(lStream);
|
|
finally
|
|
lStream.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3BinaryFrame.SaveToStream(AStream: TStream);
|
|
begin
|
|
if (DataSize > 0) and Assigned(FData) then
|
|
AStream.Write(FData^, DataSize)
|
|
end;
|
|
|
|
function TJvID3BinaryFrame.SetData(P: Pointer; const Size: Cardinal): Boolean;
|
|
begin
|
|
Result := Assigned(P) or (Size = 0);
|
|
if not Result then
|
|
Exit;
|
|
|
|
ReallocMem(FData, Size);
|
|
FDataSize := Size;
|
|
if Assigned(FData) and Assigned(P) then
|
|
Move(P^, FData^, FDataSize);
|
|
Changed;
|
|
end;
|
|
|
|
procedure TJvID3BinaryFrame.WriteData;
|
|
begin
|
|
if Assigned(FData) then
|
|
with Stream do
|
|
Write(FData^, DataSize);
|
|
end;
|
|
|
|
procedure TJvID3BinaryFrame.WriteFrame;
|
|
begin
|
|
WriteData;
|
|
end;
|
|
|
|
|
|
//=== { TJvID3ContentFrame } =================================================
|
|
|
|
procedure TJvID3ContentFrame.Assign(Source: TPersistent);
|
|
var
|
|
Src: TJvID3ContentFrame;
|
|
begin
|
|
if Source is TJvID3ContentFrame then
|
|
begin
|
|
Src := TJvID3ContentFrame(Source);
|
|
|
|
FLanguage := Src.Language;
|
|
FText := Src.Text;
|
|
FDescription := Src.Description;
|
|
end;
|
|
|
|
inherited Assign(Source);
|
|
end;
|
|
|
|
class function TJvID3ContentFrame.CanAddFrame(AController: TJvID3Controller;
|
|
AFrameID: TJvID3FrameID): Boolean;
|
|
begin
|
|
{ There may be more than one comment frame in each tag, but only one with
|
|
the same language and content descriptor.
|
|
There may be more than one 'Unsynchronised lyrics/text transcription' frame
|
|
in each tag, but only one with the same language and content descriptor.
|
|
}
|
|
Result := (AFrameID in [fiComment, fiUnsyncedLyrics]) or
|
|
inherited CanAddFrame(AController, AFrameID);
|
|
end;
|
|
|
|
function TJvID3ContentFrame.CheckFrame(const HandleError: TJvID3HandleError): Boolean;
|
|
begin
|
|
Result := CheckIsLanguageA(Self, FLanguage, HandleError);
|
|
|
|
{ If something has changed update the framesize }
|
|
if not Result and (HandleError = heAutoCorrect) then
|
|
begin
|
|
UpdateFrameSize;
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3ContentFrame.Clear;
|
|
begin
|
|
FLanguage := '';
|
|
FText := '';
|
|
FDescription := '';
|
|
|
|
inherited Clear;
|
|
end;
|
|
|
|
class function TJvID3ContentFrame.Find(AController: TJvID3Controller;
|
|
const AFrameID: TJvID3FrameID): TJvID3ContentFrame;
|
|
var
|
|
Frame: TJvID3Frame;
|
|
begin
|
|
Result := nil;
|
|
if not Assigned(AController) or not AController.Active then
|
|
Exit;
|
|
|
|
Frame := AController.Frames.FindFrame(AFrameID);
|
|
if Frame is TJvID3ContentFrame then
|
|
Result := TJvID3ContentFrame(Frame);
|
|
end;
|
|
|
|
class function TJvID3ContentFrame.FindOrCreate(AController: TJvID3Controller;
|
|
const AFrameID: TJvID3FrameID): TJvID3ContentFrame;
|
|
begin
|
|
if not Assigned(AController) then
|
|
ID3Error(RsEID3NoController);
|
|
|
|
Result := Find(AController, AFrameID);
|
|
if not Assigned(Result) then
|
|
begin
|
|
AController.CheckFrameClass(TJvID3ContentFrame, AFrameID);
|
|
Result := TJvID3ContentFrame(AController.AddFrame(AFrameID));
|
|
end;
|
|
end;
|
|
|
|
function TJvID3ContentFrame.GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal;
|
|
begin
|
|
{ Text encoding $xx
|
|
Language $xx xx xx
|
|
Short content descrip. <text string according to encoding> $00 (00)
|
|
The actual text <full text string according to encoding>
|
|
}
|
|
Result := 1 + 3 +
|
|
LengthEnc(Description, ToEncoding) +
|
|
LengthTerminatorEnc(ToEncoding) +
|
|
LengthEnc(Text, ToEncoding);
|
|
end;
|
|
|
|
function TJvID3ContentFrame.GetIsEmpty: Boolean;
|
|
begin
|
|
Result := ((Length(FLanguage) = 0) or (FLanguage = cUnknownLanguage)) and
|
|
(Text = '') and (Description = '');
|
|
end;
|
|
|
|
//function HasNonISO_8859_1Chars(const S: WideString): Boolean;
|
|
function HasNonISO_8859_1Chars(const S: String): Boolean;
|
|
var
|
|
I: Integer;
|
|
ws: WideString;
|
|
begin
|
|
ws := UTF8Decode(S);
|
|
for I := 1 to Length(ws) do
|
|
if Ord(ws[I]) > $FF then
|
|
begin
|
|
Result := True;
|
|
Exit;
|
|
end;
|
|
Result := False;
|
|
end;
|
|
|
|
function TJvID3ContentFrame.MustWriteAsUTF: Boolean;
|
|
begin
|
|
Result := HasNonISO_8859_1Chars(Description) or HasNonISO_8859_1Chars(Text);
|
|
end;
|
|
|
|
procedure TJvID3ContentFrame.ReadFrame;
|
|
begin
|
|
{ Text encoding $xx
|
|
Language $xx xx xx
|
|
Short content descrip. <text string according to encoding> $00 (00)
|
|
The actual text <full text string according to encoding>
|
|
}
|
|
|
|
with Stream do
|
|
begin
|
|
ReadEncoding;
|
|
ReadLanguage(FLanguage);
|
|
ReadStringEnc(FDescription);
|
|
ReadStringEnc(FText);
|
|
end;
|
|
end;
|
|
|
|
function TJvID3ContentFrame.SameUniqueIDAs(const Frame: TJvID3Frame): Boolean;
|
|
begin
|
|
{ There may be more than one comment frame in each tag, but only one with
|
|
the same language and content descriptor.
|
|
There may be more than one 'Unsynchronised lyrics/text transcription' frame
|
|
in each tag, but only one with the same language and content descriptor.
|
|
}
|
|
Result := (Frame is TJvID3ContentFrame) and
|
|
(Frame.FrameID = FrameID) and (FrameID in [fiComment, fiUnsyncedLyrics]);
|
|
|
|
if Result then
|
|
Result :=
|
|
AnsiSameStr(TJvID3ContentFrame(Frame).Language, Self.Language) and
|
|
(TJvID3ContentFrame(Frame).Description = Self.Description)
|
|
else
|
|
Result := inherited SameUniqueIDAs(Frame);
|
|
end;
|
|
|
|
//procedure TJvID3ContentFrame.SetDescription(const Value: WideString);
|
|
procedure TJvID3ContentFrame.SetDescription(const Value: String);
|
|
begin
|
|
if Value <> FDescription then
|
|
begin
|
|
FDescription := Value;
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3ContentFrame.SetLanguage(const Value: AnsiString);
|
|
begin
|
|
if FLanguage <> Value then
|
|
begin
|
|
FLanguage := Value;
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
//procedure TJvID3ContentFrame.SetText(const Value: WideString);
|
|
procedure TJvID3ContentFrame.SetText(const Value: String);
|
|
begin
|
|
if Value <> FText then
|
|
begin
|
|
FText := Value;
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3ContentFrame.WriteFrame;
|
|
begin
|
|
{ Text encoding $xx
|
|
Language $xx xx xx
|
|
Short content descrip. <text string according to encoding> $00 (00)
|
|
The actual text <full text string according to encoding>
|
|
}
|
|
|
|
with Stream do
|
|
begin
|
|
WriteEncoding;
|
|
WriteLanguage(Language);
|
|
WriteStringEnc(Description);
|
|
WriteTerminatorEnc;
|
|
WriteStringEnc(Text);
|
|
end;
|
|
end;
|
|
|
|
|
|
//=== { TJvID3Controller } ===================================================
|
|
|
|
constructor TJvID3Controller.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
|
|
FFrames := TJvID3Frames.Create(Self);
|
|
FHeader := TJvID3Header.Create(Self);
|
|
FExtendedHeader := TJvID3ExtendedHeader.Create(Self);
|
|
FFileInfo := TJvID3FileInfo.Create;
|
|
|
|
FActivateEvents := TList.Create;
|
|
FClients := TList.Create;
|
|
FState := [];
|
|
|
|
{ Defaults }
|
|
FReadEncodingAs := ifeAuto;
|
|
FWriteEncodingAs := ifeAuto;
|
|
FReadVersionAs := ifvDontCare;
|
|
FWriteVersionAs := ifvDontCare;
|
|
FOptions := [coAutoCorrect, coRemoveEmptyFrames];
|
|
end;
|
|
|
|
destructor TJvID3Controller.Destroy;
|
|
begin
|
|
SetActive(False);
|
|
|
|
inherited Destroy;
|
|
|
|
FreeAndNil(FActivateEvents);
|
|
FreeAndNil(FClients);
|
|
|
|
FDesigner.Free;
|
|
FDesigner := nil;
|
|
|
|
FreeAndNil(FFrames);
|
|
FHeader.Free;
|
|
FExtendedHeader.Free;
|
|
FFileInfo.Free;
|
|
FStream.Free;
|
|
end;
|
|
|
|
function TJvID3Controller.AddFrame(const AFrameID: TJvID3FrameID): TJvID3Frame;
|
|
var
|
|
FrameClass: TJvID3FrameClass;
|
|
begin
|
|
if not Active and not (icsReading in FState) then
|
|
ID3Error(RsEID3ControllerNotActive, Self);
|
|
|
|
FrameClass := GetFrameClass(AFrameID);
|
|
|
|
Result := FrameClass.Create(Self, AFrameID);
|
|
try
|
|
Result.Name := CreateUniqueName(Self, Result.FrameName, FrameClass, Result);
|
|
Result.Controller := Self;
|
|
except
|
|
Result.Free;
|
|
{ Suppress errors while reading }
|
|
if not (icsReading in FState) then
|
|
raise;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3Controller.ApplyUnsynchronisationSchemeOnCurrentStream;
|
|
var
|
|
TmpStream: TMemoryStream;
|
|
LTempStreamSize: Cardinal;
|
|
begin
|
|
TmpStream := TMemoryStream.Create;
|
|
try
|
|
if icsUsingTempStream in FState then
|
|
begin
|
|
if not Assigned(FTempStream) then
|
|
ID3Error(RsENoTempStream, Self);
|
|
|
|
LTempStreamSize := GetTempStreamSize;
|
|
FTempStream.Seek(0, soBeginning);
|
|
ApplyUnsynchronisationScheme(FTempStream, TmpStream, LTempStreamSize);
|
|
TmpStream.Seek(0, soBeginning);
|
|
FTempStream.Seek(0, soBeginning);
|
|
FTempStream.CopyFrom(TmpStream, TmpStream.Size);
|
|
end
|
|
else
|
|
begin
|
|
{ Exclude header (size=10) from the unsynchronisation }
|
|
FStream.Seek(10, soBeginning);
|
|
ApplyUnsynchronisationScheme(FStream, TmpStream, FStream.Size - 10);
|
|
TmpStream.Seek(0, soBeginning);
|
|
FStream.Seek(10, soBeginning);
|
|
FStream.CopyFrom(TmpStream, TmpStream.Size);
|
|
end;
|
|
finally
|
|
TmpStream.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3Controller.BeginReading;
|
|
begin
|
|
if FState <> [] then
|
|
ID3Error(RsEAlreadyReadingWriting, Self);
|
|
|
|
Include(FState, icsReading);
|
|
FStream := TJvID3Stream.Create;
|
|
|
|
BeginUpdate;
|
|
end;
|
|
|
|
procedure TJvID3Controller.BeginUpdate;
|
|
begin
|
|
Inc(FUpdateCount);
|
|
end;
|
|
|
|
procedure TJvID3Controller.BeginUseTempStream;
|
|
begin
|
|
if icsUsingTempStream in FState then
|
|
ID3Error(RsEAlreadyUsingTempStream, Self);
|
|
|
|
Include(FState, icsUsingTempStream);
|
|
if not Assigned(FTempStream) then
|
|
FTempStream := TJvID3Stream.Create;
|
|
|
|
FTempStream.Seek(0, soBeginning);
|
|
|
|
{ Init FTempStream as FStream }
|
|
FTempStream.FSourceEncoding := FStream.FSourceEncoding;
|
|
FTempStream.FDestEncoding := FStream.FDestEncoding;
|
|
FTempStream.FAllowedEncodings := FStream.FAllowedEncodings;
|
|
end;
|
|
|
|
procedure TJvID3Controller.BeginWriting;
|
|
begin
|
|
if FState <> [] then
|
|
ID3Error(RsEAlreadyReadingWriting, Self);
|
|
|
|
Include(FState, icsWriting);
|
|
FStream := TJvID3Stream.Create;
|
|
|
|
BeginUpdate;
|
|
end;
|
|
|
|
function TJvID3Controller.CanAddFrame(const AFrameID: TJvID3FrameID): Boolean;
|
|
var
|
|
FrameClass: TJvID3FrameClass;
|
|
begin
|
|
{ While reading we can always add all kinds of frames, ie we accept that the
|
|
stream may contain errors }
|
|
if icsReading in FState then
|
|
begin
|
|
Result := True;
|
|
Exit;
|
|
end;
|
|
|
|
FrameClass := GetFrameClass(AFrameID);
|
|
if Assigned(FrameClass) then
|
|
Result := FrameClass.CanAddFrame(Self, AFrameID)
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
procedure TJvID3Controller.ChangeToVersion(const ANewVersion: TJvID3Version);
|
|
begin
|
|
Frames.ChangeToVersion(ANewVersion);
|
|
Header.ChangeToVersion(ANewVersion);
|
|
ExtendedHeader.ChangeToVersion(ANewVersion);
|
|
end;
|
|
|
|
procedure TJvID3Controller.CheckFrameClass(FrameClass: TJvID3FrameClass;
|
|
const AFrameID: TJvID3FrameID);
|
|
var
|
|
LFrameClass: string;
|
|
begin
|
|
if FrameClass <> GetFrameClass(AFrameID) then
|
|
begin
|
|
if Assigned(FrameClass) then
|
|
LFrameClass := FrameClass.ClassName
|
|
else
|
|
LFrameClass := '';
|
|
ID3ErrorFmt(RsEID3InvalidFrameClass, [LFrameClass, ID3_FrameIDToString(AFrameID)], Self);
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3Controller.Close;
|
|
begin
|
|
SetActive(False);
|
|
end;
|
|
|
|
procedure TJvID3Controller.Commit;
|
|
const
|
|
CHandleError: array [Boolean] of TJvID3HandleError = (heRaise, heAutoCorrect);
|
|
begin
|
|
if not Active then
|
|
ID3Error(RsEID3ControllerNotActive);
|
|
|
|
try
|
|
if coRemoveEmptyFrames in Options then
|
|
FFrames.RemoveEmptyFrames;
|
|
FFrames.CheckFrames(CHandleError[coAutoCorrect in Options]);
|
|
|
|
SaveToFile(FFileName);
|
|
SetModified(False);
|
|
except
|
|
if csDesigning in ComponentState then
|
|
if Assigned(Classes.ApplicationHandleException) then
|
|
Classes.ApplicationHandleException(ExceptObject)
|
|
else
|
|
ShowException(ExceptObject, ExceptAddr)
|
|
else
|
|
raise;
|
|
end;
|
|
end;
|
|
|
|
function TJvID3Controller.CopyFromID3v1(const DoOverwrite: Boolean): Boolean;
|
|
var
|
|
ID3v1Ctrl: TJvID3v1;
|
|
begin
|
|
if not Active then
|
|
ID3Error(RsEID3ControllerNotActive, Self);
|
|
|
|
ID3v1Ctrl := TJvID3v1.Create(nil);
|
|
try
|
|
ID3v1Ctrl.FileName := FileName;
|
|
ID3v1Ctrl.Open;
|
|
Result := ID3v1Ctrl.HasTag;
|
|
if Result then
|
|
CopyFromID3v1Ctrl(ID3v1Ctrl, DoOverwrite);
|
|
finally
|
|
ID3v1Ctrl.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3Controller.CopyFromID3v1Ctrl(AID3v1: TJvID3v1;
|
|
const DoOverwrite: Boolean);
|
|
var
|
|
Frame: TJvID3Frame;
|
|
Year: Word;
|
|
|
|
function GetFrame(AFrameID: TJvID3FrameID): TJvID3Frame;
|
|
begin
|
|
Result := FFrames.FindFrame(AFrameID);
|
|
if Assigned(Result) and not DoOverwrite then
|
|
{ If the frame already exists, and we don't want to overwrite, return nil }
|
|
Result := nil
|
|
else
|
|
if not Assigned(Result) then
|
|
{ If the frame does not exists, create one }
|
|
Result := AddFrame(AFrameID);
|
|
end;
|
|
|
|
begin
|
|
{ There is a lot of extra code, because it may be possible that some frame
|
|
is not encoded in ISO-8859-1 }
|
|
|
|
if not Assigned(AID3v1) then
|
|
Exit;
|
|
|
|
// Songname
|
|
Frame := GetFrame(fiTitle);
|
|
if Assigned(Frame) then
|
|
TJvID3TextFrame(Frame).Text := UTF8ToISO_8859_1(AID3v1.SongName);
|
|
|
|
// Artist
|
|
Frame := GetFrame(fiLeadArtist);
|
|
if Assigned(Frame) then
|
|
begin
|
|
TJvID3CustomTextFrame(Frame).Text := UTF8ToISO_8859_1(AID3v1.Artist);
|
|
end;
|
|
|
|
// Album
|
|
Frame := GetFrame(fiAlbum);
|
|
if Assigned(Frame) then
|
|
TJvID3TextFrame(Frame).Text := UTF8ToISO_8859_1(AID3v1.Album);
|
|
|
|
// Year
|
|
Year := StrToIntDef(string(AID3v1.Year), 0);
|
|
if Year > 0 then
|
|
begin
|
|
if Version = ive2_4 then
|
|
begin
|
|
Frame := GetFrame(fiRecordingTime);
|
|
if Assigned(Frame) then
|
|
TJvID3TimeStampFrame(Frame).FValue := EncodeDate(Year, 1, 1);
|
|
end
|
|
else
|
|
begin
|
|
Frame := GetFrame(fiYear);
|
|
if Assigned(Frame) then
|
|
TJvID3NumberFrame(Frame).FValue := Year;
|
|
end;
|
|
end;
|
|
|
|
// Comment
|
|
Frame := GetFrame(fiComment);
|
|
if Assigned(Frame) then
|
|
TJvID3ContentFrame(Frame).Text := UTF8ToISO_8859_1(AID3v1.Comment);
|
|
|
|
// Genre
|
|
Frame := GetFrame(fiContentType);
|
|
if Assigned(Frame) then
|
|
begin
|
|
if AID3v1.Genre = 255 then
|
|
TJvID3TextFrame(Frame).Text := ''
|
|
else
|
|
TJvID3TextFrame(Frame).Text := Format('(%d)', [AID3v1.Genre]);
|
|
end;
|
|
|
|
// AlbumTrack
|
|
if AID3v1.AlbumTrack > 0 then
|
|
begin
|
|
Frame := GetFrame(fiTrackNum);
|
|
if Assigned(Frame) then
|
|
TJvID3TextFrame(Frame).Text := IntToStr(AID3v1.AlbumTrack);
|
|
end;
|
|
end;
|
|
|
|
function TJvID3Controller.CopyToID3v1(const DoOverwrite: Boolean): Boolean;
|
|
var
|
|
ID3v1Ctrl: TJvID3v1;
|
|
begin
|
|
if not Active then
|
|
ID3Error(RsEID3ControllerNotActive, Self);
|
|
|
|
ID3v1Ctrl := TJvID3v1.Create(nil);
|
|
try
|
|
ID3v1Ctrl.FileName := FileName;
|
|
ID3v1Ctrl.Open;
|
|
CopyToID3v1Ctrl(ID3v1Ctrl, DoOverwrite);
|
|
Result := ID3v1Ctrl.Commit;
|
|
finally
|
|
ID3v1Ctrl.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3Controller.CopyToID3v1Ctrl(AID3v1: TJvID3v1;
|
|
const DoOverwrite: Boolean);
|
|
var
|
|
S: string;
|
|
Frame: TJvID3Frame;
|
|
Track, P: Integer;
|
|
I: Integer;
|
|
YearSet, CommentSet: Boolean;
|
|
begin
|
|
{ There is a lot of extra code, because it may be possible that some frame
|
|
is not encoded in ISO-8859-1 }
|
|
|
|
if not Assigned(AID3v1) then
|
|
Exit;
|
|
|
|
YearSet := False;
|
|
CommentSet := False;
|
|
|
|
for I := 0 to FrameCount - 1 do
|
|
begin
|
|
Frame := FFrames[I];
|
|
|
|
with AID3v1 do
|
|
case Frame.FrameID of
|
|
fiTitle:
|
|
if DoOverwrite or (SongName = '') then
|
|
SongName := ISO_8859_1ToUTF8(Copy(TJvID3TextFrame(Frame).Text, 1, 30));
|
|
fiLeadArtist:
|
|
if DoOverwrite or (Artist = '') then
|
|
begin
|
|
{ Note: fiLeadArtist has multiple lines }
|
|
Artist := ISO_8859_1ToUTF8(Copy(TJvID3CustomTextFrame(Frame).Text, 1, 30));
|
|
end;
|
|
fiAlbum:
|
|
if DoOverwrite or (Album = '') then
|
|
Album := ISO_8859_1ToUTF8(Copy(TJvID3TextFrame(Frame).Text, 1, 30));
|
|
fiYear:
|
|
if not YearSet and (DoOverwrite or (Year = '')) then
|
|
begin
|
|
Year := Format('%.4d', [TJvID3NumberFrame(Frame).Value]);
|
|
YearSet := True;
|
|
end;
|
|
fiRecordingTime:
|
|
if not YearSet and (DoOverwrite or (Year = '')) then
|
|
begin
|
|
Year := Format('%.4d', [YearOf(TJvID3TimestampFrame(Frame).Value)]);
|
|
YearSet := True;
|
|
end;
|
|
fiComment:
|
|
{ Note : there may be more than 1 fiComment frame in the tag, just
|
|
pick the first we encounter }
|
|
if not CommentSet and (DoOverwrite or (SongName = '')) then
|
|
begin
|
|
Comment := ISO_8859_1ToUTF8(Copy(TJvID3ContentFrame(Frame).Text, 1, 30));
|
|
CommentSet := True;
|
|
end;
|
|
fiContentType:
|
|
if DoOverwrite or (Genre = 255) then
|
|
Genre := ID3_LongGenreToID(TJvID3TextFrame(Frame).Text);
|
|
fiTrackNum:
|
|
if DoOverwrite or (AlbumTrack = 0) then
|
|
begin
|
|
S := TJvID3TextFrame(Frame).Text;
|
|
P := Pos('/', S);
|
|
if P > 0 then
|
|
Track := StrToIntDef(Copy(S, 1, P - 1), 0)
|
|
else
|
|
Track := StrToIntDef(S, 0);
|
|
if (Track < 0) or (Track > 255) then
|
|
Track := 0;
|
|
AlbumTrack := Byte(Track);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3Controller.DoClose;
|
|
begin
|
|
{ Note: this will set Modified to True... }
|
|
Frames.Clear;
|
|
FFileInfo.Reset;
|
|
FActive := False;
|
|
|
|
{ ... thus we set it now back to false }
|
|
SetModified(False);
|
|
end;
|
|
|
|
procedure TJvID3Controller.DoOpen;
|
|
var
|
|
FileStream: TFileStream;
|
|
begin
|
|
FileStream := TFileStream.Create(FFileName, fmOpenRead or fmShareDenyWrite);
|
|
try
|
|
LoadFromStream(FileStream);
|
|
FActive := True;
|
|
|
|
if ReadVersionAs <> ifvDontCare then
|
|
FFrames.ChangeToVersion(CForceVersionToVersion[ReadVersionAs]);
|
|
finally
|
|
FileStream.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3Controller.EndReading;
|
|
begin
|
|
if not (icsReading in FState) then
|
|
ID3Error(RsENotReading, Self);
|
|
|
|
Exclude(FState, icsReading);
|
|
FreeAndNil(FStream);
|
|
FreeAndNil(FTempStream);
|
|
|
|
EndUpdate;
|
|
end;
|
|
|
|
procedure TJvID3Controller.EndUpdate;
|
|
begin
|
|
Dec(FUpdateCount);
|
|
if FUpdateCount = 0 then
|
|
ID3Event(ideID3Change, 0);
|
|
end;
|
|
|
|
procedure TJvID3Controller.EndUseTempStream;
|
|
begin
|
|
if not (icsUsingTempStream in FState) then
|
|
ID3Error(RsENotUsingTempStream, Self);
|
|
|
|
Exclude(FState, icsUsingTempStream);
|
|
{ Do not free the temp stream }
|
|
end;
|
|
|
|
procedure TJvID3Controller.EndWriting;
|
|
begin
|
|
if not (icsWriting in FState) then
|
|
ID3Error(RsENotWriting, Self);
|
|
|
|
Exclude(FState, icsWriting);
|
|
FreeAndNil(FStream);
|
|
FreeAndNil(FTempStream);
|
|
|
|
EndUpdate;
|
|
end;
|
|
|
|
procedure TJvID3Controller.EnsureExists(const FrameIDs: TJvID3FrameIDs);
|
|
var
|
|
FrameID: TJvID3FrameID;
|
|
IDs: TJvID3FrameIDs;
|
|
begin
|
|
if not Active then
|
|
ID3Error(RsEID3ControllerNotActive, Self);
|
|
|
|
IDs := FrameIDs - FFrames.GetFrameIDs;
|
|
{ IDs represents a set of frames we have to construct }
|
|
|
|
if IDs <> [] then
|
|
for FrameID := Low(TJvID3FrameID) to High(TJvID3FrameID) do
|
|
if (FrameID in IDs) and not (GetFrameClass(FrameID) = TJvID3SkipFrame) then
|
|
AddFrame(FrameID);
|
|
end;
|
|
|
|
procedure TJvID3Controller.Erase;
|
|
var
|
|
SavedActive: Boolean;
|
|
begin
|
|
SavedActive := Active;
|
|
Close;
|
|
ChangeTagSize(FileName, 0);
|
|
|
|
if SavedActive then
|
|
begin
|
|
Open;
|
|
{ Force Modified to be True }
|
|
SetModified(True);
|
|
end;
|
|
end;
|
|
|
|
function TJvID3Controller.FindFirstFrame(const AFrameID: TJvID3FrameID;
|
|
var Frame: TJvID3Frame): Boolean;
|
|
begin
|
|
Frame := nil;
|
|
Result := FindNextFrame(AFrameID, Frame);
|
|
end;
|
|
|
|
function TJvID3Controller.FindNextFrame(const AFrameID: TJvID3FrameID;
|
|
var From: TJvID3Frame): Boolean;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
if From = nil then
|
|
begin
|
|
From := Frames.FindFrame(AFrameID);
|
|
Result := Assigned(From);
|
|
end
|
|
else
|
|
begin
|
|
Result := True;
|
|
I := From.Index + 1;
|
|
while I < FrameCount do
|
|
begin
|
|
From := Frames[I];
|
|
if From.FrameID = AFrameID then
|
|
Exit;
|
|
Inc(I);
|
|
end;
|
|
Result := False;
|
|
From := nil;
|
|
end;
|
|
end;
|
|
|
|
class function TJvID3Controller.GetFrameClass(const FrameID: TJvID3FrameID): TJvID3FrameClass;
|
|
begin
|
|
Result := DefaultFrameClasses[FrameID];
|
|
if not Assigned(Result) then
|
|
{ TJvID3SkipFrame is the default frame for non-implemented frames }
|
|
Result := TJvID3SkipFrame;
|
|
end;
|
|
|
|
function TJvID3Controller.GetFrameCount: Integer;
|
|
begin
|
|
Result := Frames.Count;
|
|
end;
|
|
|
|
function TJvID3Controller.GetFrameCountFor(const AFrameID: TJvID3FrameID): Cardinal;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
Result := 0;
|
|
for I := 0 to FrameCount - 1 do
|
|
if Frames[I].FrameID = AFrameID then
|
|
Inc(Result);
|
|
end;
|
|
|
|
function TJvID3Controller.GetReadVersion: TJvID3Version;
|
|
begin
|
|
{ Returns the end-version (2.3 or 2.4) of a tag when reading. For example
|
|
a tag can have version 2.3 (on disk) but when ReadVersionAs is set to ifv2_4
|
|
it will be translated to a v2.4 tag, and ReadVersion will return ive2_4 in
|
|
this case }
|
|
|
|
case ReadVersionAs of
|
|
ifvDontCare:
|
|
begin
|
|
Result := Version;
|
|
if Result < ive2_2 then
|
|
Result := ive2_2
|
|
else
|
|
if Result > ive2_4 then
|
|
Result := ive2_4;
|
|
end;
|
|
ifv2_2:
|
|
Result := ive2_2;
|
|
ifv2_3:
|
|
Result := ive2_3;
|
|
ifv2_4:
|
|
Result := ive2_4;
|
|
else
|
|
Result := ive2_3;
|
|
ID3Error(RsEID3UnknownVersion, Self);
|
|
end;
|
|
end;
|
|
|
|
function TJvID3Controller.GetTagSize: Cardinal;
|
|
begin
|
|
if not Active then
|
|
Result := 0
|
|
else
|
|
Result := Header.Size;
|
|
end;
|
|
|
|
function TJvID3Controller.GetTempStreamSize: Cardinal;
|
|
begin
|
|
if not Assigned(FTempStream) then
|
|
ID3Error(RsENoTempStream, Self);
|
|
|
|
Result := FTempStream.Position;
|
|
end;
|
|
|
|
function TJvID3Controller.GetVersion: TJvID3Version;
|
|
begin
|
|
Result := MajorVersionToVersion(FHeader.MajorVersion);
|
|
end;
|
|
|
|
function TJvID3Controller.GetWriteVersion: TJvID3Version;
|
|
begin
|
|
{ Returns the end-version (2.3 or 2.4) of a tag when writing. For example
|
|
a tag can have version 2.3 but when WriteVersionAs is set to ifv2_4 it will
|
|
be translated to a v2.4 tag, and WriteVersion will return ive2_4 in this
|
|
case }
|
|
case WriteVersionAs of
|
|
ifvDontCare:
|
|
begin
|
|
Result := Version;
|
|
{ Default to v2.4; latest version }
|
|
if (Result < ive2_2) or (Result > ive2_4) then
|
|
Result := ive2_4;
|
|
end;
|
|
ifv2_2:
|
|
Result := ive2_2;
|
|
ifv2_3:
|
|
Result := ive2_3;
|
|
ifv2_4:
|
|
Result := ive2_4;
|
|
else
|
|
Result := ive2_3;
|
|
ID3Error(RsEID3UnknownVersion, Self);
|
|
end;
|
|
end;
|
|
|
|
function TJvID3Controller.HasFrame(const AFrameID: TJvID3FrameID): Boolean;
|
|
begin
|
|
Result := Assigned(Frames.FindFrame(AFrameID));
|
|
end;
|
|
|
|
procedure TJvID3Controller.ID3Event(Event: TJvID3Event; Info: Integer);
|
|
begin
|
|
if (Event in [ideFrameChange, ideFrameListChange]) and
|
|
(FState * [icsReading, icsWriting] = []) then
|
|
SetModified(True);
|
|
|
|
if (FUpdateCount = 0) and Assigned(FDesigner) then
|
|
FDesigner.ID3Event(Event, Info);
|
|
end;
|
|
|
|
procedure TJvID3Controller.Loaded;
|
|
begin
|
|
inherited Loaded;
|
|
try
|
|
if FStreamedActive then
|
|
SetActive(True);
|
|
except
|
|
if csDesigning in ComponentState then
|
|
if Assigned(Classes.ApplicationHandleException) then
|
|
Classes.ApplicationHandleException(ExceptObject)
|
|
else
|
|
ShowException(ExceptObject, ExceptAddr)
|
|
else
|
|
raise;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3Controller.LoadFromStream(AStream: TStream);
|
|
begin
|
|
BeginReading;
|
|
try
|
|
{ Clear }
|
|
FHeader.Reset;
|
|
FExtendedHeader.Reset;
|
|
FFrames.Reset;
|
|
|
|
{ Read the header }
|
|
if AStream.Size >= 10 then
|
|
FStream.ReadFromStream(AStream, 10)
|
|
else
|
|
Exit;
|
|
|
|
{ Parse the header }
|
|
FHeader.Read;
|
|
|
|
if FHeader.HasTag and (Version in CSupportedVersions) then
|
|
begin
|
|
|
|
{ Init encoding after the version is read }
|
|
FStream.InitAllowedEncodings(ReadVersion, ReadEncodingAs);
|
|
|
|
{ Note that we will overwrite the header in FStream (first 10 bytes in FStream) }
|
|
FStream.Position := 0;
|
|
|
|
if hfUnsynchronisation in FHeader.Flags then
|
|
{ Unsynchronisation scheme is applied to the tag, we have to remove it,
|
|
ie replace $FF $00 with $FF }
|
|
RemoveUnsynchronisationScheme(AStream, FStream, FHeader.Size)
|
|
else
|
|
{ If not, we just copy the stream to the memory stream }
|
|
FStream.ReadFromStream(AStream, FHeader.Size);
|
|
|
|
FStream.Position := 0;
|
|
|
|
if hfExtendedHeader in FHeader.Flags then
|
|
{ Read extended header, note that it's read after the unsynchronisation
|
|
scheme is removed }
|
|
FExtendedHeader.Read;
|
|
|
|
FFrames.Read;
|
|
end;
|
|
|
|
if Header.HasTag then
|
|
FFileInfo.Read(AStream, 10 + Header.Size)
|
|
{%H-}else
|
|
FFileInfo.Read(AStream, 0);
|
|
finally
|
|
EndReading;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3Controller.Open;
|
|
begin
|
|
SetActive(True);
|
|
end;
|
|
|
|
procedure TJvID3Controller.RegisterClient(Client: TObject;
|
|
Event: TJvID3ActivateChangeEvent);
|
|
begin
|
|
{ Based on TCustomConnection.RegisterClient }
|
|
FClients.Add(Client);
|
|
FActivateEvents.Add(TMethod(Event).Code);
|
|
end;
|
|
|
|
procedure TJvID3Controller.RemoveUnsynchronisationSchemeToTempStream(const ASize: Integer);
|
|
begin
|
|
if icsUsingTempStream in FState then
|
|
ID3Error(RsEAlreadyUsingTempStream, Self);
|
|
|
|
if not Assigned(FTempStream) then
|
|
FTempStream := TJvID3Stream.Create;
|
|
|
|
FTempStream.Seek(0, soBeginning);
|
|
RemoveUnsynchronisationScheme(FStream, FTempStream, ASize);
|
|
end;
|
|
|
|
procedure TJvID3Controller.SaveToFile(const AFileName: string);
|
|
var
|
|
PaddingSize: Integer;
|
|
OldTagSizeInclHeader: Cardinal;
|
|
NewTagSizeInclHeader: Cardinal;
|
|
FileStream: TFileStream;
|
|
|
|
{ Normally Tagsize is the size of the tag including padding excluding header, so
|
|
we have vars
|
|
|
|
xxxTagSizeInclHeader = normal Tagsize + 10 (if tag exists)
|
|
= 0 (if tag doesn't exists)
|
|
xxxTagSizeInclHeaderExclPadding = normal Tagsize + 10 - size of the padding (if tag exists)
|
|
= 0 (if tag doesn't exists)
|
|
}
|
|
function CalcNewPadding(const AOldTagSizeInclHeader: Cardinal;
|
|
const ANewTagSizeInclHeaderExclPadding: Cardinal): Cardinal;
|
|
const
|
|
CMinPadding = $800; // = 2048
|
|
CChunk = $800;
|
|
var
|
|
NewTagSizeInclHeader: Cardinal;
|
|
begin
|
|
Assert(AOldTagSizeInclHeader <= ANewTagSizeInclHeaderExclPadding);
|
|
|
|
if AOldTagSizeInclHeader = 0 then
|
|
Result := CMinPadding
|
|
else
|
|
begin
|
|
NewTagSizeInclHeader := AOldTagSizeInclHeader;
|
|
{ ?? }
|
|
while NewTagSizeInclHeader <= ANewTagSizeInclHeaderExclPadding do
|
|
Inc(NewTagSizeInclHeader, 1 + NewTagSizeInclHeader div 2);
|
|
|
|
Result := NewTagSizeInclHeader - ANewTagSizeInclHeaderExclPadding;
|
|
if Result < CMinPadding then
|
|
Result := CMinPadding;
|
|
end;
|
|
|
|
NewTagSizeInclHeader := ANewTagSizeInclHeaderExclPadding + Result;
|
|
{ Round to multiple of CChunk }
|
|
NewTagSizeInclHeader := ((NewTagSizeInclHeader + CChunk {%H-}- 1) div CChunk) * CChunk;
|
|
Result := NewTagSizeInclHeader - ANewTagSizeInclHeaderExclPadding;
|
|
end;
|
|
|
|
begin
|
|
BeginWriting;
|
|
try
|
|
FStream.InitAllowedEncodings(WriteVersion, WriteEncodingAs);
|
|
|
|
{ Maybe only write header to the filestream? }
|
|
Header.Write;
|
|
|
|
if hfExtendedHeader in FHeader.Flags then
|
|
{ Write extended header, note that it's written before the unsynchronisation
|
|
scheme is applied }
|
|
FExtendedHeader.Write;
|
|
|
|
FFrames.Write;
|
|
|
|
{ Compression }
|
|
|
|
{ Encryption }
|
|
|
|
if hfUnsynchronisation in Header.Flags then
|
|
ApplyUnsynchronisationSchemeOnCurrentStream;
|
|
|
|
FileStream := TFileStream.Create(AFileName, fmOpenReadWrite or fmShareExclusive);
|
|
try
|
|
OldTagSizeInclHeader := GetTagSizeInclHeader(FileStream);
|
|
|
|
{ FStream.Size = size of new tag including header excluding padding }
|
|
PaddingSize := OldTagSizeInclHeader - Cardinal(FStream.Size);
|
|
|
|
{ We always want to have padding (because of possible
|
|
unsynchronisation possibly needs padding), thus if PaddingSize = 0, then
|
|
also calculate new bigger padding size }
|
|
if PaddingSize <= 0 then
|
|
PaddingSize := CalcNewPadding(OldTagSizeInclHeader, FStream.Size);
|
|
|
|
NewTagSizeInclHeader := FStream.Size + PaddingSize;
|
|
if NewTagSizeInclHeader < OldTagSizeInclHeader then
|
|
Inc(PaddingSize, OldTagSizeInclHeader - NewTagSizeInclHeader)
|
|
else
|
|
if NewTagSizeInclHeader > OldTagSizeInclHeader then
|
|
begin
|
|
{ (rb) This is a bit clumbsy, we have to throw away the stream before
|
|
resizing, then resize the file, and afterward construct the stream
|
|
again.
|
|
|
|
Couldn't come up with a cleaner way
|
|
}
|
|
|
|
FreeAndNil(FileStream);
|
|
|
|
ChangeTagSize(FileName, NewTagSizeInclHeader);
|
|
|
|
FileStream := TFileStream.Create(FFileName, fmOpenReadWrite or fmShareExclusive);
|
|
end;
|
|
|
|
{ Write the padding }
|
|
FStream.Seek(0, soFromEnd);
|
|
FStream.WritePadding(PaddingSize);
|
|
|
|
{ Update header & write it again to the stream }
|
|
Header.FSize := NewTagSizeInclHeader - 10;
|
|
FStream.Seek(0, soBeginning);
|
|
Header.Write;
|
|
|
|
{ Write the memory stream to the file }
|
|
FStream.Seek(0, soBeginning);
|
|
FileStream.Seek(0, soBeginning);
|
|
FileStream.CopyFrom(FStream, FStream.Size);
|
|
finally
|
|
FileStream.Free;
|
|
end;
|
|
finally
|
|
EndWriting;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3Controller.SendActivateEvent(Activated: Boolean);
|
|
var
|
|
I: Integer;
|
|
ActivateEvent: TJvID3ActivateChangeEvent;
|
|
begin
|
|
{ Based on TCustomConnection.SendConnectEvent }
|
|
for I := 0 to FClients.Count - 1 do
|
|
begin
|
|
if FActivateEvents[I] <> nil then
|
|
begin
|
|
TMethod(ActivateEvent).Code := FActivateEvents[I];
|
|
TMethod(ActivateEvent).Data := FClients[I];
|
|
ActivateEvent(Self, Activated);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3Controller.SetActive(const Value: Boolean);
|
|
begin
|
|
{ Based on TCustomConnection.SetConnected }
|
|
if (csReading in ComponentState) and Value then
|
|
FStreamedActive := True
|
|
else
|
|
begin
|
|
if Value = FActive then
|
|
Exit;
|
|
if Value then
|
|
begin
|
|
//if Assigned(BeforeConnect) then BeforeConnect(Self);
|
|
DoOpen;
|
|
SendActivateEvent(FActive);
|
|
//if Assigned(AfterConnect) then AfterConnect(Self);
|
|
end
|
|
else
|
|
begin
|
|
//if Assigned(BeforeDisconnect) then BeforeDisconnect(Self);
|
|
//SendConnectEvent(False);
|
|
DoClose;
|
|
SendActivateEvent(FActive);
|
|
//if Assigned(AfterDisconnect) then AfterDisconnect(Self);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3Controller.SetExtendedHeader(const Value: TJvID3ExtendedHeader);
|
|
begin
|
|
FExtendedHeader.Assign(Value);
|
|
end;
|
|
|
|
procedure TJvID3Controller.SetFileName(const Value: TFileName);
|
|
var
|
|
SavedActive: Boolean;
|
|
begin
|
|
if Value <> FFileName then
|
|
begin
|
|
SavedActive := Active;
|
|
|
|
Close;
|
|
FFileName := Value;
|
|
|
|
if SavedActive then
|
|
Open;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3Controller.SetHeader(const Value: TJvID3Header);
|
|
begin
|
|
FHeader.Assign(Value);
|
|
end;
|
|
|
|
procedure TJvID3Controller.SetModified(Value: Boolean);
|
|
begin
|
|
FModified := Value;
|
|
end;
|
|
|
|
procedure TJvID3Controller.SetReadEncodingAs(const Value: TJvID3ForceEncoding);
|
|
begin
|
|
if (FReadVersionAs in [ifv2_2, ifv2_3]) and (Value in [ifeUTF_16BE, ifeUTF_8]) then
|
|
ID3Error(RsEID3EncodingNotSupported, Self);
|
|
|
|
FReadEncodingAs := Value;
|
|
end;
|
|
|
|
procedure TJvID3Controller.SetReadVersionAs(const Value: TJvID3ForceVersion);
|
|
begin
|
|
FReadVersionAs := Value;
|
|
if (FReadVersionAs in [ifv2_2, ifv2_3]) and (FReadEncodingAs in [ifeUTF_16BE, ifeUTF_8]) then
|
|
FReadEncodingAs := ifeUTF_16;
|
|
end;
|
|
|
|
procedure TJvID3Controller.SetVersion(NewVersion: TJvID3Version);
|
|
begin
|
|
if NewVersion = iveLowerThan2_2 then
|
|
NewVersion := ive2_2
|
|
else
|
|
if NewVersion = iveHigherThan2_4 then
|
|
NewVersion := ive2_4;
|
|
|
|
if NewVersion = GetVersion then
|
|
Exit;
|
|
|
|
ChangeToVersion(NewVersion);
|
|
end;
|
|
|
|
procedure TJvID3Controller.SetWriteEncodingAs(const Value: TJvID3ForceEncoding);
|
|
begin
|
|
if (FWriteVersionAs in [ifv2_2, ifv2_3]) and (Value in [ifeUTF_16BE, ifeUTF_8]) then
|
|
ID3Error(RsEID3EncodingNotSupported, Self);
|
|
|
|
FWriteEncodingAs := Value;
|
|
end;
|
|
|
|
procedure TJvID3Controller.SetWriteVersionAs(const Value: TJvID3ForceVersion);
|
|
begin
|
|
FWriteVersionAs := Value;
|
|
if (FWriteVersionAs in [ifv2_2, ifv2_3]) and (FWriteEncodingAs in [ifeUTF_16BE, ifeUTF_8]) then
|
|
FWriteEncodingAs := ifeUTF_16;
|
|
end;
|
|
|
|
procedure TJvID3Controller.UnRegisterClient(Client: TObject);
|
|
var
|
|
Index: Integer;
|
|
begin
|
|
{ Based on TCustomConnection.UnRegisterClient }
|
|
Index := FClients.IndexOf(Client);
|
|
if Index <> -1 then
|
|
begin
|
|
FClients.Delete(Index);
|
|
FActivateEvents.Delete(Index);
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3Controller.WriteTempStream;
|
|
var
|
|
LTempStreamSize: Cardinal;
|
|
begin
|
|
if not Assigned(FTempStream) then
|
|
ID3Error(RsENoTempStream, Self);
|
|
|
|
LTempStreamSize := GetTempStreamSize;
|
|
FTempStream.Seek(0, soBeginning);
|
|
FStream.CopyFrom(FTempStream, LTempStreamSize);
|
|
end;
|
|
|
|
|
|
//=== { TJvID3ControllerDesigner } ===========================================
|
|
|
|
constructor TJvID3ControllerDesigner.Create(Controller: TJvID3Controller);
|
|
begin
|
|
inherited Create;
|
|
FController := Controller;
|
|
FController.FDesigner := Self;
|
|
end;
|
|
|
|
destructor TJvID3ControllerDesigner.Destroy;
|
|
begin
|
|
FController.FDesigner := nil;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TJvID3ControllerDesigner.BeginDesign;
|
|
begin
|
|
Controller.BeginUpdate;
|
|
end;
|
|
|
|
procedure TJvID3ControllerDesigner.EndDesign;
|
|
begin
|
|
Controller.EndUpdate;
|
|
end;
|
|
|
|
procedure TJvID3ControllerDesigner.ID3Event(Event: TJvID3Event; Info: Integer);
|
|
begin
|
|
end;
|
|
|
|
|
|
//=== { TJvID3CustomTextFrame } ==============================================
|
|
|
|
procedure TJvID3CustomTextFrame.Assign(Source: TPersistent);
|
|
begin
|
|
if Source is TJvID3CustomTextFrame then
|
|
begin
|
|
Text := TJvID3CustomTextFrame(Source).Text;
|
|
end;
|
|
inherited Assign(Source);
|
|
end;
|
|
|
|
class function TJvID3CustomTextFrame.CanAddFrame(AController: TJvID3Controller;
|
|
AFrameID: TJvID3FrameID): Boolean;
|
|
begin
|
|
{ There may only be one text information frame of its kind in an tag }
|
|
Result := not AController.HasFrame(AFrameID) or
|
|
inherited CanAddFrame(AController, AFrameID);
|
|
end;
|
|
|
|
procedure TJvID3CustomTextFrame.Clear;
|
|
begin
|
|
Text := '';
|
|
inherited Clear;
|
|
end;
|
|
|
|
function TJvID3CustomTextFrame.GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal;
|
|
begin
|
|
Result := 1 + LengthEnc(Text, ToEncoding);
|
|
end;
|
|
|
|
function TJvID3CustomTextFrame.GetIsEmpty: Boolean;
|
|
begin
|
|
{ Framesize is always >=1, because we must write the Encoding byte }
|
|
Result := GetFrameSize(Encoding) <= 1;
|
|
end;
|
|
|
|
function TJvID3CustomTextFrame.MustWriteAsUTF: Boolean;
|
|
begin
|
|
Result := HasNonISO_8859_1Chars(Text);
|
|
end;
|
|
|
|
procedure TJvID3CustomTextFrame.ReadFrame;
|
|
var
|
|
//S: WideString;
|
|
S: String = '';
|
|
begin
|
|
with Stream do
|
|
begin
|
|
ReadEncoding;
|
|
ReadStringEnc(S);
|
|
Text := S;
|
|
end;
|
|
end;
|
|
|
|
function TJvID3CustomTextFrame.SameUniqueIDAs(const Frame: TJvID3Frame): Boolean;
|
|
begin
|
|
{ There may only be one text information frame of its kind in an tag }
|
|
Result := (Assigned(Frame) and (Frame.FrameID = FrameID)) or inherited SameUniqueIDAs(Frame);
|
|
end;
|
|
|
|
function TJvID3CustomTextFrame.SupportsVersion(const AVersion: TJvID3Version): Boolean;
|
|
begin
|
|
case FrameID of
|
|
{ ** Not supported in 2.2 ** }
|
|
|
|
fiFileOwner, fiEncoderSettings:
|
|
Result := AVersion in [ive2_3, ive2_4];
|
|
|
|
{ ** Deprecated in 2.4 ** }
|
|
|
|
{ [TDAT] Replaced by the TDRC frame, 'Recording time' }
|
|
fiDate,
|
|
{ [TIME] Replaced by the TDRC frame, 'Recording time' }
|
|
fiTime,
|
|
{ [TORY] Replaced by the TDOR frame, 'Original release time' }
|
|
fiOrigYear,
|
|
{ [TRDA] Replaced by the TDRC frame, 'Recording time' }
|
|
fiRecordingDates,
|
|
{ [TSIZ] The information contained in this frame is in the general case
|
|
either trivial to calculate for the player or impossible for the
|
|
tagger to calculate. There is however no good use for such
|
|
information. The frame is therefore completely deprecated. }
|
|
fiSize,
|
|
{ [TYER] This frame is replaced by the TDRC frame, 'Recording time' }
|
|
fiYear:
|
|
Result := AVersion in [ive2_2, ive2_3];
|
|
|
|
{ ** New frames in 2.4 ** }
|
|
|
|
fiEncodingTime, { [TDEN] Encoding time }
|
|
fiOrigReleaseTime, { [TDOR] Original release time }
|
|
fiRecordingTime, { [TDRC] Recording time }
|
|
fiReleaseTime, { [TDRL] Release time }
|
|
fiTaggingTime, { [TDTG] Tagging time }
|
|
//fiInvolvedPeople2, { [TIPL] Involved people list }
|
|
//fiMusicianCreditList, { [TMCL] Musician credits list }
|
|
fiMood, { [TMOO] Mood }
|
|
fiProducedNotice, { [TPRO] Produced notice }
|
|
fiAlbumSortOrder, { [TSOA] Album sort order }
|
|
fiPerformerSortOrder, { [TSOP] Performer sort order }
|
|
fiTitleSortOrder, { [TSOT] Title sort order }
|
|
fiSetSubTitle: { [TSST] Set subtitle }
|
|
Result := AVersion = ive2_4;
|
|
else
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3CustomTextFrame.WriteFrame;
|
|
begin
|
|
with Stream do
|
|
begin
|
|
WriteEncoding;
|
|
WriteStringEnc(Text);
|
|
end;
|
|
end;
|
|
|
|
//=== { TJvID3DoubleListFrame } ==============================================
|
|
|
|
procedure TJvID3DoubleListFrame.AfterConstruction;
|
|
begin
|
|
inherited AfterConstruction;
|
|
|
|
FList := TStringList.Create;
|
|
TStringList(FList).OnChange := @ListChanged;
|
|
(*
|
|
{$IFDEF COMPILER12_UP}
|
|
FList := TStringList.Create;
|
|
TStringList(FList).OnChange := ListChanged;
|
|
{$ELSE}
|
|
FList := JclUnicode.TWideStringList.Create;
|
|
JclUnicode.TWideStringList(FList).OnChange := ListChanged;
|
|
{$ENDIF COMPILER12_UP}
|
|
*)
|
|
end;
|
|
|
|
procedure TJvID3DoubleListFrame.Assign(Source: TPersistent);
|
|
begin
|
|
if Source is TJvID3DoubleListFrame then
|
|
begin
|
|
FList.Assign(TJvID3DoubleListFrame(Source).List);
|
|
end;
|
|
inherited Assign(Source);
|
|
end;
|
|
|
|
procedure TJvID3DoubleListFrame.BeforeDestruction;
|
|
begin
|
|
inherited BeforeDestruction;
|
|
FList.Free;
|
|
end;
|
|
|
|
class function TJvID3DoubleListFrame.CanAddFrame(AController: TJvID3Controller;
|
|
AFrameID: TJvID3FrameID): Boolean;
|
|
begin
|
|
{ There may only be one "IPLS" frame in each tag. }
|
|
Result :=
|
|
((AFrameID in [fiInvolvedPeople, fiInvolvedPeople2, fiMusicianCreditList]) and
|
|
not AController.HasFrame(AFrameID)) or
|
|
inherited CanAddFrame(AController, AFrameID);
|
|
end;
|
|
|
|
procedure TJvID3DoubleListFrame.ChangeToVersion(const ANewVersion: TJvID3Version);
|
|
var
|
|
Frame: TJvID3DoubleListFrame;
|
|
begin
|
|
if IsEmpty then
|
|
Exit;
|
|
|
|
case ANewVersion of
|
|
ive2_2, ive2_3:
|
|
if FrameID in [fiInvolvedPeople2, fiMusicianCreditList] then
|
|
begin
|
|
{ Change fiInvolvedPeople2, fiMusicianCreditList to fiInvolvedPeople }
|
|
Frame := TJvID3DoubleListFrame.FindOrCreate(FController, fiInvolvedPeople);
|
|
List.Assign(Frame.List);
|
|
end;
|
|
ive2_4:
|
|
if FrameID = fiInvolvedPeople then
|
|
begin
|
|
{ Change fiInvolvedPeople to fiInvolvedPeople2 }
|
|
Frame := TJvID3DoubleListFrame.FindOrCreate(FController, fiInvolvedPeople2);
|
|
List.Assign(Frame.List);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TJvID3DoubleListFrame.CheckFrame(const HandleError: TJvID3HandleError): Boolean;
|
|
begin
|
|
Result := True;
|
|
end;
|
|
|
|
procedure TJvID3DoubleListFrame.Clear;
|
|
begin
|
|
List.Clear;
|
|
inherited Clear;
|
|
end;
|
|
|
|
class function TJvID3DoubleListFrame.Find(AController: TJvID3Controller;
|
|
const AFrameID: TJvID3FrameID): TJvID3DoubleListFrame;
|
|
var
|
|
Frame: TJvID3Frame;
|
|
begin
|
|
Result := nil;
|
|
if not Assigned(AController) or not AController.Active then
|
|
Exit;
|
|
|
|
Frame := AController.Frames.FindFrame(AFrameID);
|
|
if Frame is TJvID3DoubleListFrame then
|
|
Result := TJvID3DoubleListFrame(Frame)
|
|
end;
|
|
|
|
class function TJvID3DoubleListFrame.FindOrCreate(AController: TJvID3Controller;
|
|
const AFrameID: TJvID3FrameID): TJvID3DoubleListFrame;
|
|
begin
|
|
if not Assigned(AController) then
|
|
ID3Error(RsEID3NoController);
|
|
|
|
Result := Find(AController, AFrameID);
|
|
if not Assigned(Result) then
|
|
begin
|
|
AController.CheckFrameClass(TJvID3DoubleListFrame, AFrameID);
|
|
Result := TJvID3DoubleListFrame(AController.AddFrame(AFrameID));
|
|
end;
|
|
end;
|
|
|
|
function TJvID3DoubleListFrame.GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
{ 1 byte for encoding }
|
|
Result := 1;
|
|
|
|
for I := 0 to List.Count - 1 do
|
|
begin
|
|
Inc(Result, LengthEnc(Names[I], ToEncoding));
|
|
Inc(Result, LengthTerminatorEnc(ToEncoding));
|
|
Inc(Result, LengthEnc(Values[I], ToEncoding));
|
|
Inc(Result, LengthTerminatorEnc(ToEncoding));
|
|
end;
|
|
end;
|
|
|
|
function TJvID3DoubleListFrame.GetIsEmpty: Boolean;
|
|
begin
|
|
Result := (List.Count = 0) or ((List.Count = 1) and (List[0] = ''))
|
|
end;
|
|
|
|
//function TJvID3DoubleListFrame.GetNames(const AIndex: Integer): WideString;
|
|
function TJvID3DoubleListFrame.GetNames(const AIndex: Integer): String;
|
|
begin
|
|
Result := List.Names[AIndex];
|
|
end;
|
|
|
|
//function TJvID3DoubleListFrame.GetValues(const AIndex: Integer): WideString;
|
|
function TJvID3DoubleListFrame.GetValues(const AIndex: Integer): String;
|
|
begin
|
|
if Index >= 0 then
|
|
Result := Copy(List[AIndex], Length(Names[AIndex]) + 2, MaxInt)
|
|
else
|
|
Result := '';
|
|
end;
|
|
|
|
procedure TJvID3DoubleListFrame.ListChanged(Sender: TObject);
|
|
begin
|
|
Changed;
|
|
end;
|
|
|
|
function TJvID3DoubleListFrame.MustWriteAsUTF: Boolean;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
Result := False;
|
|
for I := 0 to List.Count - 1 do
|
|
if HasNonISO_8859_1Chars(List[i]) then
|
|
begin
|
|
Result := True;
|
|
Exit;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3DoubleListFrame.ReadFrame;
|
|
const
|
|
CMinBytes: array [TJvID3Encoding] of Byte = (2, 4, 4, 2);
|
|
var
|
|
//S1, S2: WideString;
|
|
S1: String = '';
|
|
S2: String = '';
|
|
begin
|
|
with Stream do
|
|
begin
|
|
ReadEncoding;
|
|
|
|
while BytesTillEndOfFrame > CMinBytes[Encoding] do
|
|
begin
|
|
ReadStringEnc(S1);
|
|
ReadStringEnc(S2);
|
|
List.Add(S1 + '=' + S2);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TJvID3DoubleListFrame.SameUniqueIDAs(const Frame: TJvID3Frame): Boolean;
|
|
begin
|
|
Result := (Assigned(Frame) and (Frame.FrameID = FrameID)) or inherited SameUniqueIDAs(Frame);
|
|
end;
|
|
|
|
//procedure TJvID3DoubleListFrame.SetList(Value: {$IFDEF COMPILER12_UP}TStrings{$ELSE}JclUnicode.TWideStrings{$ENDIF COMPILER12_UP});
|
|
procedure TJvID3DoubleListFrame.SetList(Value: TStrings);
|
|
begin
|
|
FList.Assign(Value);
|
|
Changed;
|
|
end;
|
|
|
|
function TJvID3DoubleListFrame.SupportsVersion(const AVersion: TJvID3Version): Boolean;
|
|
begin
|
|
case FrameID of
|
|
{ Deprecated in 2.4 }
|
|
|
|
{ [IPLS] - Involved people list
|
|
This frame is replaced by the two frames TMCL, 'Musician credits
|
|
and TIPL, 'Involved people list' }
|
|
|
|
fiInvolvedPeople:
|
|
Result := AVersion in [ive2_2, ive2_3];
|
|
|
|
{ New frames in 2.4 }
|
|
|
|
fiInvolvedPeople2, { [TIPL] Involved people list }
|
|
fiMusicianCreditList: { [TMCL] Musician credits list }
|
|
Result := AVersion = ive2_4;
|
|
else
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3DoubleListFrame.WriteFrame;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
with Stream do
|
|
begin
|
|
WriteEncoding;
|
|
for I := 0 to List.Count - 1 do
|
|
begin
|
|
WriteStringEnc(Names[I]);
|
|
WriteTerminatorEnc;
|
|
WriteStringEnc(Values[I]);
|
|
WriteTerminatorEnc;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
//=== { TJvID3ExtendedHeader } ===============================================
|
|
|
|
procedure TJvID3ExtendedHeader.Assign(Source: TPersistent);
|
|
begin
|
|
if Source is TJvID3ExtendedHeader then
|
|
begin
|
|
FTotalFrameCRC := TJvID3ExtendedHeader(Source).TotalFrameCRC;
|
|
FSizeOfPadding := TJvID3ExtendedHeader(Source).SizeOfPadding;
|
|
FFlags := TJvID3ExtendedHeader(Source).Flags;
|
|
end
|
|
else
|
|
inherited Assign(Source);
|
|
end;
|
|
|
|
procedure TJvID3ExtendedHeader.ChangeToVersion(const ANewVersion: TJvID3Version);
|
|
begin
|
|
case ANewVersion of
|
|
ive2_2:
|
|
FFlags := [];
|
|
ive2_3:
|
|
FFlags := FFlags - [hefTagIsAnUpdate, hefTagRestrictions];
|
|
ive2_4:
|
|
{ Nothing }
|
|
else
|
|
ID3Error(RsEID3VersionNotSupported, Controller);
|
|
end;
|
|
end;
|
|
|
|
function TJvID3ExtendedHeader.GetSize: Cardinal;
|
|
begin
|
|
Result := GetSizeForVersion(Controller.Version);
|
|
end;
|
|
|
|
function TJvID3ExtendedHeader.GetSizeForVersion(const AVersion: TJvID3Version): Cardinal;
|
|
begin
|
|
case AVersion of
|
|
ive2_2:
|
|
Result := 0;
|
|
ive2_3:
|
|
begin
|
|
{ The 'Extended header size', currently 6 or 10 bytes, excludes itself. }
|
|
Result := 6;
|
|
if hefCRCDataPresent in Flags then
|
|
Inc(Result, 4);
|
|
end;
|
|
ive2_4:
|
|
begin
|
|
Result := 6;
|
|
if hefTagIsAnUpdate in Flags then
|
|
Inc(Result, 1);
|
|
if hefCRCDataPresent in Flags then
|
|
Inc(Result, 6);
|
|
if hefTagRestrictions in Flags then
|
|
Inc(Result, 2);
|
|
end;
|
|
else
|
|
Result := 0;
|
|
ID3Error(RsEID3UnknownVersion, Controller);
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3ExtendedHeader.Read;
|
|
var
|
|
LSize: Cardinal = 0;
|
|
LFlag: Byte = 0;
|
|
FlagDataLength: Byte = 0;
|
|
begin
|
|
Reset;
|
|
|
|
{ Controller.Version is the actual version of the stream; Controller.ReadVersion
|
|
is the version it's transformed in _after_ reading the data from the stream }
|
|
case Controller.Version of
|
|
ive2_2:
|
|
; { Do nothing }
|
|
ive2_3:
|
|
with Stream do
|
|
begin
|
|
ReadFixedNumber(LSize);
|
|
|
|
BeginReadFrame(LSize);
|
|
try
|
|
{ Flags:
|
|
|
|
%x0000000 00000000 x - CRC data present
|
|
}
|
|
Read(LFlag, 1);
|
|
if LFlag and $80 > 0 then
|
|
Include(FFlags, hefCRCDataPresent);
|
|
|
|
{ Not used: }
|
|
Read(LFlag, 1);
|
|
|
|
{ Size of padding }
|
|
ReadFixedNumber(FSizeOfPadding);
|
|
|
|
if hefCRCDataPresent in FFlags then
|
|
{ Total frame CRC }
|
|
ReadFixedNumber(FTotalFrameCRC);
|
|
finally
|
|
EndReadFrame;
|
|
end;
|
|
end;
|
|
ive2_4:
|
|
with Stream do
|
|
begin
|
|
ReadSyncSafeInteger(LSize);
|
|
|
|
{ LSize is the size of the whole extended header, thus including the
|
|
just read 4 bytes. An extended header can never have a size of fewer
|
|
than six bytes}
|
|
|
|
if LSize < 6 then
|
|
Exit;
|
|
|
|
BeginReadFrame(LSize - 4);
|
|
try
|
|
{ Nr of flag bytes; always 1 in v2.4 }
|
|
Read(FlagDataLength, 1);
|
|
|
|
{ Flags:
|
|
|
|
%0bcd0000 b - Tag is an update
|
|
c - CRC data present
|
|
d - Tag restrictions
|
|
}
|
|
Read(LFlag, 1);
|
|
|
|
if LFlag and $40 > 0 then
|
|
Include(FFlags, hefTagIsAnUpdate);
|
|
if LFlag and $20 > 0 then
|
|
Include(FFlags, hefCRCDataPresent);
|
|
if LFlag and $10 > 0 then
|
|
Include(FFlags, hefTagRestrictions);
|
|
|
|
if hefTagIsAnUpdate in FFlags then
|
|
begin
|
|
Read(FlagDataLength, 1);
|
|
{ Expect FlagDataLength to be 0 }
|
|
end;
|
|
if hefCRCDataPresent in FFlags then
|
|
begin
|
|
Read(FlagDataLength, 1);
|
|
{ Expect FlagDataLength to be 5 }
|
|
ReadSyncSafeInteger(FTotalFrameCRC, 5);
|
|
end;
|
|
if hefTagRestrictions in FFlags then
|
|
begin
|
|
Read(FlagDataLength, 1);
|
|
{ Expect FlagDataLength to be 1 }
|
|
Read(LFlag, 1);
|
|
{ Flags:
|
|
|
|
%ppqrrstt p - Tag size restrictions
|
|
q - Text encoding restrictions
|
|
r - Text fields size restrictions
|
|
s - Image encoding restrictions
|
|
t - Image size restrictions
|
|
}
|
|
with FRestrictions do
|
|
begin
|
|
RTagSize := TJvID3TagSizeRestriction((LFlag shr 6) and 3);
|
|
RTextEncoding := TJvID3TextEncodingRestriction((LFlag shr 5) and 1);
|
|
RTextFieldsSize := TJvID3TextFieldsSizeRestriction((LFlag shr 3) and 3);
|
|
RImageEncoding := TJvID3ImageEncodingRestriction((LFlag shr 2) and 1);
|
|
RImageSize := TJvID3ImageSizeRestriction(LFlag and 3);
|
|
end;
|
|
end;
|
|
finally
|
|
EndReadFrame;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3ExtendedHeader.Reset;
|
|
begin
|
|
FTotalFrameCRC := 0;
|
|
FSizeOfPadding := 0;
|
|
FFlags := [];
|
|
end;
|
|
|
|
procedure TJvID3ExtendedHeader.SetFlags(const Value: TJvID3HeaderExtendedFlags);
|
|
var
|
|
ChangedFlags: TJvID3HeaderExtendedFlags;
|
|
begin
|
|
if FFlags <> Value then
|
|
begin
|
|
ChangedFlags := FFlags + Value - (FFlags * Value);
|
|
|
|
{ hefCRCDataPresent is currently not supported }
|
|
if (hefCRCDataPresent in ChangedFlags) and (hefCRCDataPresent in Value) then
|
|
ID3Error(RsEControllerDoesNotSupportCRC, Controller);
|
|
|
|
FFlags := Value;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3ExtendedHeader.Write;
|
|
var
|
|
LFlag: Byte;
|
|
FlagDataLength: Byte;
|
|
LExtendedHeaderSize: Cardinal;
|
|
begin
|
|
LExtendedHeaderSize := GetSizeForVersion(Controller.WriteVersion);
|
|
|
|
case Controller.WriteVersion of
|
|
ive2_2:
|
|
; { Do nothing }
|
|
ive2_3:
|
|
with Stream do
|
|
begin
|
|
WriteFixedNumber(LExtendedHeaderSize);
|
|
|
|
BeginWriteFrame(LExtendedHeaderSize);
|
|
try
|
|
{ Flags:
|
|
|
|
%x0000000 00000000 x - CRC data present
|
|
}
|
|
LFlag := 0;
|
|
if hefCRCDataPresent in Flags then
|
|
Inc(LFlag, $80);
|
|
Write(LFlag, 1);
|
|
|
|
{ Not used }
|
|
LFlag := 0;
|
|
Write(LFlag, 1);
|
|
|
|
{ Size of padding }
|
|
WriteFixedNumber(FSizeOfPadding);
|
|
|
|
if hefCRCDataPresent in FFlags then
|
|
{ Total frame CRC }
|
|
WriteFixedNumber(FTotalFrameCRC);
|
|
finally
|
|
EndWriteFrame;
|
|
end;
|
|
end;
|
|
ive2_4:
|
|
with Stream do
|
|
begin
|
|
WriteSyncSafeInteger(LExtendedHeaderSize);
|
|
{ LExtendedHeaderSize is the size of the whole extended header, thus
|
|
including the just read 4 bytes }
|
|
|
|
BeginWriteFrame(LExtendedHeaderSize - 4);
|
|
try
|
|
{ Nr of flag bytes; always 1 in v2.4 }
|
|
FlagDataLength := 1;
|
|
Write(FlagDataLength, 1);
|
|
|
|
{ Flags:
|
|
|
|
%0bcd0000 b - Tag is an update
|
|
c - CRC data present
|
|
d - Tag restrictions
|
|
}
|
|
LFlag := 0;
|
|
if hefTagIsAnUpdate in Flags then
|
|
Inc(LFlag, $40);
|
|
if hefCRCDataPresent in Flags then
|
|
Inc(LFlag, $20);
|
|
if hefTagRestrictions in Flags then
|
|
Inc(LFlag, $10);
|
|
Write(LFlag, 1);
|
|
|
|
if hefTagIsAnUpdate in FFlags then
|
|
begin
|
|
{ FlagDataLength is always 0 for hefTagIsAnUpdate }
|
|
FlagDataLength := 0;
|
|
Write(FlagDataLength, 1);
|
|
end;
|
|
if hefCRCDataPresent in FFlags then
|
|
begin
|
|
{ FlagDataLength is always 5 for hefCRCDataPresent }
|
|
FlagDataLength := 5;
|
|
Write(FlagDataLength, 1);
|
|
|
|
WriteSyncSafeInteger(FTotalFrameCRC, 5);
|
|
end;
|
|
if hefTagRestrictions in FFlags then
|
|
begin
|
|
{ FlagDataLength is always 1 for hefTagIsAnUpdate }
|
|
FlagDataLength := 1;
|
|
Write(FlagDataLength, 1);
|
|
|
|
{ Flags:
|
|
|
|
%ppqrrstt p - Tag size restrictions
|
|
q - Text encoding restrictions
|
|
r - Text fields size restrictions
|
|
s - Image encoding restrictions
|
|
t - Image size restrictions
|
|
}
|
|
with FRestrictions do
|
|
LFlag :=
|
|
((Byte(RTagSize) and 3) shl 7) +
|
|
((Byte(RTextEncoding) and 1) shl 5) +
|
|
((Byte(RTextFieldsSize) and 3) shl 3) +
|
|
((Byte(RImageEncoding) and 1) shl 2) +
|
|
(Byte(RImageSize) and 3);
|
|
Write(LFlag, 1);
|
|
end;
|
|
finally
|
|
EndWriteFrame;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
//=== { TJvID3FileInfo } =====================================================
|
|
|
|
procedure TJvID3FileInfo.Calc;
|
|
const
|
|
CID3v1Size: array [Boolean] of Integer = (0, 128);
|
|
var
|
|
Tmp: Extended;
|
|
begin
|
|
if FAudioSize = 0 then
|
|
{ No vbr tag found, so we calculate the audio size }
|
|
FAudioSize := FFileSize - FHeaderFoundAt - CID3v1Size[FHasID3v1Tag];
|
|
|
|
if (FAudioSize > 0) and (FFrameCount > 0) then
|
|
begin
|
|
{ We've found a vbr tag (with enough info) }
|
|
Tmp := FAudioSize / FFrameCount;
|
|
FFrameLengthInBytes := Round(Tmp);
|
|
|
|
{ Determine average bitrate }
|
|
Tmp := FSamplingRateFrequency * Tmp / CLayerArray[Layer];
|
|
if Version in [mvVersion2, mvVersion25] then
|
|
Tmp := Tmp / 2;
|
|
|
|
FBitrate := Round(Tmp);
|
|
FLengthInSec := Trunc((FAudioSize * 8) / (1000 * Tmp));
|
|
end
|
|
else
|
|
if FBitrate > 0 then
|
|
FLengthInSec := Trunc((FAudioSize * 8) / (1000 * FBitrate));
|
|
|
|
if FFrameLengthInBytes = 0 then
|
|
begin
|
|
{ Didn't calc the FFrameLengthInBytes yet }
|
|
Tmp := 0;
|
|
if (FBitrate <> CFreeBitrate) and (FSamplingRateFrequency > 0) then
|
|
begin
|
|
Tmp := CLayerArray[Layer] * FBitrate / FSamplingRateFrequency + FPaddingLength;
|
|
if Version in [mvVersion2, mvVersion25] then
|
|
Tmp := Tmp / 2;
|
|
end;
|
|
|
|
if Tmp > 0 then
|
|
begin
|
|
FFrameCount := Round(FAudioSize / Tmp);
|
|
FFrameLengthInBytes := Round(Tmp);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TJvID3FileInfo.GetIsValid: Boolean;
|
|
begin
|
|
Result := (FHeaderFoundAt >= 0) and (FLayer <> mlNotDefined) and (FVersion <> mvReserved);
|
|
end;
|
|
|
|
procedure TJvID3FileInfo.ParseMPEGTag(AMPEGTag: PAnsiChar);
|
|
var
|
|
LHasPadding: Boolean;
|
|
B: Byte;
|
|
begin
|
|
{ Most info from http://www.dv.co.yu/mpgscript/mpeghdr.htm }
|
|
|
|
{ AAAAAAAA AAABBCCD EEEEFFGH IIJJKLMM -> bits
|
|
|
|
A 11 (31-21) Frame sync (all bits set)
|
|
B 2 (20,19) MPEG Audio version ID
|
|
00 - MPEG Version 2.5 (unofficial)
|
|
01 - reserved
|
|
10 - MPEG Version 2 (ISO/IEC 13818-3)
|
|
11 - MPEG Version 1 (ISO/IEC 11172-3)
|
|
C 2 (18,17) Layer description
|
|
00 - reserved
|
|
01 - Layer III
|
|
10 - Layer II
|
|
11 - Layer I
|
|
D 1 (16) Protection bit
|
|
0 - Protected by CRC (16bit crc follows header)
|
|
1 - Not protected
|
|
E 4 (15,12) Bitrate index
|
|
|
|
bits V1,L1 V1,L2 V1,L3 V2,L1 V2, L2 & L3
|
|
0000 free free free free free
|
|
0001 32 32 32 32 8
|
|
0010 64 48 40 48 16
|
|
0011 96 56 48 56 24
|
|
0100 128 64 56 64 32
|
|
0101 160 80 64 80 40
|
|
0110 192 96 80 96 48
|
|
0111 224 112 96 112 56
|
|
1000 256 128 112 128 64
|
|
1001 288 160 128 144 80
|
|
1010 320 192 160 160 96
|
|
1011 352 224 192 176 112
|
|
1100 384 256 224 192 128
|
|
1101 416 320 256 224 144
|
|
1110 448 384 320 256 160
|
|
1111 bad bad bad bad bad
|
|
|
|
NOTES: All values are in kbps
|
|
V1 - MPEG Version 1
|
|
V2 - MPEG Version 2 and Version 2.5
|
|
L1 - Layer I
|
|
L2 - Layer II
|
|
L3 - Layer III
|
|
"free" means free format.
|
|
"bad" means that this is not an allowed value
|
|
|
|
F 2 (11,10) Sampling rate frequency index (values are in Hz) bits
|
|
MPEG1 MPEG2 MPEG2.5
|
|
00 44100 22050 11025
|
|
01 48000 24000 12000
|
|
10 32000 16000 8000
|
|
11 reserv. reserv. reserv.
|
|
G 1 (9) Padding bit
|
|
0 - frame is not padded
|
|
1 - frame is padded with one extra slot
|
|
H 1 (8) Private bit.
|
|
I 2 (7,6) Channel Mode
|
|
00 - Stereo
|
|
01 - Joint stereo (Stereo)
|
|
10 - Dual channel (2 mono channels)
|
|
11 - Single channel (Mono)
|
|
J 2 (5,4) Mode extension (Only if Joint stereo)
|
|
Layer I and II Layer III
|
|
value Intensity stereo MS stereo
|
|
00 bands 4 to 31 off off
|
|
01 bands 8 to 31 on off
|
|
10 bands 12 to 31 off on
|
|
11 bands 16 to 31 on on
|
|
K 1 (3) Copyright
|
|
0 - Audio is not copyrighted
|
|
1 - Audio is copyrighted
|
|
L 1 (2) Original
|
|
0 - Copy of original media
|
|
1 - Original media
|
|
M 2 (1,0) Emphasis
|
|
00 - none
|
|
01 - 50/15 ms
|
|
10 - reserved
|
|
11 - CCIT J.17
|
|
}
|
|
|
|
{ Note: we assume a Reset is done before Parse is called, so we can
|
|
do quick exits }
|
|
|
|
{ D }
|
|
B := PByte(AMPEGTag + 1)^;
|
|
if B and $1 = 0 then
|
|
Include(FBits, mbProtection);
|
|
{ C }
|
|
B := B shr 1;
|
|
FLayer := TJvMPEGLayer(B and $3);
|
|
{ B }
|
|
B := B shr 2;
|
|
FVersion := TJvMPEGVersion(B and $3);
|
|
if (FLayer = mlNotDefined) or (FVersion = mvReserved) then
|
|
Exit;
|
|
|
|
B := PByte(AMPEGTag + 2)^;
|
|
{ H }
|
|
if B and $1 > 0 then
|
|
Include(FBits, mbPrivate);
|
|
B := B shr 1;
|
|
{ G }
|
|
LHasPadding := B and $1 > 0;
|
|
B := B shr 1;
|
|
{ F }
|
|
FSamplingRateFrequency := CSamplingFrequency[Version, B and $3];
|
|
B := B shr 2;
|
|
{ E }
|
|
FBitrate := CBitrate[CMapBitrate[Version in [mvVersion2, mvVersion25], Layer], B and $F];
|
|
if FBitrate = CBadBitrate then
|
|
Exit;
|
|
|
|
B := PByte(AMPEGTag + 3)^;
|
|
{ M }
|
|
FEmphasis := TJvMPEGEmphasis(B and $3);
|
|
B := B shr 2;
|
|
{ L }
|
|
if B and $1 > 0 then
|
|
Include(FBits, mbOriginal);
|
|
B := B shr 1;
|
|
{ K }
|
|
if B and $1 > 0 then
|
|
Include(FBits, mbCopyrighted);
|
|
B := B shr 1;
|
|
{ J }
|
|
FModeExtension := TJvMPEGModeExtension(B and $3);
|
|
B := B shr 2;
|
|
{ I }
|
|
FChannelMode := TJvMPEGChannelMode(B and $3);
|
|
|
|
{ Calculate some stuff }
|
|
if LHasPadding then
|
|
begin
|
|
if Layer = mlLayerI then
|
|
FPaddingLength := 4
|
|
else
|
|
FPaddingLength := 1;
|
|
end
|
|
else
|
|
FPaddingLength := 0;
|
|
end;
|
|
|
|
procedure TJvID3FileInfo.ParseVbrTag(AMPEGTag: PAnsiChar);
|
|
const
|
|
VBRTag_Xing: array [0..3] of AnsiChar = AnsiString('Xing'); { Do not change case }
|
|
VBRTag_Info: array [0..3] of AnsiChar = AnsiString('Info'); { Do not change case }
|
|
FRAMES_FLAG = $0001;
|
|
BYTES_FLAG = $0002;
|
|
//TOC_FLAG = $0004;
|
|
var
|
|
HeadFlags: Integer;
|
|
begin
|
|
{ Now try to find the Xing or Info tag }
|
|
|
|
{ maximum bytes needed is currently: 4 + 32 + 4 + 4 + 4 + 4 = 52 }
|
|
if Version = mvVersion1 then
|
|
begin
|
|
if ChannelMode <> mcSingleChannel then
|
|
Inc(AMPEGTag, 32 + 4)
|
|
else
|
|
Inc(AMPEGTag, 17 + 4)
|
|
end
|
|
else
|
|
begin
|
|
if ChannelMode <> mcSingleChannel then
|
|
Inc(AMPEGTag, 17 + 4)
|
|
else
|
|
Inc(AMPEGTag, 9 + 4);
|
|
end;
|
|
|
|
if (PLongint(AMPEGTag)^ <> Longint(VBRTag_Xing)) and
|
|
(PLongint(AMPEGTag)^ <> Longint(VBRTag_Info)) then
|
|
Exit;
|
|
Inc(AMPEGTag, 4);
|
|
|
|
{ (rb) Now always true?? }
|
|
FIsVBR := True;
|
|
|
|
HeadFlags := ReverseBytes(PInteger(AMPEGTag)^);
|
|
Inc(AMPEGTag, 4);
|
|
|
|
if HeadFlags and FRAMES_FLAG > 0 then
|
|
begin
|
|
FFrameCount := ReverseBytes(PInteger(AMPEGTag)^);
|
|
Inc(AMPEGTag, 4);
|
|
end;
|
|
|
|
if HeadFlags and BYTES_FLAG > 0 then
|
|
FAudioSize := ReverseBytes(PInteger(AMPEGTag)^);
|
|
end;
|
|
|
|
procedure TJvID3FileInfo.Read(AStream: TStream; const Offset: Int64);
|
|
const
|
|
CID3v1Tag = AnsiString('TAG'); { do not change case }
|
|
CTagSize = 128;
|
|
CTagIDSize = 3;
|
|
CMPEGTagSize = 52;
|
|
var
|
|
TagID: array [0..CTagIDSize - 1] of AnsiChar;
|
|
MPEGTag: array [0..CMPEGTagSize - 1] of AnsiChar;
|
|
begin
|
|
Reset;
|
|
|
|
FHeaderFoundAt := SearchSync(AStream, Offset, MPEGTag{%H-}, CMPEGTagSize);
|
|
if FHeaderFoundAt < 0 then
|
|
Exit;
|
|
|
|
ParseMPEGTag(@MPEGTag);
|
|
ParseVbrTag(@MPEGTag);
|
|
|
|
if FFileSize = 0 then
|
|
FFileSize := AStream.Size;
|
|
|
|
if (FAudioSize = 0) and (FFileSize >= 128) then
|
|
begin
|
|
{ Need to determine if the file has an ID3v1 tag }
|
|
AStream.Seek(-CTagSize, soFromEnd);
|
|
FHasID3v1Tag := (AStream.Read(TagID{%H-}, CTagIDSize) = CTagIDSize) and (TagID = CID3v1Tag);
|
|
end;
|
|
|
|
{ We now know enough to calculate the rest }
|
|
Calc;
|
|
end;
|
|
|
|
procedure TJvID3FileInfo.Reset;
|
|
begin
|
|
FAudioSize := 0;
|
|
FBitrate := 0;
|
|
FBits := [];
|
|
FChannelMode := Low(TJvMPEGChannelMode);
|
|
FEmphasis := Low(TJvMPEGEmphasis);
|
|
FFileSize := 0;
|
|
FFrameCount := 0;
|
|
FFrameLengthInBytes := 0;
|
|
FHasID3v1Tag := False;
|
|
FHeaderFoundAt := -1;
|
|
FIsVBR := False;
|
|
FLayer := Low(TJvMPEGLayer);
|
|
FLengthInSec := 0;
|
|
FModeExtension := Low(TJvMPEGModeExtension);
|
|
FSamplingRateFrequency := 0;
|
|
FVersion := Low(TJvMPEGVersion);
|
|
end;
|
|
|
|
|
|
//=== { TJvID3Frame } ========================================================
|
|
|
|
constructor TJvID3Frame.Create(AOwner: TComponent; const AFrameID: TJvID3FrameID;
|
|
const AFrameIDStr: AnsiString);
|
|
begin
|
|
inherited Create(AOwner);
|
|
|
|
CheckFrameID(AFrameID);
|
|
|
|
FFrameID := AFrameID;
|
|
FrameName := AFrameIDStr;
|
|
|
|
FEncoding := ienISO_8859_1;
|
|
end;
|
|
|
|
destructor TJvID3Frame.Destroy;
|
|
begin
|
|
if (FController <> nil) and (FFrames <> nil) then
|
|
FFrames.Remove(Self);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TJvID3Frame.Assign(Source: TPersistent);
|
|
begin
|
|
if Source = nil then
|
|
Clear
|
|
else
|
|
if Source is TJvID3Frame then
|
|
begin
|
|
FFlags := TJvID3Frame(Source).Flags;
|
|
FEncryptionID := TJvID3Frame(Source).EncryptionID;
|
|
FGroupID := TJvID3Frame(Source).GroupID;
|
|
FDecompressedSize := TJvID3Frame(Source).FDecompressedSize;
|
|
FEncoding := TJvID3Frame(Source).Encoding;
|
|
{ v2.4 }
|
|
FDataLengthIndicator := TJvID3Frame(Source).FDataLengthIndicator;
|
|
|
|
Changed;
|
|
end
|
|
else
|
|
inherited Assign(Source);
|
|
end;
|
|
|
|
class function TJvID3Frame.CanAddFrame(AController: TJvID3Controller;
|
|
AFrameID: TJvID3FrameID): Boolean;
|
|
begin
|
|
Result := False;
|
|
end;
|
|
|
|
procedure TJvID3Frame.Changed;
|
|
begin
|
|
FFrameSize := GetFrameSize(Encoding);
|
|
DataChanged;
|
|
end;
|
|
|
|
procedure TJvID3Frame.ChangeToVersion(const ANewVersion: TJvID3Version);
|
|
begin
|
|
{ Do nothing }
|
|
end;
|
|
|
|
function TJvID3Frame.CheckFrame(const HandleError: TJvID3HandleError): Boolean;
|
|
begin
|
|
Result := False;
|
|
end;
|
|
|
|
procedure TJvID3Frame.CheckFrameID(const AFrameID: TJvID3FrameID);
|
|
begin
|
|
if AFrameID in [fiErrorFrame, fiPaddingFrame] then
|
|
ErrorFmt(RsEID3FrameIDNotSupported, [ID3_FrameIDToString(AFrameID)]);
|
|
|
|
if TJvID3Controller.GetFrameClass(AFrameID) <> ClassType then
|
|
ErrorFmt(RsEID3FrameIDNotSupported, [ID3_FrameIDToString(AFrameID)]);
|
|
end;
|
|
|
|
procedure TJvID3Frame.CheckFrameIDStr(const S: AnsiString);
|
|
var
|
|
LFrameID: TJvID3FrameID;
|
|
begin
|
|
LFrameID := ID3_StringToFrameID(S);
|
|
if LFrameID in [fiErrorFrame, fiPaddingFrame] then
|
|
ErrorFmt(RsEID3FrameIDStrNotSupported, [S]);
|
|
|
|
if TJvID3Controller.GetFrameClass(LFrameID) <> ClassType then
|
|
ErrorFmt(RsEID3FrameIDStrNotSupported, [S]);
|
|
end;
|
|
|
|
function TJvID3Frame.CheckIsUnique: Boolean;
|
|
begin
|
|
Result := FFrames.CheckIsUnique(Self);
|
|
end;
|
|
|
|
procedure TJvID3Frame.Clear;
|
|
begin
|
|
Changed;
|
|
end;
|
|
|
|
procedure TJvID3Frame.DataChanged;
|
|
begin
|
|
if Assigned(FController) then
|
|
FController.ID3Event(ideFrameChange, PtrInt(Self));
|
|
end;
|
|
|
|
procedure TJvID3Frame.Error(const Msg: string);
|
|
begin
|
|
ID3ErrorFmt(RsEErrorInFrame, [FrameName, Name, Msg], Controller);
|
|
end;
|
|
|
|
procedure TJvID3Frame.ErrorFmt(const Msg: string;
|
|
const Args: array of const);
|
|
begin
|
|
Error(Format(Msg, Args));
|
|
end;
|
|
|
|
function TJvID3Frame.GetFrameIDStrForVersion(
|
|
const Version: TJvID3Version): AnsiString;
|
|
begin
|
|
if FFrameIDStr = '' then
|
|
case Version of
|
|
ive2_2:
|
|
Result := ID3_FrameIDToString(FrameID, 3);
|
|
ive2_3, ive2_4:
|
|
Result := ID3_FrameIDToString(FrameID, 4);
|
|
else
|
|
Error(RsEID3UnknownVersion);
|
|
end
|
|
else
|
|
Result := FFrameIDStr;
|
|
end;
|
|
|
|
function TJvID3Frame.GetFrameName: AnsiString;
|
|
begin
|
|
Result := GetFrameIDStrForVersion(ive2_3);
|
|
end;
|
|
|
|
function TJvID3Frame.GetIndex: Integer;
|
|
begin
|
|
if FFrames <> nil then
|
|
Result := FFrames.IndexOf(Self)
|
|
else
|
|
Result := -1;
|
|
end;
|
|
|
|
function TJvID3Frame.GetIsEmpty: Boolean;
|
|
begin
|
|
Result := True;
|
|
end;
|
|
|
|
function TJvID3Frame.GetStream: TJvID3Stream;
|
|
begin
|
|
if not Assigned(FController) then
|
|
Error(RsEID3NoController);
|
|
|
|
if icsUsingTempStream in FController.FState then
|
|
Result := FController.FTempStream
|
|
else
|
|
Result := FController.FStream;
|
|
end;
|
|
|
|
function TJvID3Frame.MustWriteAsUTF: Boolean;
|
|
begin
|
|
Result := False;
|
|
end;
|
|
|
|
procedure TJvID3Frame.Read;
|
|
var
|
|
LFrameSize: Integer;
|
|
begin
|
|
{ Note: don't use 'with Stream do' for the whole procedure, because calling
|
|
BeginUseTempStream changes the value of property Stream
|
|
}
|
|
|
|
ReadFrameHeader;
|
|
|
|
if not Stream.CanRead(FrameSize) then
|
|
begin
|
|
{ Serious error, skip the rest of the stream }
|
|
Stream.BeginReadFrame(Stream.BytesTillEndOfTag);
|
|
Stream.EndReadFrame;
|
|
end
|
|
else
|
|
if (Controller.Version = ive2_4) and (fhfUnsynchronisationApplied in FFlags) then
|
|
begin
|
|
{ Stream is unsynchronised, remove the unsynchronisation scheme and
|
|
read the frame }
|
|
|
|
Stream.BeginReadFrame(FrameSize);
|
|
try
|
|
Controller.RemoveUnsynchronisationSchemeToTempStream(FrameSize);
|
|
finally
|
|
Stream.EndReadFrame;
|
|
end;
|
|
|
|
LFrameSize := Controller.GetTempStreamSize;
|
|
|
|
Controller.BeginUseTempStream;
|
|
try
|
|
Stream.BeginReadFrame(LFrameSize);
|
|
try
|
|
//Self.Clear;
|
|
ReadFrame;
|
|
finally
|
|
Stream.EndReadFrame;
|
|
end;
|
|
finally
|
|
Controller.EndUseTempStream;
|
|
end;
|
|
end
|
|
else
|
|
with Stream do
|
|
begin
|
|
BeginReadFrame(FrameSize);
|
|
try
|
|
//Self.Clear;
|
|
ReadFrame;
|
|
finally
|
|
EndReadFrame;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3Frame.ReadEncoding;
|
|
begin
|
|
Stream.ReadEnc(FEncoding);
|
|
end;
|
|
|
|
procedure TJvID3Frame.ReadFrameHeader;
|
|
var
|
|
Flag0: Byte = 0;
|
|
Flag1: Byte = 0;
|
|
begin
|
|
case Controller.Version of
|
|
ive2_2:
|
|
with Stream do
|
|
begin
|
|
{ Frame ID $xx xx xx (three characters) // read in TJvID3Frames.Read
|
|
Size $xx xx xx
|
|
}
|
|
|
|
ReadFixedNumber3(FFrameSize);
|
|
|
|
FFlags := [];
|
|
end;
|
|
ive2_3:
|
|
with Stream do
|
|
begin
|
|
{ Frame ID $xx xx xx xx (four characters) // read in TJvID3Frames.Read
|
|
Size $xx xx xx xx
|
|
Flags $xx xx
|
|
}
|
|
|
|
ReadFixedNumber(FFrameSize);
|
|
|
|
{ Flags:
|
|
|
|
%abc00000 %ijk00000 a - Tag alter preservation i - Compression
|
|
b - File alter preservation j - Encryption
|
|
c - Read only k - Grouping identity
|
|
}
|
|
|
|
FFlags := [];
|
|
|
|
Read(Flag0, 1);
|
|
Read(Flag1, 1);
|
|
|
|
if (Flag0 and $80) > 0 then
|
|
Include(FFlags, fhfOnTagAlterDiscardFrame);
|
|
if (Flag0 and $40) > 0 then
|
|
Include(FFlags, fhfOnFileAlterDiscardFrame);
|
|
if (Flag0 and $20) > 0 then
|
|
Include(FFlags, fhfReadOnly);
|
|
|
|
if (Flag1 and $80) > 0 then
|
|
Include(FFlags, fhfIsCompressed);
|
|
if (Flag1 and $40) > 0 then
|
|
Include(FFlags, fhfIsEncrypted);
|
|
if (Flag1 and $20) > 0 then
|
|
Include(FFlags, fhfContainsGroupInformation);
|
|
|
|
if fhfIsCompressed in Flags then
|
|
ReadFixedNumber(FDecompressedSize);
|
|
|
|
if fhfIsEncrypted in Flags then
|
|
Read(FEncryptionID, 1);
|
|
|
|
if fhfContainsGroupInformation in Flags then
|
|
Read(FGroupID, 1);
|
|
end;
|
|
ive2_4:
|
|
with Stream do
|
|
begin
|
|
{ Frame ID $xx xx xx xx (four characters) // read in TJvID3Frames.Read
|
|
Size 4 * %0xxxxxxx
|
|
Flags $xx xx
|
|
}
|
|
ReadSyncSafeInteger(FFrameSize, 4);
|
|
FFlags := [];
|
|
|
|
{ Flags:
|
|
|
|
%0abc0000 %0h00kmnp a - Tag alter preservation k - Compression
|
|
b - File alter preservation m - Encryption
|
|
c - Read only n - Unsynchronisation
|
|
h - Grouping identity p - Data length indicator
|
|
}
|
|
|
|
Read(Flag0, 1);
|
|
Read(Flag1, 1);
|
|
|
|
if (Flag0 and $40) > 0 then
|
|
Include(FFlags, fhfOnTagAlterDiscardFrame);
|
|
if (Flag0 and $20) > 0 then
|
|
Include(FFlags, fhfOnFileAlterDiscardFrame);
|
|
if (Flag0 and $10) > 0 then
|
|
Include(FFlags, fhfReadOnly);
|
|
|
|
if (Flag1 and $40) > 0 then
|
|
Include(FFlags, fhfContainsGroupInformation);
|
|
if (Flag1 and $08) > 0 then
|
|
Include(FFlags, fhfIsCompressed);
|
|
if (Flag1 and $04) > 0 then
|
|
Include(FFlags, fhfIsEncrypted);
|
|
if (Flag1 and $02) > 0 then
|
|
Include(FFlags, fhfUnsynchronisationApplied);
|
|
if (Flag1 and $01) > 0 then
|
|
Include(FFlags, fhfDataLengthIndicator);
|
|
|
|
if fhfContainsGroupInformation in Flags then
|
|
Read(FGroupID, 1);
|
|
|
|
if fhfIsEncrypted in Flags then
|
|
Read(FEncryptionID, 1);
|
|
|
|
if fhfDataLengthIndicator in Flags then
|
|
{ TODO : why , 4? }
|
|
ReadSyncSafeInteger(FDataLengthIndicator, 4);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TJvID3Frame.SameUniqueIDAs(const Frame: TJvID3Frame): Boolean;
|
|
begin
|
|
Result := False;
|
|
end;
|
|
|
|
procedure TJvID3Frame.SetController(const AController: TJvID3Controller);
|
|
begin
|
|
if AController <> FController then
|
|
begin
|
|
if Assigned(FController) then
|
|
FController.FFrames.Remove(Self);
|
|
|
|
FController := AController;
|
|
|
|
if Assigned(FController) then
|
|
FController.FFrames.Add(Self);
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3Frame.SetEncoding(const Value: TJvID3Encoding);
|
|
begin
|
|
if FEncoding <> Value then
|
|
begin
|
|
FEncoding := Value;
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3Frame.SetFlags(const Value: TJvID3FrameHeaderFlags);
|
|
var
|
|
ChangedFlags: TJvID3FrameHeaderFlags;
|
|
begin
|
|
if FFlags <> Value then
|
|
begin
|
|
ChangedFlags := FFlags + Value - (FFlags * Value);
|
|
|
|
{ fhfIsCompressed is currently not supported }
|
|
if (fhfIsCompressed in ChangedFlags) and (fhfIsCompressed in Value) then
|
|
ID3Error(RsEControllerDoesNotSupportCompression, Controller);
|
|
|
|
{ fhfIsEncrypted is currently not supported }
|
|
if (fhfIsEncrypted in ChangedFlags) and (fhfIsEncrypted in Value) then
|
|
ID3Error(RsEControllerDoesNotSupportEncryption, Controller);
|
|
|
|
FFlags := Value;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3Frame.SetFrameID(const Value: TJvID3FrameID);
|
|
begin
|
|
{ TODO : Refresh designer while changing }
|
|
CheckFrameID(Value);
|
|
|
|
FFrameID := Value;
|
|
FFrameIDStr := '';
|
|
end;
|
|
|
|
procedure TJvID3Frame.SetFrameName(NewFrameName: AnsiString);
|
|
begin
|
|
{ TODO : Refresh designer while changing }
|
|
if NewFrameName = '' then
|
|
FFrameIDStr := ''
|
|
else
|
|
begin
|
|
{ Force uppercase }
|
|
NewFrameName := AnsiUpperCase(NewFrameName);
|
|
|
|
CheckFrameIDStr(NewFrameName);
|
|
FFrameID := ID3_StringToFrameID(NewFrameName);
|
|
if FFrameID = fiUnknownFrame then
|
|
FFrameIDStr := NewFrameName
|
|
else
|
|
FFrameIDStr := '';
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3Frame.SetIndex(const Value: Integer);
|
|
begin
|
|
if FFrames <> nil then
|
|
FFrames.SetFrameIndex(Self, Value)
|
|
end;
|
|
|
|
function TJvID3Frame.SupportsVersion(const AVersion: TJvID3Version): Boolean;
|
|
begin
|
|
Result := AVersion in CSupportedVersions;
|
|
end;
|
|
|
|
procedure TJvID3Frame.UpdateFrameSize;
|
|
begin
|
|
FFrameSize := GetFrameSize(Encoding);
|
|
end;
|
|
|
|
procedure TJvID3Frame.Write;
|
|
var
|
|
LFrameSize: Cardinal;
|
|
begin
|
|
{ Note: don't use 'with Stream do' for the whole procedure, because calling
|
|
BeginUseTempStream changes the value of property Stream
|
|
}
|
|
|
|
if not SupportsVersion(Controller.WriteVersion) then
|
|
Exit;
|
|
|
|
if Controller.WriteEncodingAs = ifeAuto then
|
|
begin
|
|
if Self.MustWriteAsUTF then
|
|
Self.Encoding := ienUTF_16
|
|
else
|
|
Self.Encoding := ienISO_8859_1
|
|
end;
|
|
|
|
Stream.SourceEncoding := Self.Encoding;
|
|
|
|
WriteID;
|
|
|
|
{ Get the frame size, with the encoding as the stream }
|
|
LFrameSize := GetFrameSize(Stream.DestEncoding);
|
|
|
|
if (Controller.WriteVersion = ive2_4) and
|
|
(fhfUnsynchronisationApplied in FFlags) then
|
|
begin
|
|
{ Write the frame to the temporary stream }
|
|
Controller.BeginUseTempStream;
|
|
try
|
|
Stream.BeginWriteFrame(LFrameSize);
|
|
try
|
|
WriteFrame;
|
|
finally
|
|
Stream.EndWriteFrame;
|
|
end;
|
|
|
|
{ Retrieve the frame size _before_ unsynchronisation }
|
|
FDataLengthIndicator := Controller.GetTempStreamSize;
|
|
|
|
Controller.ApplyUnsynchronisationSchemeOnCurrentStream;
|
|
finally
|
|
Controller.EndUseTempStream;
|
|
end;
|
|
|
|
{ Retrieve the frame size _after_ unsynchronisation }
|
|
LFrameSize := Controller.GetTempStreamSize;
|
|
|
|
WriteFrameHeader(LFrameSize);
|
|
|
|
Controller.WriteTempStream;
|
|
end
|
|
else
|
|
with Stream do
|
|
begin
|
|
WriteFrameHeader(LFrameSize);
|
|
|
|
BeginWriteFrame(LFrameSize);
|
|
try
|
|
WriteFrame;
|
|
finally
|
|
EndWriteFrame;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3Frame.WriteEncoding;
|
|
begin
|
|
with Stream do
|
|
WriteEnc;
|
|
end;
|
|
|
|
procedure TJvID3Frame.WriteFrameHeader(const AFrameSize: Cardinal);
|
|
var
|
|
Flag0, Flag1: Byte;
|
|
begin
|
|
{ Note: A v2.3 or v2.3 frame size is written as 4 bytes, thus always fits
|
|
exactly in a Cardinal. A v2.2 frame size is written as 3 bytes }
|
|
case Controller.WriteVersion of
|
|
ive2_2:
|
|
if AFrameSize > $00FFFFFF then // = 16 MB
|
|
ID3Error(RsEFrameSizeTooBig, Self)
|
|
else
|
|
with Stream do
|
|
begin
|
|
{ Frame ID $xx xx xx (three characters) // Written in TJvID3Frame.Write
|
|
Size $xx xx xx
|
|
}
|
|
|
|
WriteFixedNumber3(AFrameSize);
|
|
end;
|
|
ive2_3:
|
|
with Stream do
|
|
begin
|
|
{ Frame ID $xx xx xx xx (four characters) // Written in TJvID3Frame.Write
|
|
Size $xx xx xx xx
|
|
Flags $xx xx
|
|
}
|
|
|
|
WriteFixedNumber(AFrameSize);
|
|
|
|
{ Flags:
|
|
|
|
%abc00000 %ijk00000 a - Tag alter preservation i - Compression
|
|
b - File alter preservation j - Encryption
|
|
c - Read only k - Grouping identity
|
|
}
|
|
|
|
Flag0 := 0;
|
|
Flag1 := 0;
|
|
|
|
if fhfOnTagAlterDiscardFrame in FFlags then
|
|
Inc(Flag0, $80);
|
|
if fhfOnFileAlterDiscardFrame in FFlags then
|
|
Inc(Flag0, $40);
|
|
if fhfReadOnly in FFlags then
|
|
Inc(Flag0, $20);
|
|
|
|
{ Compression is not supported }
|
|
//if fhfIsCompressed in FFlags then
|
|
// Inc(Flag1, $80);
|
|
{ Encryption is not supported }
|
|
//if fhfIsEncrypted in FFlags then
|
|
// Inc(Flag1, $40);
|
|
if fhfContainsGroupInformation in FFlags then
|
|
Inc(Flag1, $20);
|
|
|
|
Write(Flag0, 1);
|
|
Write(Flag1, 1);
|
|
|
|
{ Compression is not supported }
|
|
//if fhfIsCompressed in Flags then
|
|
// WriteFixedNumber(FDecompressedSize);
|
|
|
|
{ Encryption is not supported }
|
|
//if fhfIsEncrypted in Flags then
|
|
// Write(FEncryptionID, 1);
|
|
|
|
if fhfContainsGroupInformation in Flags then
|
|
Write(FGroupID, 1);
|
|
end;
|
|
ive2_4:
|
|
with Stream do
|
|
begin
|
|
{ Frame ID $xx xx xx xx (four characters) // read in TJvID3Frames.Read
|
|
Size 4 * %0xxxxxxx
|
|
Flags $xx xx
|
|
}
|
|
WriteSyncSafeInteger(AFrameSize, 4);
|
|
|
|
{ Flags:
|
|
|
|
%0abc0000 %0h00kmnp a - Tag alter preservation k - Compression
|
|
b - File alter preservation m - Encryption
|
|
c - Read only n - Unsynchronisation
|
|
h - Grouping identity p - Data length indicator
|
|
}
|
|
|
|
Flag0 := 0;
|
|
Flag1 := 0;
|
|
|
|
if fhfOnTagAlterDiscardFrame in FFlags then
|
|
Inc(Flag0, $40);
|
|
if fhfOnFileAlterDiscardFrame in FFlags then
|
|
Inc(Flag0, $20);
|
|
if fhfReadOnly in FFlags then
|
|
Inc(Flag0, $10);
|
|
|
|
if fhfContainsGroupInformation in FFlags then
|
|
Inc(Flag1, $40);
|
|
{ Compression is not supported }
|
|
//if fhfIsCompressed in FFlags then
|
|
// Inc(Flag1, $08);
|
|
{ Encryption is not supported }
|
|
//if fhfIsEncrypted in FFlags then
|
|
// Inc(Flag1, $04);
|
|
if fhfUnsynchronisationApplied in FFlags then
|
|
Inc(Flag1, $02);
|
|
if fhfDataLengthIndicator in FFlags then
|
|
Inc(Flag1, $01);
|
|
|
|
Write(Flag0, 1);
|
|
Write(Flag1, 1);
|
|
|
|
if fhfContainsGroupInformation in Flags then
|
|
Write(FGroupID, 1);
|
|
|
|
{ Encryption is not supported }
|
|
//if fhfIsEncrypted in Flags then
|
|
// Write(FEncryptionID, 1);
|
|
|
|
if fhfDataLengthIndicator in Flags then
|
|
WriteSyncSafeInteger(FDataLengthIndicator, 4);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3Frame.WriteID;
|
|
var
|
|
LFrameIDStr: AnsiString;
|
|
FrameIDLength: Byte;
|
|
begin
|
|
LFrameIDStr := GetFrameIDStrForVersion(Controller.WriteVersion);
|
|
FrameIDLength := GetFrameIDLength(Controller.WriteVersion);
|
|
|
|
if Length(LFrameIDStr) <> FrameIDLength then
|
|
begin
|
|
SetLength(LFrameIDStr, FrameIDLength);
|
|
FillChar(LFrameIDStr, FrameIDLength, #0);
|
|
end;
|
|
|
|
Stream.Write(PAnsiChar(LFrameIDStr)^, FrameIDLength);
|
|
end;
|
|
|
|
|
|
//=== { TJvID3Frames } =======================================================
|
|
|
|
procedure TJvID3Frames.Add(Frame: TJvID3Frame);
|
|
begin
|
|
CheckCanAddFrame(Frame.FrameID);
|
|
|
|
FList.Add(Frame);
|
|
Frame.FFrames := Self;
|
|
Frame.Controller := Controller;
|
|
Changed;
|
|
end;
|
|
|
|
procedure TJvID3Frames.AfterConstruction;
|
|
begin
|
|
FList := TList.Create;
|
|
inherited AfterConstruction;
|
|
end;
|
|
|
|
procedure TJvID3Frames.Assign(Source: TPersistent);
|
|
var
|
|
I: Integer;
|
|
Frame: TJvID3Frame;
|
|
begin
|
|
if Source is TJvID3Frames then
|
|
begin
|
|
Clear;
|
|
for I := 0 to TJvID3Frames(Source).FList.Count - 1 do
|
|
begin
|
|
Frame := Controller.AddFrame(TJvID3Frames(Source).Frames[I].FrameID);
|
|
Frame.FrameName := TJvID3Frames(Source).Frames[I].FrameName;
|
|
Frame.Assign(TJvID3Frames(Source).Frames[I]);
|
|
end;
|
|
end
|
|
else
|
|
inherited Assign(Source);
|
|
end;
|
|
|
|
procedure TJvID3Frames.BeforeDestruction;
|
|
begin
|
|
inherited BeforeDestruction;
|
|
if FList <> nil then
|
|
Clear;
|
|
FList.Free;
|
|
end;
|
|
|
|
procedure TJvID3Frames.Changed;
|
|
begin
|
|
if (FController <> nil) and not (csDestroying in FController.ComponentState) then
|
|
FController.ID3Event(ideFrameListChange, 0);
|
|
{if Assigned(OnChange) then OnChange(Self);}
|
|
end;
|
|
|
|
procedure TJvID3Frames.ChangeToVersion(const ANewVersion: TJvID3Version);
|
|
var
|
|
I: Integer;
|
|
begin
|
|
if not (ANewVersion in CSupportedVersions) then
|
|
ID3Error(RsEID3VersionNotSupported, Controller);
|
|
|
|
for I := Count - 1 downto 0 do
|
|
Frames[I].ChangeToVersion(ANewVersion);
|
|
|
|
for I := Count - 1 downto 0 do
|
|
if not Frames[I].SupportsVersion(ANewVersion) then
|
|
Frames[I].Free;
|
|
end;
|
|
|
|
procedure TJvID3Frames.CheckCanAddFrame(FrameID: TJvID3FrameID);
|
|
begin
|
|
if not FController.CanAddFrame(FrameID) then
|
|
ID3ErrorFmt(RsEID3AlreadyContainsFrame, [ID3_FrameIDToString(FrameID)]);
|
|
end;
|
|
|
|
function TJvID3Frames.CheckFrames(const HandleError: TJvID3HandleError): Boolean;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
Result := False;
|
|
{ Check whether the frames have correct parameters }
|
|
for I := 0 to Count - 1 do
|
|
if not Frames[I].CheckFrame(HandleError) then
|
|
Exit;
|
|
|
|
{ Check whether the frames are unique }
|
|
for I := Count - 1 downto 0 do
|
|
if not Frames[I].CheckIsUnique then
|
|
case HandleError of
|
|
heAutoCorrect:
|
|
Frames[I].Free;
|
|
heRaise:
|
|
Frames[I].Error(RsEID3DuplicateFrame);
|
|
else
|
|
Exit;
|
|
end;
|
|
|
|
Result := True;
|
|
end;
|
|
|
|
function TJvID3Frames.CheckIsUnique(Frame: TJvID3Frame): Boolean;
|
|
var
|
|
FoundFrame: TJvID3Frame = nil;
|
|
begin
|
|
Result := True;
|
|
if not Assigned(Frame) then
|
|
Exit;
|
|
|
|
if not Controller.FindFirstFrame(Frame.FrameID, FoundFrame) then
|
|
Exit;
|
|
|
|
while Assigned(FoundFrame) and (FoundFrame.Index < Frame.Index) do
|
|
begin
|
|
if FoundFrame.SameUniqueIDAs(Frame) then
|
|
begin
|
|
Result := False;
|
|
Break;
|
|
end;
|
|
|
|
if not Controller.FindNextFrame(Frame.FrameID, FoundFrame) then
|
|
Break;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3Frames.Clear;
|
|
var
|
|
F: TJvID3Frame;
|
|
begin
|
|
if FList.Count <= 0 then
|
|
Exit;
|
|
|
|
while FList.Count > 0 do
|
|
begin
|
|
F := TJvID3Frame(FList.Last);
|
|
F.FController := nil;
|
|
F.Free;
|
|
FList.Delete(FList.Count - 1);
|
|
end;
|
|
Changed;
|
|
end;
|
|
|
|
function TJvID3Frames.FindFrame(const FrameID: TJvID3FrameID): TJvID3Frame;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
for I := 0 to FList.Count - 1 do
|
|
begin
|
|
Result := TJvID3Frame(FList.Items[I]);
|
|
if Result.FrameID = FrameID then
|
|
Exit
|
|
end;
|
|
Result := nil;
|
|
end;
|
|
|
|
function TJvID3Frames.FindFrame(const FrameName: AnsiString): TJvID3Frame;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
for I := 0 to FList.Count - 1 do
|
|
begin
|
|
Result := TJvID3Frame(FList.Items[I]);
|
|
if SameText(Result.FrameName, FrameName) then
|
|
Exit;
|
|
end;
|
|
Result := nil;
|
|
end;
|
|
|
|
function TJvID3Frames.FrameByID(const FrameID: TJvID3FrameID): TJvID3Frame;
|
|
begin
|
|
Result := FindFrame(FrameID);
|
|
if Result = nil then
|
|
ID3ErrorFmt(RsEID3FrameNotFound, [ID3_FrameIDToString(FrameID)], Controller);
|
|
end;
|
|
|
|
function TJvID3Frames.FrameByName(const FrameName: AnsiString): TJvID3Frame;
|
|
begin
|
|
Result := FindFrame(FrameName);
|
|
if Result = nil then
|
|
ID3ErrorFmt(RsEID3FrameNotFound, [FrameName], Controller);
|
|
end;
|
|
|
|
function TJvID3Frames.GetCount: Integer;
|
|
begin
|
|
Result := FList.Count;
|
|
end;
|
|
|
|
function TJvID3Frames.GetFrame(Index: Integer): TJvID3Frame;
|
|
begin
|
|
Result := TJvID3Frame(FList[Index]);
|
|
end;
|
|
|
|
function TJvID3Frames.{%H-}GetFrameIDs: TJvID3FrameIDs;
|
|
begin
|
|
end;
|
|
|
|
procedure TJvID3Frames.GetFrameNames(List: TStrings);
|
|
var
|
|
I: Integer;
|
|
begin
|
|
List.BeginUpdate;
|
|
try
|
|
List.Clear;
|
|
for I := 0 to FList.Count - 1 do
|
|
List.Add(string(TJvID3Frame(FList.Items[I]).FrameName))
|
|
finally
|
|
List.EndUpdate;
|
|
end;
|
|
end;
|
|
|
|
function TJvID3Frames.IndexOf(Frame: TJvID3Frame): Integer;
|
|
begin
|
|
Result := FList.IndexOf(Frame);
|
|
end;
|
|
|
|
procedure TJvID3Frames.Read;
|
|
const
|
|
{ v2.2 : Frame header is 6 bytes
|
|
v2.3 and up : Frame header is minimal 10 bytes }
|
|
CMinimalHeaderSize: array [Boolean] of Byte = (6, 10);
|
|
var
|
|
Frame: TJvID3Frame;
|
|
FrameIDStr: AnsiString;
|
|
FrameID: TJvID3FrameID;
|
|
|
|
LFrameIDLength: Byte;
|
|
LMinimalHeaderSize: Byte;
|
|
begin
|
|
LFrameIDLength := GetFrameIDLength(Controller.Version);
|
|
LMinimalHeaderSize := CMinimalHeaderSize[Controller.Version > ive2_2];
|
|
SetLength(FrameIDStr, LFrameIDLength);
|
|
|
|
with Stream do
|
|
while BytesTillEndOfTag >= LMinimalHeaderSize do
|
|
begin
|
|
if Read(PAnsiChar(FrameIDStr)^, LFrameIDLength) <> LFrameIDLength then
|
|
Exit;
|
|
|
|
FrameID := ID3_StringToFrameID(FrameIDStr);
|
|
|
|
if FrameID in [fiPaddingFrame, fiErrorFrame] then
|
|
Exit;
|
|
|
|
Frame := Controller.AddFrame(FrameID);
|
|
if Assigned(Frame) then
|
|
begin
|
|
Frame.FrameName := FrameIDStr;
|
|
Frame.Read;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3Frames.Remove(Frame: TJvID3Frame);
|
|
begin
|
|
if Assigned(Frame) then
|
|
begin
|
|
FList.Remove(Frame);
|
|
Frame.FFrames := nil;
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3Frames.RemoveEmptyFrames;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
for I := Count - 1 downto 0 do
|
|
if Frames[I].IsEmpty then
|
|
Frames[I].Free;
|
|
end;
|
|
|
|
procedure TJvID3Frames.Reset;
|
|
begin
|
|
Clear;
|
|
end;
|
|
|
|
procedure TJvID3Frames.SetFrame(Index: Integer; Value: TJvID3Frame);
|
|
begin
|
|
Frames[Index].Assign(Value);
|
|
end;
|
|
|
|
procedure TJvID3Frames.SetFrameIndex(Frame: TJvID3Frame; Value: Integer);
|
|
var
|
|
CurIndex, lCount: Integer;
|
|
begin
|
|
CurIndex := FList.IndexOf(Frame);
|
|
if CurIndex >= 0 then
|
|
begin
|
|
lCount := FList.Count;
|
|
if Value < 0 then
|
|
Value := 0;
|
|
if Value >= lCount then
|
|
Value := lCount - 1;
|
|
if Value <> CurIndex then
|
|
begin
|
|
FList.Delete(CurIndex);
|
|
FList.Insert(Value, Frame);
|
|
Changed;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3Frames.Write;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
for I := 0 to FList.Count - 1 do
|
|
Frames[I].Write;
|
|
end;
|
|
|
|
|
|
//=== { TJvID3GeneralObjFrame } ==============================================
|
|
|
|
procedure TJvID3GeneralObjFrame.Assign(Source: TPersistent);
|
|
var
|
|
Src: TJvID3GeneralObjFrame;
|
|
begin
|
|
if Source is TJvID3GeneralObjFrame then
|
|
begin
|
|
Src := TJvID3GeneralObjFrame(Source);
|
|
FContentDescription := Src.ContentDescription;
|
|
FMIMEType := Src.MIMEType;
|
|
FFileName := Src.FFileName;
|
|
end;
|
|
inherited Assign(Source);
|
|
end;
|
|
|
|
class function TJvID3GeneralObjFrame.CanAddFrame(AController: TJvID3Controller;
|
|
AFrameID: TJvID3FrameID): Boolean;
|
|
begin
|
|
{ There may be more than one "GEOB" frame in each tag, but only one with the
|
|
same content descriptor. }
|
|
Result := (AFrameID = fiGeneralObject) or
|
|
inherited CanAddFrame(AController, AFrameID);
|
|
end;
|
|
|
|
function TJvID3GeneralObjFrame.CheckFrame(const HandleError: TJvID3HandleError): Boolean;
|
|
begin
|
|
Result := True;
|
|
end;
|
|
|
|
procedure TJvID3GeneralObjFrame.Clear;
|
|
begin
|
|
FContentDescription := '';
|
|
FMIMEType := '';
|
|
FFileName := '';
|
|
|
|
inherited Clear;
|
|
end;
|
|
|
|
{class function TJvID3GeneralObjFrame.Find(AController: TJvID3Controller;
|
|
const AContentDescription: WideString): TJvID3GeneralObjFrame; }
|
|
class function TJvID3GeneralObjFrame.Find(AController: TJvID3Controller;
|
|
const AContentDescription: String): TJvID3GeneralObjFrame;
|
|
var
|
|
Frame: TJvID3Frame = nil;
|
|
begin
|
|
Result := nil;
|
|
if not Assigned(AController) or not AController.Active then
|
|
Exit;
|
|
|
|
if not AController.FindFirstFrame(fiGeneralObject, Frame) then
|
|
Exit;
|
|
|
|
while (Frame is TJvID3GeneralObjFrame) and
|
|
(TJvID3GeneralObjFrame(Frame).ContentDescription <> AContentDescription)
|
|
do
|
|
AController.FindNextFrame(fiGeneralObject, Frame);
|
|
|
|
if Frame is TJvID3GeneralObjFrame then
|
|
Result := TJvID3GeneralObjFrame(Frame);
|
|
end;
|
|
|
|
class function TJvID3GeneralObjFrame.Find(AController: TJvID3Controller): TJvID3GeneralObjFrame;
|
|
var
|
|
Frame: TJvID3Frame;
|
|
begin
|
|
Result := nil;
|
|
if not Assigned(AController) or not AController.Active then
|
|
Exit;
|
|
|
|
Frame := AController.Frames.FindFrame(fiGeneralObject);
|
|
if Frame is TJvID3GeneralObjFrame then
|
|
Result := TJvID3GeneralObjFrame(Frame);
|
|
end;
|
|
|
|
{class function TJvID3GeneralObjFrame.FindOrCreate(AController: TJvID3Controller;
|
|
const AContentDescription: WideString): TJvID3GeneralObjFrame;}
|
|
class function TJvID3GeneralObjFrame.FindOrCreate(AController: TJvID3Controller;
|
|
const AContentDescription: String): TJvID3GeneralObjFrame;
|
|
begin
|
|
if not Assigned(AController) then
|
|
ID3Error(RsEID3NoController);
|
|
|
|
Result := Find(AController, AContentDescription);
|
|
if not Assigned(Result) then
|
|
begin
|
|
Result := TJvID3GeneralObjFrame(AController.AddFrame(fiGeneralObject));
|
|
Result.ContentDescription := AContentDescription;
|
|
end;
|
|
end;
|
|
|
|
class function TJvID3GeneralObjFrame.FindOrCreate(AController: TJvID3Controller): TJvID3GeneralObjFrame;
|
|
begin
|
|
if not Assigned(AController) then
|
|
ID3Error(RsEID3NoController);
|
|
|
|
Result := Find(AController);
|
|
if not Assigned(Result) then
|
|
Result := TJvID3GeneralObjFrame(AController.AddFrame(fiGeneralObject));
|
|
end;
|
|
|
|
function TJvID3GeneralObjFrame.GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal;
|
|
begin
|
|
{ Text encoding $xx
|
|
MIME type <text string> $00
|
|
FileName <text string according to encoding> $00 (00)
|
|
Content description <text string according to encoding> $00 (00)
|
|
Encapsulated object <binary data>
|
|
}
|
|
Result := 1 + Cardinal(Length(MIMEType)) + 1 +
|
|
LengthEnc(FileName, ToEncoding) +
|
|
LengthTerminatorEnc(ToEncoding) +
|
|
LengthEnc(ContentDescription, ToEncoding) +
|
|
LengthTerminatorEnc(ToEncoding) +
|
|
DataSize;
|
|
end;
|
|
|
|
function TJvID3GeneralObjFrame.GetIsEmpty: Boolean;
|
|
begin
|
|
Result := inherited GetIsEmpty and (Length(FMIMEType) = 0) and
|
|
(ContentDescription = '') and
|
|
(FileName = '');
|
|
end;
|
|
|
|
function TJvID3GeneralObjFrame.MustWriteAsUTF: Boolean;
|
|
begin
|
|
Result := HasNonISO_8859_1Chars(FileName) or HasNonISO_8859_1Chars(ContentDescription);
|
|
end;
|
|
|
|
procedure TJvID3GeneralObjFrame.ReadFrame;
|
|
begin
|
|
{ Text encoding $xx
|
|
MIME type <text string> $00
|
|
FileName <text string according to encoding> $00 (00)
|
|
Content description <text string according to encoding> $00 (00)
|
|
Encapsulated object <binary data>
|
|
}
|
|
with Stream do
|
|
begin
|
|
ReadEncoding;
|
|
ReadStringA(FMIMEType);
|
|
ReadStringEnc(FFileName);
|
|
ReadStringEnc(FContentDescription);
|
|
end;
|
|
ReadData(Stream.BytesTillEndOfFrame);
|
|
end;
|
|
|
|
function TJvID3GeneralObjFrame.SameUniqueIDAs(const Frame: TJvID3Frame): Boolean;
|
|
begin
|
|
{ There may be more than one "GEOB" frame in each tag, but only one with the
|
|
same content descriptor. }
|
|
Result := (Frame is TJvID3GeneralObjFrame) and
|
|
(Frame.FrameID = FrameID) and (FrameID = fiGeneralObject);
|
|
|
|
if Result then
|
|
Result := (TJvID3GeneralObjFrame(Frame).ContentDescription = ContentDescription)
|
|
else
|
|
Result := inherited SameUniqueIDAs(Frame);
|
|
end;
|
|
|
|
//procedure TJvID3GeneralObjFrame.SetContentDescription(const Value: WideString);
|
|
procedure TJvID3GeneralObjFrame.SetContentDescription(const Value: String);
|
|
begin
|
|
if Value <> FContentDescription then
|
|
begin
|
|
FContentDescription := Value;
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
//procedure TJvID3GeneralObjFrame.SetFileName(const Value: WideString);
|
|
procedure TJvID3GeneralObjFrame.SetFileName(const Value: String);
|
|
begin
|
|
if Value <> FFileName then
|
|
begin
|
|
FFileName := Value;
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3GeneralObjFrame.SetMIMEType(const Value: AnsiString);
|
|
begin
|
|
if FMIMEType <> Value then
|
|
begin
|
|
FMIMEType := Value;
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3GeneralObjFrame.WriteFrame;
|
|
begin
|
|
{ Text encoding $xx
|
|
MIME type <text string> $00
|
|
FileName <text string according to encoding> $00 (00)
|
|
Content description <text string according to encoding> $00 (00)
|
|
Encapsulated object <binary data>
|
|
}
|
|
with Stream do
|
|
begin
|
|
WriteEncoding;
|
|
WriteStringA(MIMEType);
|
|
WriteTerminatorA;
|
|
WriteStringEnc(FileName);
|
|
WriteTerminatorEnc;
|
|
WriteStringEnc(ContentDescription);
|
|
WriteTerminatorEnc;
|
|
end;
|
|
WriteData;
|
|
end;
|
|
|
|
|
|
//=== { TJvID3Header } =======================================================
|
|
|
|
procedure TJvID3Header.Assign(Source: TPersistent);
|
|
begin
|
|
if Source is TJvID3Header then
|
|
begin
|
|
FHasTag := TJvID3Header(Source).HasTag;
|
|
FRevisionNumber := TJvID3Header(Source).RevisionNumber;
|
|
FMajorVersion := TJvID3Header(Source).MajorVersion;
|
|
FSize := TJvID3Header(Source).Size;
|
|
FFlags := TJvID3Header(Source).Flags;
|
|
end
|
|
else
|
|
inherited Assign(Source);
|
|
end;
|
|
|
|
procedure TJvID3Header.ChangeToVersion(const ANewVersion: TJvID3Version);
|
|
begin
|
|
case ANewVersion of
|
|
ive2_2:
|
|
begin
|
|
FRevisionNumber := 0;
|
|
FMajorVersion := 2;
|
|
{ Only flag 'hfUnsynchronisation' is allowed }
|
|
FFlags := FFlags * [hfUnsynchronisation];
|
|
end;
|
|
ive2_3:
|
|
begin
|
|
FRevisionNumber := 0;
|
|
FMajorVersion := 3;
|
|
Exclude(FFlags, hfFooterPresent);
|
|
end;
|
|
ive2_4:
|
|
begin
|
|
FRevisionNumber := 0;
|
|
FMajorVersion := 4;
|
|
end;
|
|
else
|
|
ID3Error(RsEID3VersionNotSupported);
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3Header.Read;
|
|
var
|
|
Header: TID3v2HeaderRec;
|
|
begin
|
|
Reset;
|
|
|
|
with Stream do
|
|
begin
|
|
BeginReadFrame(10);
|
|
try
|
|
if Read(Header{%H-}, 10) <> 10 then
|
|
Exit;
|
|
|
|
FHasTag := Header.Identifier = cID3HeaderId;
|
|
if not FHasTag then
|
|
Exit;
|
|
|
|
{ This sets Controller.Version }
|
|
FMajorVersion := Header.MajorVersion;
|
|
FRevisionNumber := Header.RevisionNumber;
|
|
|
|
{ v2.2 : %ae000000 a - Unsynchronisation d - Footer present
|
|
v2.3 : %abc00000 b - Extended header e - Compression (only v2.2)
|
|
v2.4 : %abcd0000 c - Experimental indicator
|
|
}
|
|
if Header.Flags and $80 > 0 then
|
|
Include(FFlags, hfUnsynchronisation);
|
|
if Header.Flags and $40 > 0 then
|
|
begin
|
|
{ v2.2: Since no compression scheme has been decided yet, the ID3
|
|
decoder (for now) should just ignore the entire tag if the
|
|
compression bit is set. }
|
|
if Controller.Version <> ive2_2 then
|
|
Include(FFlags, hfExtendedHeader);
|
|
end;
|
|
if Header.Flags and $20 > 0 then
|
|
Include(FFlags, hfExperimentalIndicator);
|
|
if Header.Flags and $10 > 0 then
|
|
Include(FFlags, hfFooterPresent);
|
|
|
|
{ The ID3v2 tag size is the size of the complete tag after unsychronisation,
|
|
including padding, excluding the header but not excluding the extended
|
|
header }
|
|
UnSyncSafe(Header.Size, 4, FSize);
|
|
finally
|
|
EndReadFrame;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3Header.Reset;
|
|
begin
|
|
FHasTag := False;
|
|
FRevisionNumber := 0;
|
|
FMajorVersion := 0;
|
|
FSize := 0;
|
|
FFlags := [];
|
|
end;
|
|
|
|
procedure TJvID3Header.SetFlags(const Value: TJvID3HeaderFlags);
|
|
var
|
|
ChangedFlags: TJvID3HeaderFlags;
|
|
begin
|
|
if FFlags <> Value then
|
|
begin
|
|
ChangedFlags := FFlags + Value - (FFlags * Value);
|
|
|
|
{ hfFooterPresent is currently not supported }
|
|
if (hfFooterPresent in ChangedFlags) and (hfFooterPresent in Value) then
|
|
ID3Error(RsEControllerDoesNotSupportFooter, Controller);
|
|
|
|
FFlags := Value;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3Header.Write;
|
|
const
|
|
{ iveLowerThan2_2, ive2_2, ive2_3, ive2_4, iveHigherThan2_4 }
|
|
CMajorVersion: array [TJvID3Version] of Byte = (2, 2, 3, 4, 4);
|
|
CRevisionNumber: array [TJvID3Version] of Byte = (0, 0, 0, 0, 0);
|
|
var
|
|
Header: TID3v2HeaderRec;
|
|
begin
|
|
{ Check max size }
|
|
if Self.FSize > $0FFFFFFF then // 28 bits = 256 MB
|
|
ID3Error(RsETagTooBig, Controller);
|
|
|
|
FillChar(Header{%H-}, SizeOf(Header), #0);
|
|
|
|
with Stream do
|
|
begin
|
|
BeginWriteFrame(10);
|
|
try
|
|
Header.Identifier := cID3HeaderId;
|
|
Header.MajorVersion := CMajorVersion[Controller.WriteVersion];
|
|
Header.RevisionNumber := CRevisionNumber[Controller.WriteVersion];
|
|
|
|
{ v2.2 : %ae000000 a - Unsynchronisation d - Footer present
|
|
v2.3 : %abc00000 b - Extended header e - Compression (only v2.2)
|
|
v2.4 : %abcd0000 c - Experimental indicator
|
|
}
|
|
if hfUnsynchronisation in Flags then
|
|
Inc(Header.Flags, $80);
|
|
if Controller.WriteVersion > ive2_2 then
|
|
begin
|
|
if hfExtendedHeader in Flags then
|
|
Inc(Header.Flags, $40);
|
|
if hfExperimentalIndicator in Flags then
|
|
Inc(Header.Flags, $20);
|
|
{ Only for v2.4 }
|
|
if (Controller.WriteVersion = ive2_4) and (hfFooterPresent in Flags) then
|
|
Inc(Header.Flags, $10);
|
|
end;
|
|
{ The ID3v2 tag size is the size of the complete tag after unsychronisation,
|
|
including padding, excluding the header but not excluding the extended
|
|
header }
|
|
SyncSafe(FSize, Header.Size, 4);
|
|
|
|
WriteBuffer(Header, 10);
|
|
finally
|
|
EndWriteFrame;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
//=== { TJvID3NumberFrame } ==================================================
|
|
|
|
procedure TJvID3NumberFrame.ChangeToVersion(const ANewVersion: TJvID3Version);
|
|
var
|
|
Year: Word;
|
|
LDate: TDateTime;
|
|
Frame: TJvID3Frame;
|
|
begin
|
|
if ANewVersion <> ive2_4 then
|
|
Exit;
|
|
|
|
{ Change
|
|
|
|
* fiYear, fiDate, fiTime, fiRecordingDates frames into 1 fiRecordingTime frame
|
|
* fiOrigYear frame into 1 fiOrigReleaseTime frame }
|
|
|
|
if FrameID = fiYear then
|
|
begin
|
|
if Assigned(FFrames.FindFrame(fiRecordingTime)) then
|
|
Exit;
|
|
|
|
{ 1. Determine the year from a fiYear frame, ie this frame }
|
|
Year := Value;
|
|
|
|
{ 2. Determine month + day from a fiDate frame }
|
|
Frame := TJvID3TextFrame.Find(FController, fiDate);
|
|
if Assigned(Frame) then
|
|
with TJvID3TextFrame(Frame) do
|
|
LDate := GetID3Date(FText, Encoding, Year)
|
|
else
|
|
try
|
|
{ hm, no date frame , just assume it's 1 jan }
|
|
LDate := EncodeDate(Year, 1, 1);
|
|
except
|
|
on EConvertError do
|
|
LDate := 0;
|
|
end;
|
|
|
|
{ 3. Determine hour + min from a fiTime frame}
|
|
Frame := TJvID3TextFrame.Find(FController, fiTime);
|
|
if Assigned(Frame) then
|
|
with TJvID3TextFrame(Frame) do
|
|
LDate := LDate + GetID3Time(FText, Encoding);
|
|
|
|
{ 4. Copy constructed date to a fiRecordingTime frame }
|
|
TJvID3TimestampFrame.FindOrCreate(FController, fiRecordingTime).Value := LDate;
|
|
end
|
|
else
|
|
if FrameID = fiOrigYear then
|
|
begin
|
|
if Assigned(FFrames.FindFrame(fiOrigReleaseTime)) then
|
|
Exit;
|
|
|
|
try
|
|
LDate := EncodeDate(Value, 1, 1);
|
|
except
|
|
on EConvertError do
|
|
LDate := 0;
|
|
end;
|
|
|
|
{ Copy date to a fiRecordingTime frame }
|
|
TJvID3TimestampFrame.FindOrCreate(FController, fiOrigReleaseTime).Value := LDate;
|
|
end;
|
|
end;
|
|
|
|
function TJvID3NumberFrame.CheckFrame(const HandleError: TJvID3HandleError): Boolean;
|
|
begin
|
|
if FrameID in [fiOrigYear, fiYear] then
|
|
begin
|
|
{ Always 4 characters long }
|
|
Result := FValue < 10000;
|
|
|
|
if not Result then
|
|
case HandleError of
|
|
heAutoCorrect:
|
|
begin
|
|
{ No need to call UpdateFrameSize, because it's always 4 chars long }
|
|
Result := True;
|
|
FValue := 0;
|
|
end;
|
|
heRaise:
|
|
ErrorFmt(RsEID3ValueTooBig, [FValue]);
|
|
end;
|
|
end
|
|
else
|
|
Result := True;
|
|
end;
|
|
|
|
class function TJvID3NumberFrame.Find(AController: TJvID3Controller;
|
|
const AFrameID: TJvID3FrameID): TJvID3NumberFrame;
|
|
var
|
|
Frame: TJvID3Frame;
|
|
begin
|
|
Result := nil;
|
|
if not Assigned(AController) or not AController.Active then
|
|
Exit;
|
|
|
|
Frame := AController.Frames.FindFrame(AFrameID);
|
|
if Frame is TJvID3NumberFrame then
|
|
Result := TJvID3NumberFrame(Frame)
|
|
end;
|
|
|
|
class function TJvID3NumberFrame.FindOrCreate(AController: TJvID3Controller;
|
|
const AFrameID: TJvID3FrameID): TJvID3NumberFrame;
|
|
begin
|
|
if not Assigned(AController) then
|
|
ID3Error(RsEID3NoController);
|
|
|
|
Result := Find(AController, AFrameID);
|
|
if not Assigned(Result) then
|
|
begin
|
|
AController.CheckFrameClass(TJvID3NumberFrame, AFrameID);
|
|
Result := TJvID3NumberFrame(AController.AddFrame(AFrameID));
|
|
end;
|
|
end;
|
|
|
|
function TJvID3NumberFrame.GetIsEmpty: Boolean;
|
|
begin
|
|
if FrameID in [fiOrigYear, fiYear] then
|
|
Result := Value = 0
|
|
else
|
|
Result := inherited GetIsEmpty;
|
|
end;
|
|
|
|
//function TJvID3NumberFrame.GetText: WideString;
|
|
function TJvID3NumberFrame.GetText: String;
|
|
const
|
|
CFormat: array[Boolean] of string = ('%d', '%.4d');
|
|
begin
|
|
Result := Format(CFormat[FrameID in [fiOrigYear, fiYear]], [FValue]);
|
|
end;
|
|
|
|
//procedure TJvID3NumberFrame.SetText(const ANewText: WideString);
|
|
procedure TJvID3NumberFrame.SetText(const ANewText: String);
|
|
begin
|
|
FValue := StrToIntDef(ANewText, 0);
|
|
UpdateFrameSize;
|
|
end;
|
|
|
|
procedure TJvID3NumberFrame.SetValue(const AValue: Cardinal);
|
|
begin
|
|
if AValue <> FValue then
|
|
begin
|
|
FValue := AValue;
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
|
|
//=== { TJvID3OwnershipFrame } ===============================================
|
|
|
|
procedure TJvID3OwnershipFrame.Assign(Source: TPersistent);
|
|
var
|
|
Src: TJvID3OwnershipFrame;
|
|
begin
|
|
if Source is TJvID3OwnershipFrame then
|
|
begin
|
|
Src := TJvID3OwnershipFrame(Source);
|
|
FPricePayed := Src.PricePayed;
|
|
FSeller := Src.Seller;
|
|
FDateOfPurch := Src.DateOfPurch;
|
|
end;
|
|
|
|
inherited Assign(Source);
|
|
end;
|
|
|
|
class function TJvID3OwnershipFrame.CanAddFrame(AController: TJvID3Controller;
|
|
AFrameID: TJvID3FrameID): Boolean;
|
|
begin
|
|
{ There may only be one 'OWNE' frame in a tag }
|
|
Result := ((AFrameID = fiOwnership) and not AController.HasFrame(fiOwnership)) or
|
|
inherited CanAddFrame(AController, AFrameID);
|
|
end;
|
|
|
|
function TJvID3OwnershipFrame.CheckFrame(const HandleError: TJvID3HandleError): Boolean;
|
|
begin
|
|
Result := True;
|
|
end;
|
|
|
|
procedure TJvID3OwnershipFrame.Clear;
|
|
begin
|
|
FPricePayed := '';
|
|
FSeller := '';
|
|
FDateOfPurch := 0;
|
|
inherited Clear;
|
|
end;
|
|
|
|
class function TJvID3OwnershipFrame.Find(AController: TJvID3Controller): TJvID3OwnershipFrame;
|
|
var
|
|
Frame: TJvID3Frame;
|
|
begin
|
|
Result := nil;
|
|
if not Assigned(AController) or not AController.Active then
|
|
Exit;
|
|
|
|
Frame := AController.Frames.FindFrame(fiOwnership);
|
|
if Frame is TJvID3OwnershipFrame then
|
|
Result := TJvID3OwnershipFrame(Frame)
|
|
end;
|
|
|
|
class function TJvID3OwnershipFrame.FindOrCreate(AController: TJvID3Controller): TJvID3OwnershipFrame;
|
|
begin
|
|
if not Assigned(AController) then
|
|
ID3Error(RsEID3NoController);
|
|
|
|
Result := Find(AController);
|
|
if not Assigned(Result) then
|
|
Result := TJvID3OwnershipFrame(AController.AddFrame(fiOwnership));
|
|
end;
|
|
|
|
{ Text encoding $xx
|
|
Price payed <text string> $00
|
|
Date of purch. <text string>
|
|
Seller <text string according to encoding> }
|
|
function TJvID3OwnershipFrame.GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal;
|
|
begin
|
|
Result := 1 + Cardinal(Length(FPricePayed)) + 1 + 8 +
|
|
LengthEnc(Seller, ToEncoding);
|
|
end;
|
|
|
|
function TJvID3OwnershipFrame.GetIsEmpty: Boolean;
|
|
begin
|
|
Result := (Length(FPricePayed) = 0) and (Seller = '') and
|
|
(FDateOfPurch = 0);
|
|
end;
|
|
|
|
function TJvID3OwnershipFrame.MustWriteAsUTF: Boolean;
|
|
begin
|
|
Result := HasNonISO_8859_1Chars(Seller);
|
|
end;
|
|
|
|
{ Text encoding $xx
|
|
Price payed <text string> $00
|
|
Date of purch. <text string>
|
|
Seller <text string according to encoding> }
|
|
procedure TJvID3OwnershipFrame.ReadFrame;
|
|
begin
|
|
with Stream do
|
|
begin
|
|
ReadEncoding;
|
|
ReadStringA(FPricePayed);
|
|
ReadDate(FDateOfPurch);
|
|
ReadStringEnc(FSeller);
|
|
end;
|
|
end;
|
|
|
|
function TJvID3OwnershipFrame.SameUniqueIDAs(const Frame: TJvID3Frame): Boolean;
|
|
begin
|
|
{ There may only be one 'OWNE' frame in a tag }
|
|
Result := (Assigned(Frame) and (Frame.FrameID = FrameID) and (FrameID = fiOwnership)) or
|
|
inherited SameUniqueIDAs(Frame);
|
|
end;
|
|
|
|
procedure TJvID3OwnershipFrame.SetDateOfPurch(const Value: TDateTime);
|
|
begin
|
|
if FDateOfPurch <> Value then
|
|
begin
|
|
FDateOfPurch := Value;
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3OwnershipFrame.SetPricePayed(const Value: AnsiString);
|
|
begin
|
|
if FPricePayed <> Value then
|
|
begin
|
|
FPricePayed := Value;
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
//procedure TJvID3OwnershipFrame.SetSeller(const Value: WideString);
|
|
procedure TJvID3OwnershipFrame.SetSeller(const Value: String);
|
|
begin
|
|
if Value <> FSeller then
|
|
begin
|
|
FSeller := Value;
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
function TJvID3OwnershipFrame.SupportsVersion(const AVersion: TJvID3Version): Boolean;
|
|
begin
|
|
case FrameID of
|
|
{ ** Not supported in 2.2 ** }
|
|
|
|
fiOwnership:
|
|
Result := AVersion in [ive2_3, ive2_4];
|
|
else
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3OwnershipFrame.WriteFrame;
|
|
begin
|
|
{ Text encoding $xx
|
|
Price payed <text string> $00
|
|
Date of purch. <text string>
|
|
Seller <text string according to encoding>
|
|
}
|
|
with Stream do
|
|
begin
|
|
WriteEncoding;
|
|
WriteStringA(PricePayed);
|
|
WriteTerminatorA;
|
|
WriteDate(DateOfPurch);
|
|
WriteStringEnc(Seller);
|
|
end;
|
|
end;
|
|
|
|
|
|
//=== { TJvID3PictureFrame } =================================================
|
|
|
|
procedure TJvID3PictureFrame.Assign(Source: TPersistent);
|
|
var
|
|
lStream: TMemoryStream;
|
|
begin
|
|
if Source is TPicture then
|
|
Assign(TPicture(Source).Graphic)
|
|
else
|
|
if Source is TGraphic then
|
|
begin
|
|
lStream := TMemoryStream.Create;
|
|
try
|
|
TGraphic(Source).SaveToStream(lStream);
|
|
lStream.Seek(0, soBeginning);
|
|
LoadFromStream(lStream);
|
|
finally
|
|
lStream.Free;
|
|
end;
|
|
end
|
|
else
|
|
if Source is TJvID3PictureFrame then
|
|
begin
|
|
FMIMEType := TJvID3PictureFrame(Source).MIMEType;
|
|
FPictureType := TJvID3PictureFrame(Source).PictureType;
|
|
FDescription := TJvID3PictureFrame(Source).Description;
|
|
FURL := TJvID3PictureFrame(Source).URL;
|
|
end
|
|
else
|
|
inherited Assign(Source);
|
|
end;
|
|
|
|
procedure TJvID3PictureFrame.AssignTo(Dest: TPersistent);
|
|
var
|
|
TmpFileName: string;
|
|
begin
|
|
if (Dest is TPicture) or (Dest is TGraphic) then
|
|
begin
|
|
if (DataSize > 0) and (MIMEType <> cURLArrow) then
|
|
begin
|
|
{ !! We can't use FileGetTempName; it /creates/ a file with extension TMP but
|
|
we need to have a specific extension }
|
|
// TmpFileName := SysUtils.IncludeTrailingPathDelimiter(PathGetTempPath) + cPictureFrameFileNameTemplate + '_' + IntToStr(Random(500)) + '_' + FormatDateTime('zzz', Now);
|
|
TmpFileName := AppendPathDelim(GetTempDir) + cPictureFrameFileNameTemplate + '_' + IntToStr(Random(500)) + '_' + FormatDateTime('zzz', Now);
|
|
TmpFileName := FindUnusedFileName(TmpFileName, MIMETypeToExt(string(MIMEType)), '');
|
|
try
|
|
SaveToFile(TmpFileName);
|
|
try
|
|
try
|
|
if Dest is TPicture then
|
|
TPicture(Dest).LoadFromFile(TmpFileName)
|
|
else
|
|
if Dest is TGraphic then
|
|
TGraphic(Dest).LoadFromFile(TmpFileName);
|
|
except
|
|
on EInvalidGraphic do
|
|
; { Do nothing }
|
|
end
|
|
finally
|
|
SysUtils.DeleteFile(TmpFileName);
|
|
end;
|
|
except
|
|
{ Something went wrong while saving picture to file }
|
|
end;
|
|
end
|
|
else
|
|
Dest.Assign(nil);
|
|
end
|
|
else
|
|
inherited AssignTo(Dest);
|
|
end;
|
|
|
|
class function TJvID3PictureFrame.CanAddFrame(AController: TJvID3Controller;
|
|
AFrameID: TJvID3FrameID): Boolean;
|
|
begin
|
|
{ There may be several pictures attached to one file, each in their
|
|
individual "APIC" frame, but only one with the same content descriptor.
|
|
There may only be one picture with the picture type declared as picture
|
|
type $01 and $02 respectively.
|
|
}
|
|
Result := (AFrameID = fiPicture) or inherited CanAddFrame(AController, AFrameID);
|
|
end;
|
|
|
|
function TJvID3PictureFrame.CheckFrame(const HandleError: TJvID3HandleError): Boolean;
|
|
begin
|
|
{ The description has a maximum length of 64 characters, but may be empty. }
|
|
|
|
Result := CheckMaxCharCount(Self, FDescription, 64, HandleError);
|
|
if not Result and (HandleError = heAutoCorrect) then
|
|
begin
|
|
UpdateFrameSize;
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3PictureFrame.Clear;
|
|
begin
|
|
FMIMEType := '';
|
|
FPictureType := ptOther;
|
|
FDescription := '';
|
|
FURL := '';
|
|
|
|
inherited Clear;
|
|
end;
|
|
|
|
class function TJvID3PictureFrame.Find(AController: TJvID3Controller;
|
|
const AType: TJvID3PictureType): TJvID3PictureFrame;
|
|
var
|
|
Frame: TJvID3Frame = nil;
|
|
begin
|
|
Result := nil;
|
|
if not Assigned(AController) or not AController.Active then
|
|
Exit;
|
|
|
|
if not AController.FindFirstFrame(fiPicture, Frame) then
|
|
Exit;
|
|
|
|
while (Frame is TJvID3PictureFrame) and
|
|
(TJvID3PictureFrame(Frame).PictureType <> AType) do
|
|
AController.FindNextFrame(fiPicture, Frame);
|
|
|
|
if Frame is TJvID3PictureFrame then
|
|
Result := TJvID3PictureFrame(Frame)
|
|
end;
|
|
|
|
class function TJvID3PictureFrame.FindOrCreate(AController: TJvID3Controller;
|
|
const AType: TJvID3PictureType): TJvID3PictureFrame;
|
|
begin
|
|
if not Assigned(AController) then
|
|
ID3Error(RsEID3NoController);
|
|
|
|
Result := Find(AController, AType);
|
|
if not Assigned(Result) then
|
|
begin
|
|
Result := TJvID3PictureFrame(AController.AddFrame(fiPicture));
|
|
Result.PictureType := AType;
|
|
end;
|
|
end;
|
|
|
|
function TJvID3PictureFrame.GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal;
|
|
begin
|
|
{ Text encoding: $xx
|
|
MIME type: <text string> $00
|
|
Picture type: $xx
|
|
Description: <text string according to encoding> $00 (00)
|
|
Picture data: <binary data>
|
|
}
|
|
if HasOnlyURL then
|
|
Result := 1 + Length(cURLArrow) + 1 + 1 +
|
|
LengthEnc(Description, ToEncoding) +
|
|
LengthTerminatorEnc(ToEncoding) + Cardinal(Length(URL))
|
|
else
|
|
Result := 1 + Cardinal(Length(MIMEType)) + 1 + 1 +
|
|
LengthEnc(Description, ToEncoding) +
|
|
LengthTerminatorEnc(ToEncoding) + DataSize;
|
|
end;
|
|
|
|
function TJvID3PictureFrame.GetHasOnlyURL: Boolean;
|
|
begin
|
|
Result := (DataSize = 0) and (URL > '');
|
|
end;
|
|
|
|
function TJvID3PictureFrame.GetIsEmpty: Boolean;
|
|
begin
|
|
{ Don't care about FPictureType }
|
|
Result := inherited GetIsEmpty and
|
|
((Length(MIMEType) = 0) or (MIMEType = cURLArrow)) and
|
|
(Length(URL) = 0) and (Description = '');
|
|
end;
|
|
|
|
function TJvID3PictureFrame.MustWriteAsUTF: Boolean;
|
|
begin
|
|
Result := HasNonISO_8859_1Chars(Description);
|
|
end;
|
|
|
|
procedure TJvID3PictureFrame.ReadFrame;
|
|
var
|
|
LPictureType: Byte = 0;
|
|
begin
|
|
{ Text encoding $xx
|
|
MIME type <text string> $00
|
|
Picture type $xx
|
|
Description <text string according to encoding> $00 (00)
|
|
Picture data <binary data>
|
|
}
|
|
with Stream do
|
|
begin
|
|
ReadEncoding;
|
|
ReadStringA(FMIMEType);
|
|
if BytesTillEndOfFrame < 1 then
|
|
Exit;
|
|
|
|
Read(LPictureType, 1);
|
|
if LPictureType <= Integer(High(TJvID3PictureType)) then
|
|
FPictureType := TJvID3PictureType(LPictureType)
|
|
else
|
|
FPictureType := ptOther;
|
|
|
|
ReadStringEnc(FDescription);
|
|
|
|
if MIMEType = cURLArrow then
|
|
{ There is the possibility to put only a link to the image file by using
|
|
the 'MIME type' "-->" and having a complete URL instead of picture data.
|
|
}
|
|
ReadStringA(FURL)
|
|
else
|
|
Self.ReadData(BytesTillEndOfFrame);
|
|
end;
|
|
end;
|
|
|
|
function TJvID3PictureFrame.SameUniqueIDAs(const Frame: TJvID3Frame): Boolean;
|
|
begin
|
|
{ There may be several pictures attached to one file, each in their
|
|
individual "APIC" frame, but only one with the same content descriptor.
|
|
There may only be one picture with the picture type declared as picture
|
|
type $01 and $02 respectively.
|
|
}
|
|
Result := (Frame is TJvID3PictureFrame) and
|
|
(Frame.FrameID = FrameID) and (FrameID = fiPicture);
|
|
|
|
if Result then
|
|
Result :=
|
|
(TJvID3PictureFrame(Frame).PictureType = PictureType) and
|
|
((PictureType in [ptFileIcon, ptOtherFileIcon]) or
|
|
(Description = TJvID3PictureFrame(Frame).Description))
|
|
else
|
|
Result := inherited SameUniqueIDAs(Frame);
|
|
end;
|
|
|
|
procedure TJvID3PictureFrame.SetDescription(const Value: String);
|
|
//procedure TJvID3PictureFrame.SetDescription(const Value: WideString);
|
|
begin
|
|
if Value <> FDescription then
|
|
begin
|
|
FDescription := Value;
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3PictureFrame.SetMIMEType(const Value: AnsiString);
|
|
begin
|
|
if FMIMEType <> Value then
|
|
begin
|
|
FMIMEType := Value;
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3PictureFrame.SetURL(const Value: AnsiString);
|
|
begin
|
|
if FURL <> Value then
|
|
begin
|
|
FURL := Value;
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3PictureFrame.WriteFrame;
|
|
begin
|
|
{ Text encoding $xx
|
|
MIME type <text string> $00
|
|
Picture type $xx
|
|
Description <text string according to encoding> $00 (00)
|
|
Picture data <binary data>
|
|
|
|
There is the possibility to put only a link to the image file by using
|
|
the 'MIME type' "-->" and having a complete URL instead of picture data. }
|
|
|
|
with Stream do
|
|
begin
|
|
WriteEncoding;
|
|
if HasOnlyURL then
|
|
WriteStringA(cURLArrow)
|
|
else
|
|
WriteStringA(MIMEType);
|
|
WriteTerminatorA;
|
|
|
|
Write(PictureType, 1);
|
|
|
|
WriteStringEnc(Description);
|
|
WriteTerminatorEnc;
|
|
if HasOnlyURL then
|
|
WriteStringA(URL)
|
|
else
|
|
Self.WriteData;
|
|
end;
|
|
end;
|
|
|
|
|
|
//=== { TJvID3PlayCounterFrame } =============================================
|
|
|
|
procedure TJvID3PlayCounterFrame.Assign(Source: TPersistent);
|
|
begin
|
|
if Source is TJvID3PlayCounterFrame then
|
|
FCounter := TJvID3PlayCounterFrame(Source).Counter;
|
|
|
|
inherited Assign(Source);
|
|
end;
|
|
|
|
class function TJvID3PlayCounterFrame.CanAddFrame(AController: TJvID3Controller;
|
|
AFrameID: TJvID3FrameID): Boolean;
|
|
begin
|
|
{ There may only be one "PCNT" frame in each tag. }
|
|
Result := not AController.HasFrame(AFrameID) or
|
|
inherited CanAddFrame(AController, AFrameID);
|
|
end;
|
|
|
|
function TJvID3PlayCounterFrame.CheckFrame(const HandleError: TJvID3HandleError): Boolean;
|
|
begin
|
|
Result := True;
|
|
end;
|
|
|
|
procedure TJvID3PlayCounterFrame.Clear;
|
|
begin
|
|
FCounter := 0;
|
|
inherited Clear;
|
|
end;
|
|
|
|
class function TJvID3PlayCounterFrame.Find(AController: TJvID3Controller): TJvID3PlayCounterFrame;
|
|
var
|
|
Frame: TJvID3Frame;
|
|
begin
|
|
Result := nil;
|
|
if not Assigned(AController) or not AController.Active then
|
|
Exit;
|
|
|
|
Frame := AController.Frames.FindFrame(fiPlayCounter);
|
|
if Frame is TJvID3PlayCounterFrame then
|
|
Result := TJvID3PlayCounterFrame(Frame)
|
|
end;
|
|
|
|
class function TJvID3PlayCounterFrame.FindOrCreate(AController: TJvID3Controller): TJvID3PlayCounterFrame;
|
|
begin
|
|
if not Assigned(AController) then
|
|
ID3Error(RsEID3NoController);
|
|
|
|
Result := Find(AController);
|
|
if not Assigned(Result) then
|
|
Result := TJvID3PlayCounterFrame(AController.AddFrame(fiPlayCounter));
|
|
end;
|
|
|
|
function TJvID3PlayCounterFrame.GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal;
|
|
begin
|
|
Result := 4;
|
|
end;
|
|
|
|
function TJvID3PlayCounterFrame.GetIsEmpty: Boolean;
|
|
begin
|
|
Result := False;
|
|
end;
|
|
|
|
procedure TJvID3PlayCounterFrame.ReadFrame;
|
|
begin
|
|
Stream.ReadNumber(FCounter);
|
|
end;
|
|
|
|
function TJvID3PlayCounterFrame.SameUniqueIDAs(const Frame: TJvID3Frame): Boolean;
|
|
begin
|
|
{ There may only be one "PCNT" frame in each tag. }
|
|
Result := ((Frame.FrameID = FrameID) and (FrameID = fiPlayCounter)) or
|
|
inherited SameUniqueIDAs(Frame);
|
|
end;
|
|
|
|
procedure TJvID3PlayCounterFrame.SetCounter(const Value: Cardinal);
|
|
begin
|
|
if FCounter <> Value then
|
|
begin
|
|
FCounter := Value;
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3PlayCounterFrame.WriteFrame;
|
|
begin
|
|
Stream.WriteNumber(FCounter);
|
|
end;
|
|
|
|
|
|
//=== { TJvID3PopularimeterFrame } ===========================================
|
|
|
|
procedure TJvID3PopularimeterFrame.Assign(Source: TPersistent);
|
|
begin
|
|
if Source is TJvID3PopularimeterFrame then
|
|
begin
|
|
FRating := TJvID3PopularimeterFrame(Source).Rating;
|
|
FCounter := TJvID3PopularimeterFrame(Source).Counter;
|
|
FEMailAddress := TJvID3PopularimeterFrame(Source).EMailAddress;
|
|
end;
|
|
inherited Assign(Source);
|
|
end;
|
|
|
|
class function TJvID3PopularimeterFrame.CanAddFrame(AController: TJvID3Controller;
|
|
AFrameID: TJvID3FrameID): Boolean;
|
|
begin
|
|
{ There may be more than one "POPM" frame in each tag, but only one with the
|
|
same email address. }
|
|
Result := (AFrameID = fiPopularimeter) or inherited CanAddFrame(AController, AFrameID);
|
|
end;
|
|
|
|
function TJvID3PopularimeterFrame.CheckFrame(const HandleError: TJvID3HandleError): Boolean;
|
|
begin
|
|
Result := True;
|
|
end;
|
|
|
|
procedure TJvID3PopularimeterFrame.Clear;
|
|
begin
|
|
FRating := 0;
|
|
FCounter := 0;
|
|
FEMailAddress := '';
|
|
|
|
inherited Clear;
|
|
end;
|
|
|
|
class function TJvID3PopularimeterFrame.Find(AController: TJvID3Controller): TJvID3PopularimeterFrame;
|
|
var
|
|
Frame: TJvID3Frame;
|
|
begin
|
|
Result := nil;
|
|
if not Assigned(AController) or not AController.Active then
|
|
Exit;
|
|
|
|
Frame := AController.Frames.FindFrame(fiPopularimeter);
|
|
if Frame is TJvID3PopularimeterFrame then
|
|
Result := TJvID3PopularimeterFrame(Frame);
|
|
end;
|
|
|
|
class function TJvID3PopularimeterFrame.Find(AController: TJvID3Controller;
|
|
const AEmailAddress: AnsiString): TJvID3PopularimeterFrame;
|
|
var
|
|
Frame: TJvID3Frame = nil;
|
|
begin
|
|
Result := nil;
|
|
if not Assigned(AController) or not AController.Active then
|
|
Exit;
|
|
|
|
if not AController.FindFirstFrame(fiPopularimeter, Frame) then
|
|
Exit;
|
|
|
|
while (Frame is TJvID3PopularimeterFrame) and
|
|
not AnsiSameStr(AEmailAddress, TJvID3PopularimeterFrame(Frame).EMailAddress) do
|
|
AController.FindNextFrame(fiPopularimeter, Frame);
|
|
|
|
if Frame is TJvID3PopularimeterFrame then
|
|
Result := TJvID3PopularimeterFrame(Frame);
|
|
end;
|
|
|
|
class function TJvID3PopularimeterFrame.FindOrCreate(AController: TJvID3Controller): TJvID3PopularimeterFrame;
|
|
begin
|
|
if not Assigned(AController) then
|
|
ID3Error(RsEID3NoController);
|
|
|
|
Result := Find(AController);
|
|
if not Assigned(Result) then
|
|
Result := TJvID3PopularimeterFrame(AController.AddFrame(fiPopularimeter));
|
|
end;
|
|
|
|
class function TJvID3PopularimeterFrame.FindOrCreate(AController: TJvID3Controller;
|
|
const AEmailAddress: AnsiString): TJvID3PopularimeterFrame;
|
|
begin
|
|
if not Assigned(AController) then
|
|
ID3Error(RsEID3NoController);
|
|
|
|
Result := Find(AController, AEmailAddress);
|
|
if not Assigned(Result) then
|
|
begin
|
|
Result := TJvID3PopularimeterFrame(AController.AddFrame(fiPopularimeter));
|
|
Result.EMailAddress := AEmailAddress;
|
|
end;
|
|
end;
|
|
|
|
function TJvID3PopularimeterFrame.GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal;
|
|
begin
|
|
{ Email to user <text string> $00
|
|
Rating $xx
|
|
Counter $xx xx xx xx (xx ...) }
|
|
Result := Length(FEMailAddress) + 1 + 1 + 4;
|
|
end;
|
|
|
|
function TJvID3PopularimeterFrame.GetIsEmpty: Boolean;
|
|
begin
|
|
Result := (FRating = 0) and (FCounter = 0) and (Length(FEMailAddress) = 0);
|
|
end;
|
|
|
|
procedure TJvID3PopularimeterFrame.ReadFrame;
|
|
begin
|
|
{ Email to user <text string> $00
|
|
Rating $xx
|
|
Counter $xx xx xx xx (xx ...) }
|
|
with Stream do
|
|
begin
|
|
ReadStringA(FEMailAddress);
|
|
Read(FRating, 1);
|
|
ReadNumber(FCounter);
|
|
end;
|
|
end;
|
|
|
|
function TJvID3PopularimeterFrame.SameUniqueIDAs(const Frame: TJvID3Frame): Boolean;
|
|
begin
|
|
{ There may be more than one "POPM" frame in each tag, but only one with the
|
|
same email address. }
|
|
Result := (Frame is TJvID3PopularimeterFrame) and
|
|
(Frame.FrameID = FrameID) and (FrameID = fiPopularimeter);
|
|
|
|
if Result then
|
|
Result := AnsiSameStr(TJvID3PopularimeterFrame(Frame).EMailAddress, EMailAddress)
|
|
else
|
|
Result := inherited SameUniqueIDAs(Frame);
|
|
end;
|
|
|
|
procedure TJvID3PopularimeterFrame.SetCounter(const Value: Cardinal);
|
|
begin
|
|
if FCounter <> Value then
|
|
begin
|
|
FCounter := Value;
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3PopularimeterFrame.SetEMailAddress(const Value: AnsiString);
|
|
begin
|
|
if FEMailAddress <> Value then
|
|
begin
|
|
FEMailAddress := Value;
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3PopularimeterFrame.SetRating(const Value: Byte);
|
|
begin
|
|
if FRating <> Value then
|
|
begin
|
|
FRating := Value;
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3PopularimeterFrame.WriteFrame;
|
|
begin
|
|
{ Email to user <text string> $00
|
|
Rating $xx
|
|
Counter $xx xx xx xx (xx ...) }
|
|
with Stream do
|
|
begin
|
|
WriteStringA(EMailAddress);
|
|
WriteTerminatorA;
|
|
Write(Rating, 1);
|
|
WriteNumber(Counter);
|
|
end;
|
|
end;
|
|
|
|
|
|
//=== { TJvID3SimpleListFrame } ==============================================
|
|
|
|
procedure TJvID3SimpleListFrame.AfterConstruction;
|
|
begin
|
|
inherited AfterConstruction;
|
|
|
|
FList := TJvID3stringList.Create;
|
|
TStringList(FList).OnChange := @ListChanged;
|
|
(*
|
|
{$IFDEF COMPILER12_UP}
|
|
FList := TJvID3StringList.Create;
|
|
TStringList(FList).OnChange := ListChanged;
|
|
{$ELSE}
|
|
FList := JclUnicode.TWideStringList.Create;
|
|
JclUnicode.TWideStringList(FList).OnChange := ListChanged;
|
|
{$ENDIF COMPILER12_UP}
|
|
*)
|
|
end;
|
|
|
|
procedure TJvID3SimpleListFrame.BeforeDestruction;
|
|
begin
|
|
inherited BeforeDestruction;
|
|
FList.Free;
|
|
end;
|
|
|
|
function TJvID3SimpleListFrame.CheckFrame(const HandleError: TJvID3HandleError): Boolean;
|
|
begin
|
|
Result := False;
|
|
case FrameID of
|
|
fiLanguage:
|
|
case Encoding of
|
|
ienISO_8859_1:
|
|
Result := CheckIsLanguageList(Self, List, HandleError);
|
|
ienUTF_16, ienUTF_16BE, ienUTF_8:
|
|
Result := CheckIsLanguageList(Self, List, HandleError);
|
|
else
|
|
Error(RsEID3UnknownEncoding);
|
|
end;
|
|
else
|
|
case Encoding of
|
|
ienISO_8859_1:
|
|
Result := CheckList(Self, List, Separator, HandleError);
|
|
ienUTF_16, ienUTF_16BE, ienUTF_8:
|
|
Result := CheckList(Self, List, Separator, HandleError);
|
|
else
|
|
Error(RsEID3UnknownEncoding);
|
|
end;
|
|
end;
|
|
|
|
if not Result and (HandleError = heAutoCorrect) then
|
|
begin
|
|
UpdateFrameSize;
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
class function TJvID3SimpleListFrame.Find(AController: TJvID3Controller;
|
|
const AFrameID: TJvID3FrameID): TJvID3SimpleListFrame;
|
|
var
|
|
Frame: TJvID3Frame;
|
|
begin
|
|
Result := nil;
|
|
if not Assigned(AController) or not AController.Active then
|
|
Exit;
|
|
|
|
Frame := AController.Frames.FindFrame(AFrameID);
|
|
if Frame is TJvID3SimpleListFrame then
|
|
Result := TJvID3SimpleListFrame(Frame);
|
|
end;
|
|
|
|
class function TJvID3SimpleListFrame.FindOrCreate(AController: TJvID3Controller;
|
|
const AFrameID: TJvID3FrameID): TJvID3SimpleListFrame;
|
|
begin
|
|
if not Assigned(AController) then
|
|
ID3Error(RsEID3NoController);
|
|
|
|
Result := Find(AController, AFrameID);
|
|
if not Assigned(Result) then
|
|
begin
|
|
AController.CheckFrameClass(TJvID3SimpleListFrame, AFrameID);
|
|
Result := TJvID3SimpleListFrame(AController.AddFrame(AFrameID));
|
|
end;
|
|
end;
|
|
|
|
function TJvID3SimpleListFrame.GetFixedStringLength: Integer;
|
|
begin
|
|
case FrameID of
|
|
fiLanguage:
|
|
Result := 3
|
|
else
|
|
Result := -1;
|
|
end;
|
|
end;
|
|
|
|
function TJvID3SimpleListFrame.GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal;
|
|
var
|
|
I: Integer;
|
|
CharLength: Integer;
|
|
begin
|
|
if ToEncoding = ienUTF_8 then
|
|
begin
|
|
//Result := 1 + Length(WideStringToUTF8(Text));
|
|
Result := 1 + Length(Text);
|
|
Exit;
|
|
end;
|
|
|
|
{ Encoding byte = 1 }
|
|
Result := 1;
|
|
CharLength := 0;
|
|
|
|
if FixedStringLength > 0 then
|
|
Inc(CharLength, List.Count * FixedStringLength)
|
|
else
|
|
begin
|
|
for I := 0 to List.Count - 1 do
|
|
begin
|
|
Inc(CharLength, Length(List[I]));
|
|
Inc(CharLength); // separator
|
|
end;
|
|
|
|
{ Set one separator less, the last line does not have a trailing
|
|
separator }
|
|
if not IsNullSeparator then
|
|
Dec(CharLength);
|
|
end;
|
|
|
|
case ToEncoding of
|
|
ienISO_8859_1:
|
|
Inc(Result, CharLength);
|
|
ienUTF_16:
|
|
{ Add the BOM's }
|
|
Inc(Result, List.Count * 2 + CharLength * 2);
|
|
ienUTF_16BE:
|
|
Inc(Result, CharLength * 2);
|
|
else
|
|
Error(RsEID3UnknownEncoding);
|
|
end;
|
|
end;
|
|
|
|
function TJvID3SimpleListFrame.GetIsNullSeparator: Boolean;
|
|
begin
|
|
// Result := (FixedStringLength < 0) and (Separator = WideNull);
|
|
Result := (FixedStringLength < 0) and (Separator = #0);
|
|
end;
|
|
|
|
//function TJvID3SimpleListFrame.GetSeparator: WideChar;
|
|
function TJvID3SimpleListFrame.GetSeparator: Char;
|
|
begin
|
|
case FrameID of
|
|
fiLyricist, fiComposer, fiOrigLyricist, fiOrigArtist, fiLeadArtist:
|
|
// Result := WideChar('/');
|
|
Result := '/';
|
|
fiLanguage, fiContentType:
|
|
Result := #0;
|
|
// Result := WideNull;
|
|
else
|
|
{ ?? Unknown }
|
|
//Result := WideChar('/');
|
|
Result := '/';
|
|
end;
|
|
end;
|
|
|
|
//function TJvID3SimpleListFrame.GetText: WideString;
|
|
function TJvID3SimpleListFrame.GetText: String;
|
|
begin
|
|
if Separator <> #0 then
|
|
Result := (FList as TJvID3StringList).GetSeparatedText(Separator)
|
|
else
|
|
Result := (FList as TJvID3StringList).GetSeparatedText('');
|
|
(*
|
|
if Separator <> WideNull then
|
|
{$IFDEF COMPILER12_UP}
|
|
Result := (FList as TJvID3StringList).GetSeparatedText(Separator)
|
|
{$ELSE}
|
|
Result := (FList as JclUnicode.TWideStringList).GetSeparatedText(Separator)
|
|
{$ENDIF COMPILER12_UP}
|
|
else
|
|
{$IFDEF COMPILER12_UP}
|
|
Result := (FList as TJvID3StringList).GetSeparatedText('');
|
|
{$ELSE}
|
|
Result := (FList as JclUnicode.TWideStringList).GetSeparatedText('');
|
|
{$ENDIF COMPILER12_UP}
|
|
*)
|
|
end;
|
|
|
|
procedure TJvID3SimpleListFrame.ListChanged(Sender: TObject);
|
|
begin
|
|
if not (icsReading in Controller.FState) then
|
|
Changed;
|
|
end;
|
|
|
|
//procedure TJvID3SimpleListFrame.SetText(const ANewText: WideString);
|
|
procedure TJvID3SimpleListFrame.SetText(const ANewText: String);
|
|
begin
|
|
if FixedStringLength >= 0 then
|
|
ExtractFixedStrings(ANewText, FixedStringLength, List)
|
|
else
|
|
ExtractStrings(Separator, ANewText, List);
|
|
end;
|
|
|
|
procedure TJvID3SimpleListFrame.ReadFrame;
|
|
const
|
|
cMinBytes: array [TJvID3Encoding] of Byte = (2, 4, 4, 2);
|
|
var
|
|
//S: WideString;
|
|
S: String = '';
|
|
begin
|
|
if IsNullSeparator then
|
|
begin
|
|
with Stream do
|
|
begin
|
|
ReadEncoding;
|
|
while BytesTillEndOfFrame > cMinBytes[Encoding] do
|
|
begin
|
|
ReadStringEnc(S);
|
|
List.Add(S);
|
|
end;
|
|
end;
|
|
end
|
|
else
|
|
inherited ReadFrame;
|
|
end;
|
|
|
|
//procedure TJvID3SimpleListFrame.SetList(Value: {$IFDEF COMPILER12_UP}TStrings{$ELSE}JclUnicode.TWideStrings{$ENDIF COMPILER12_UP});
|
|
procedure TJvID3SimpleListFrame.SetList(Value: TStrings);
|
|
begin
|
|
FList.Assign(Value);
|
|
end;
|
|
|
|
procedure TJvID3SimpleListFrame.WriteFrame;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
if IsNullSeparator then
|
|
begin
|
|
with Stream do
|
|
begin
|
|
WriteEncoding;
|
|
for I := 0 to List.Count - 1 do
|
|
begin
|
|
WriteStringEnc(List[I]);
|
|
WriteTerminatorEnc;
|
|
end;
|
|
end;
|
|
end
|
|
else
|
|
inherited WriteFrame;
|
|
end;
|
|
|
|
|
|
//=== { TJvID3SkipFrame } ====================================================
|
|
|
|
procedure TJvID3SkipFrame.ChangeToVersion(const ANewVersion: TJvID3Version);
|
|
var
|
|
LFrameID: TJvID3FrameID;
|
|
begin
|
|
case ANewVersion of
|
|
ive2_2:
|
|
if Length(FFrameIDStr) = 4 then
|
|
begin
|
|
LFrameID := ID3_StringToFrameID(FFrameIDStr);
|
|
if LFrameID in [fiErrorFrame, fiPaddingFrame] then
|
|
FFrameIDStr := ''
|
|
else
|
|
FFrameIDStr := ID3_FrameIDToString(LFrameID, 3);
|
|
end;
|
|
ive2_3, ive2_4:
|
|
if Length(FFrameIDStr) = 3 then
|
|
begin
|
|
LFrameID := ID3_StringToFrameID(FFrameIDStr);
|
|
if LFrameID in [fiErrorFrame, fiPaddingFrame] then
|
|
FFrameIDStr := ''
|
|
else
|
|
FFrameIDStr := ID3_FrameIDToString(LFrameID, 3);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
//=== { TJvID3Stream } =======================================================
|
|
|
|
procedure TJvID3Stream.BeginReadFrame(const AFrameSize: Integer);
|
|
begin
|
|
if FReadingFrame or FWritingFrame then
|
|
ID3Error(RsEAlreadyReadingWritingFrame);
|
|
|
|
FStartPosition := Position;
|
|
FCurrentFrameSize := AFrameSize;
|
|
FReadingFrame := True;
|
|
end;
|
|
|
|
procedure TJvID3Stream.BeginWriteFrame(const AFrameSize: Integer);
|
|
begin
|
|
if FReadingFrame or FWritingFrame then
|
|
ID3Error(RsEAlreadyReadingWritingFrame);
|
|
|
|
//if not Assigned(Memory) then
|
|
// { $0A = 10, the size of the header }
|
|
// Capacity := $0A;
|
|
|
|
FStartPosition := Position;
|
|
FCurrentFrameSize := AFrameSize;
|
|
FWritingFrame := True;
|
|
end;
|
|
|
|
function TJvID3Stream.CanRead(const ACount: Cardinal): Boolean;
|
|
var
|
|
LBytesToRead: Longint;
|
|
begin
|
|
Assert(not FWritingFrame, RsECannotCallCanRead);
|
|
|
|
if FReadingFrame then
|
|
LBytesToRead := BytesTillEndOfFrame
|
|
else
|
|
LBytesToRead := BytesTillEndOfTag;
|
|
|
|
Result := (LBytesToRead >= 0) and (ACount <= Cardinal(LBytesToRead));
|
|
end;
|
|
|
|
procedure TJvID3Stream.EndReadFrame;
|
|
begin
|
|
if not FReadingFrame then
|
|
ID3Error(RsENotReadingFrame);
|
|
MoveToNextFrame;
|
|
FReadingFrame := False;
|
|
end;
|
|
|
|
procedure TJvID3Stream.EndWriteFrame;
|
|
begin
|
|
if not FWritingFrame then
|
|
ID3Error(RsENotWritingFrame);
|
|
MoveToNextFrame;
|
|
FWritingFrame := False;
|
|
end;
|
|
|
|
function TJvID3Stream.GetBytesTillEndOfFrame: Longint;
|
|
begin
|
|
Result := Int64(FStartPosition) + FCurrentFrameSize - Position;
|
|
end;
|
|
|
|
function TJvID3Stream.GetBytesTillEndOfTag: Longint;
|
|
begin
|
|
Result := Size - Position;
|
|
end;
|
|
|
|
function TJvID3Stream.InFrame(P: Pointer): Boolean;
|
|
begin
|
|
{ This function is used to check _when_ we're reading a frame, that we don't
|
|
read beyond the end marker }
|
|
|
|
Result := not FReadingFrame or (PAnsiChar(P) < PAnsiChar(Memory) + FStartPosition + FCurrentFrameSize);
|
|
end;
|
|
|
|
procedure TJvID3Stream.InitAllowedEncodings(const AVersion: TJvID3Version;
|
|
const AEncoding: TJvID3ForceEncoding);
|
|
begin
|
|
if AEncoding in [ifeDontCare, ifeAuto] then
|
|
case AVersion of
|
|
ive2_2, ive2_3:
|
|
FAllowedEncodings := [ienISO_8859_1, ienUTF_16];
|
|
ive2_4:
|
|
FAllowedEncodings := [ienISO_8859_1, ienUTF_16, ienUTF_16BE, ienUTF_8];
|
|
else
|
|
ID3Error(RsEID3UnknownVersion);
|
|
end
|
|
else
|
|
begin
|
|
{ Convert force encoding type to encoding type }
|
|
FAllowedEncodings := [CForceEncodingToEncoding[AEncoding]];
|
|
if (AVersion in [ive2_2, ive2_3]) and (FAllowedEncodings * [ienUTF_16BE, ienUTF_8] <> []) then
|
|
FAllowedEncodings := [ienUTF_16];
|
|
end;
|
|
|
|
UpdateDestEncoding;
|
|
end;
|
|
|
|
procedure TJvID3Stream.MoveToNextFrame;
|
|
begin
|
|
if FWritingFrame and (BytesTillEndOfFrame <> 0) then
|
|
ID3Error(RsEFrameSizeDiffers);
|
|
|
|
Seek(BytesTillEndOfFrame, soFromCurrent);
|
|
end;
|
|
|
|
function TJvID3Stream.ReadDate(var ADate: TDateTime): Longint;
|
|
var
|
|
Year, Month, Day: Word;
|
|
P: PAnsiChar;
|
|
begin
|
|
P := PAnsiChar(Memory) + Position;
|
|
|
|
Year := 0;
|
|
Month := 0;
|
|
Day := 0;
|
|
Result := 0;
|
|
|
|
while (Result < 8) and InFrame(P) and (P^ in DigitSymbols) do
|
|
begin
|
|
{ Use Day as temp variable }
|
|
Day := Day * 10 + Ord(P^) - Ord('0');
|
|
|
|
{ Format = YYYYMMDD }
|
|
case Result of
|
|
3:
|
|
begin
|
|
Year := Day;
|
|
Day := 0;
|
|
end;
|
|
5:
|
|
begin
|
|
Month := Day;
|
|
Day := 0;
|
|
end;
|
|
end;
|
|
Inc(P);
|
|
Inc(Result);
|
|
end;
|
|
|
|
if Result = 8 then
|
|
begin
|
|
Seek(Result, soFromCurrent);
|
|
try
|
|
ADate := EncodeDate(Year, Month, Day);
|
|
except
|
|
on EConvertError do
|
|
ADate := 0;
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
Result := 0;
|
|
ADate := 0;
|
|
end;
|
|
end;
|
|
|
|
function TJvID3Stream.ReadEnc(var AEncoding: TJvID3Encoding): Longint;
|
|
var
|
|
B: Byte = 0;
|
|
begin
|
|
Result := Read(B, 1);
|
|
if B <= Integer(High(TJvID3Encoding)) then
|
|
SourceEncoding := TJvID3Encoding(B)
|
|
else
|
|
ID3Error(RsEID3UnknownEncoding);
|
|
|
|
AEncoding := DestEncoding;
|
|
end;
|
|
|
|
function TJvID3Stream.ReadFixedNumber(var AValue: Cardinal): Longint;
|
|
begin
|
|
Result := Read(AValue, 4);
|
|
{ Swap byte order from big endian to little endian }
|
|
AValue := ReverseBytes(AValue);
|
|
end;
|
|
|
|
function TJvID3Stream.ReadFixedNumber3(var AValue: Cardinal): Longint;
|
|
type
|
|
TBytes = array [0..3] of Byte;
|
|
begin
|
|
AValue := 0;
|
|
Result := Read(TBytes(AValue)[1], 3);
|
|
{ Swap byte order from big endian to little endian }
|
|
AValue := ReverseBytes(AValue);
|
|
end;
|
|
|
|
procedure TJvID3Stream.ReadFromStream(AStream: TStream;
|
|
const ASize: PtrInt);
|
|
begin
|
|
Position := 0;
|
|
SetSize(ASize);
|
|
if ASize <> 0 then
|
|
AStream.ReadBuffer(Memory^, ASize);
|
|
end;
|
|
|
|
function TJvID3Stream.ReadLanguage(var Language: AnsiString): Longint;
|
|
begin
|
|
if not CanRead(3) then
|
|
Result := 0
|
|
else
|
|
begin
|
|
SetLength(Language, 3);
|
|
Result := Read(Language[1], 3);
|
|
end;
|
|
|
|
if Result < 3 then
|
|
begin
|
|
Language := '';
|
|
Exit;
|
|
end;
|
|
end;
|
|
|
|
function TJvID3Stream.ReadNumber(var AValue: Cardinal): Longint;
|
|
begin
|
|
{ When reading a frame, a number _always_ fills up the remaining part of
|
|
the frame; a number might be bigger than 4 bytes, but that can't be read
|
|
currently }
|
|
if not FReadingFrame then
|
|
ID3Error(RsENotReadingFrame);
|
|
|
|
if BytesTillEndOfFrame = 4 then
|
|
begin
|
|
Result := Read(AValue, 4);
|
|
{ Swap byte order from big endian to little endian }
|
|
AValue := ReverseBytes(AValue);
|
|
end
|
|
else
|
|
begin
|
|
{ Error (if BytesTillEndOfFrame < 4) or not implemented (if BytesTillEndOfFrame > 4) }
|
|
AValue := 0;
|
|
Result := 0;
|
|
end;
|
|
end;
|
|
|
|
function TJvID3Stream.ReadStringA(var SA: AnsiString): Longint;
|
|
var
|
|
P, StartPos: PAnsiChar;
|
|
begin
|
|
StartPos := PAnsiChar(Memory) + Position;
|
|
P := StartPos;
|
|
|
|
while (P^ <> #0) and InFrame(P) do
|
|
Inc(P);
|
|
Result := P - StartPos;
|
|
|
|
SetString(SA, StartPos, Result);
|
|
|
|
{ Skip terminator }
|
|
if InFrame(P) then
|
|
Inc(Result);
|
|
|
|
Seek(Result, soFromCurrent);
|
|
end;
|
|
|
|
//function TJvID3Stream.ReadStringEnc(var S: WideString): Longint;
|
|
function TJvID3Stream.ReadStringEnc(var S: String): LongInt;
|
|
var
|
|
SA: AnsiString = '';
|
|
SW: WideString = '';
|
|
begin
|
|
case SourceEncoding of
|
|
ienISO_8859_1:
|
|
begin
|
|
Result := ReadStringA(SA);
|
|
S := Iso_8859_1ToUTF8(SA)
|
|
//S := AnsiStringToUTF16(SA);
|
|
end;
|
|
ienUTF_16, ienUTF_16BE:
|
|
// Result := ReadStringW(S);
|
|
begin
|
|
Result := ReadStringW(SW);
|
|
S := UTF8Encode(SW);
|
|
end;
|
|
ienUTF_8:
|
|
Result := ReadStringUTF8(S);
|
|
else
|
|
Result := 0;
|
|
ID3Error(RsEID3UnknownEncoding);
|
|
end;
|
|
end;
|
|
|
|
function TJvID3Stream.ReadStringUTF8(var SA: String): LongInt;
|
|
begin
|
|
Result := ReadStringA(SA);
|
|
end;
|
|
{
|
|
function TJvID3Stream.ReadStringUTF8(var SW: WideString): Longint;
|
|
var
|
|
SA: AnsiString;
|
|
begin
|
|
Result := ReadStringA(SA);
|
|
SW := UTF8ToWideString(SA);
|
|
end;
|
|
}
|
|
|
|
function TJvID3Stream.ReadStringW(var SW: WideString): Longint;
|
|
var
|
|
Order: WideChar = #0;
|
|
P: PWideChar;
|
|
StartPos: PAnsiChar;
|
|
TerminatorFound: Boolean;
|
|
WideCharCount: Integer;
|
|
begin
|
|
Result := 0;
|
|
|
|
if SourceEncoding = ienUTF_16 then
|
|
begin
|
|
{ Try read the BOM }
|
|
if not CanRead(2) then
|
|
begin
|
|
SW := '';
|
|
Exit;
|
|
end;
|
|
|
|
Result := Read(Order, 2);
|
|
if (Order <> BOM_LSB_FIRST) and (Order <> BOM_MSB_FIRST) then
|
|
begin
|
|
SW := '';
|
|
Exit;
|
|
end;
|
|
end;
|
|
|
|
StartPos := PAnsiChar(Memory) + Position;
|
|
P := PWideChar(StartPos);
|
|
|
|
{ Read until #0#0 found or until FEndMarker }
|
|
while InFrame(P) and not (P^ = WideNull) do
|
|
Inc(P);
|
|
|
|
TerminatorFound := InFrame(P);
|
|
WideCharCount := (PAnsiChar(Pointer(P)) - StartPos) div 2;
|
|
Result := Result + WideCharCount * 2;
|
|
|
|
SetLength(SW, WideCharCount);
|
|
if WideCharCount > 0 then
|
|
Move(StartPos[0], SW[1], WideCharCount * SizeOf(WideChar));
|
|
if (SourceEncoding = ienUTF_16) and (Order = BOM_MSB_FIRST) then
|
|
SW := BEToN(SW);
|
|
//StrSwapByteOrder(PWideChar(SW));
|
|
|
|
{ Skip Terminator }
|
|
if TerminatorFound then
|
|
begin
|
|
Inc(Result, 2);
|
|
Inc(WideCharCount);
|
|
end;
|
|
|
|
Seek(WideCharCount * 2, soFromCurrent);
|
|
end;
|
|
|
|
function TJvID3Stream.ReadSyncSafeInteger(var AInt: Cardinal;
|
|
const ASize: Byte): Longint;
|
|
var
|
|
Value: PAnsiChar;
|
|
begin
|
|
GetMem(Value, ASize);
|
|
try
|
|
Result := Read(Value^, ASize);
|
|
UnSyncSafe(Value^, ASize, AInt);
|
|
finally
|
|
FreeMem(Value);
|
|
end;
|
|
end;
|
|
|
|
function TJvID3Stream.ReadSyncSafeInteger(var AInt: Int64;
|
|
const ASize: Byte): Longint;
|
|
var
|
|
Value: PAnsiChar;
|
|
begin
|
|
GetMem(Value, ASize);
|
|
try
|
|
Result := Read(Value^, ASize);
|
|
UnSyncSafe(Value^, ASize, AInt);
|
|
finally
|
|
FreeMem(Value);
|
|
end;
|
|
end;
|
|
|
|
function TJvID3Stream.ReadSyncSafeInteger(var AInt: Cardinal): Longint;
|
|
var
|
|
Value: Cardinal = 0;
|
|
begin
|
|
Result := Read(Value, 4);
|
|
UnSyncSafe(Value, 4, AInt);
|
|
end;
|
|
|
|
//function TJvID3Stream.ReadUserString(var S1, S2: WideString): Longint;
|
|
function TJvID3Stream.ReadUserString(var S1, S2: String): LongInt;
|
|
var
|
|
SA1: AnsiString = '';
|
|
SA2: AnsiString = '';
|
|
SW1: WideString = '';
|
|
SW2: WideString = '';
|
|
begin
|
|
case SourceEncoding of
|
|
ienISO_8859_1:
|
|
begin
|
|
Result := ReadUserStringA(SA1, SA2);
|
|
S1 := ISO_8859_1ToUTF8(SA1);
|
|
S2 := ISO_8859_1ToUTF8(SA2);
|
|
{
|
|
S1 := AnsiStringToUTF16(SA1);
|
|
S2 := AnsiStringToUTF16(SA2);
|
|
}
|
|
end;
|
|
ienUTF_16, ienUTF_16BE:
|
|
begin
|
|
Result := ReadUserStringW(SW1, SW2);
|
|
S1 := UTF8Encode(SW1);
|
|
S2 := UTF8Encode(SW2);
|
|
end;
|
|
//Result := ReadUserStringW(S1, S2);
|
|
ienUTF_8:
|
|
Result := ReadUserStringUTF8(S1, S2);
|
|
else
|
|
Result := 0;
|
|
ID3Error(RsEID3UnknownEncoding);
|
|
end;
|
|
end;
|
|
|
|
function TJvID3Stream.ReadUserStringA(var SA1, SA2: AnsiString): Longint;
|
|
begin
|
|
Result := ReadStringA(SA1);
|
|
|
|
if CanRead(1) then
|
|
Result := Result + ReadStringA(SA2)
|
|
else
|
|
SA2 := '';
|
|
end;
|
|
|
|
function TJvID3Stream.ReadUserStringUTF8(var SA1, SA2: String): LongInt;
|
|
begin
|
|
Result := ReadUserStringA(SA1, SA2);
|
|
end;
|
|
{
|
|
function TJvID3Stream.ReadUserStringUTF8(var SW1, SW2: WideString): Longint;
|
|
var
|
|
SA1, SA2: AnsiString;
|
|
begin
|
|
Result := ReadUserStringA(SA1, SA2);
|
|
SW1 := UTF8ToWideString(SA1);
|
|
SW2 := UTF8ToWideString(SA2);
|
|
end; }
|
|
|
|
function TJvID3Stream.ReadUserStringW(var SW1, SW2: WideString): Longint;
|
|
begin
|
|
Result := ReadStringW(SW1);
|
|
|
|
if CanRead(2) then
|
|
Result := Result + ReadStringW(SW2)
|
|
else
|
|
SW2 := '';
|
|
end;
|
|
|
|
procedure TJvID3Stream.SetSourceEncoding(const Value: TJvID3Encoding);
|
|
begin
|
|
if FSourceEncoding <> Value then
|
|
begin
|
|
FSourceEncoding := Value;
|
|
UpdateDestEncoding;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3Stream.UpdateDestEncoding;
|
|
const
|
|
CEncodingTry: array [0..3] of TJvID3Encoding =
|
|
(ienUTF_16, ienUTF_16BE, ienUTF_8, ienISO_8859_1);
|
|
var
|
|
I: Integer;
|
|
begin
|
|
{ FSourceEncoding is the encoding of a specific frame; the controller
|
|
may prevent writing of some encodings (for example if the
|
|
version (2.3) doesn't support it).
|
|
|
|
Therefore we use FDestEncoding, that is set to the encoding actually
|
|
written to the stream
|
|
(when writing, symetrically for reading )
|
|
}
|
|
Assert(FAllowedEncodings <> [], RsEAllowedEncodingsIsEmpty);
|
|
|
|
FDestEncoding := FSourceEncoding;
|
|
if not (FDestEncoding in FAllowedEncodings) then
|
|
begin
|
|
I := 0;
|
|
while (I <= High(CEncodingTry)) and not (CEncodingTry[I] in FAllowedEncodings) do
|
|
Inc(I);
|
|
if I > High(CEncodingTry) then
|
|
// insanity, should not happen
|
|
ID3Error(RsECouldNotFindAllowableEncoding);
|
|
|
|
FDestEncoding := CEncodingTry[I];
|
|
end;
|
|
end;
|
|
|
|
function TJvID3Stream.WriteDate(const ADate: TDateTime): Longint;
|
|
var
|
|
Year, Month, Day: Word;
|
|
S: AnsiString;
|
|
begin
|
|
{ Format = YYYYMMDD }
|
|
DecodeDate(ADate, Year, Month, Day);
|
|
S := {$IFDEF HAS_UNIT_ANSISTRINGS}AnsiStrings.{$ENDIF HAS_UNIT_ANSISTRINGS}Format('%.4d%.2d%.2d', [Year, Month, Day]);
|
|
Result := WriteStringA(S);
|
|
end;
|
|
|
|
function TJvID3Stream.WriteEnc: Longint;
|
|
begin
|
|
Result := Write(DestEncoding, 1);
|
|
end;
|
|
|
|
function TJvID3Stream.WriteFixedNumber(AValue: Cardinal): Longint;
|
|
begin
|
|
{ Swap byte order from little endian to big endian }
|
|
AValue := ReverseBytes(AValue);
|
|
Result := Write(AValue, 4);
|
|
end;
|
|
|
|
function TJvID3Stream.WriteFixedNumber3(AValue: Cardinal): Longint;
|
|
type
|
|
TBytes = array [0..3] of Byte;
|
|
begin
|
|
Assert(AValue <= $00FFFFFF, RsEValueTooBig);
|
|
|
|
{ Swap byte order from little endian to big endian }
|
|
AValue := ReverseBytes(AValue);
|
|
Result := Write(TBytes(AValue)[1], 3);
|
|
end;
|
|
|
|
function TJvID3Stream.WriteLanguage(const Language: AnsiString): Longint;
|
|
begin
|
|
if Length(Language) <> 3 then
|
|
ID3Error(RsELanguageNotOfLength3);
|
|
|
|
Result := WriteStringA(Language);
|
|
end;
|
|
|
|
function TJvID3Stream.WriteNumber(AValue: Cardinal): Longint;
|
|
begin
|
|
{ Swap byte order from little endian to big endian }
|
|
AValue := ReverseBytes(AValue);
|
|
Result := Write(AValue, 4);
|
|
end;
|
|
|
|
function TJvID3Stream.WritePadding(const Count: Longint): Longint;
|
|
var
|
|
Pos: Longint;
|
|
begin
|
|
Pos := Position + Count;
|
|
if Pos > 0 then
|
|
begin
|
|
if Pos > Size then
|
|
begin
|
|
if Pos > Capacity then
|
|
Capacity := Pos;
|
|
Size := Pos;
|
|
end;
|
|
FillChar(Pointer(PAnsiChar(Memory) + Position)^, Count, 0);
|
|
//System.Move(Buffer, Pointer(PAnsiChar(FMemory) + FPosition)^, Count);
|
|
Position := Pos;
|
|
Result := Count;
|
|
Exit;
|
|
end;
|
|
Result := 0;
|
|
end;
|
|
|
|
function TJvID3Stream.WriteStringA(const SA: AnsiString): Longint;
|
|
begin
|
|
Result := Write(PAnsiChar(SA)^, Length(SA));
|
|
end;
|
|
|
|
//function TJvID3Stream.WriteStringEnc(const S: WideString): Longint;
|
|
function TJvID3Stream.WriteStringEnc(const S: String): LongInt;
|
|
begin
|
|
case DestEncoding of
|
|
ienISO_8859_1:
|
|
Result := WriteStringA(UTF8toISO_8859_1(S));
|
|
// Result := WriteStringA(UTF16ToAnsiString(S));
|
|
ienUTF_16, ienUTF_16BE:
|
|
Result := WriteStringW(UTF8Decode(S));
|
|
//Result := WriteStringW(S);
|
|
ienUTF_8:
|
|
Result := WriteStringUTF8(S);
|
|
else
|
|
Result := 0;
|
|
ID3Error(RsEID3UnknownEncoding);
|
|
end;
|
|
end;
|
|
|
|
function TJvID3Stream.WriteStringUTF8(const SA: String): LongInt;
|
|
begin
|
|
Result := WriteStringA(SA);
|
|
end;
|
|
{
|
|
function TJvID3Stream.WriteStringUTF8(const SW: WideString): Longint;
|
|
var
|
|
SA: AnsiString;
|
|
begin
|
|
SA := WideStringToUTF8(SW);
|
|
Result := WriteStringA(SA);
|
|
end;
|
|
}
|
|
function TJvID3Stream.WriteStringW(const SW: WideString): Longint;
|
|
var
|
|
Order: WideChar;
|
|
begin
|
|
Result := 0;
|
|
|
|
if DestEncoding = ienUTF_16 then
|
|
begin
|
|
Order := BOM_LSB_FIRST;
|
|
Result := Write(Order, 2);
|
|
end;
|
|
|
|
Result := Result + Write(SW[1], 2 * Length(SW));
|
|
end;
|
|
|
|
function TJvID3Stream.WriteSyncSafeInteger(const AInt: Int64;
|
|
const ASize: Byte): Longint;
|
|
var
|
|
Value: PAnsiChar;
|
|
begin
|
|
GetMem(Value, ASize);
|
|
try
|
|
SyncSafe(AInt, Value^, ASize);
|
|
Result := Write(Value^, ASize);
|
|
finally
|
|
FreeMem(Value);
|
|
end;
|
|
end;
|
|
|
|
function TJvID3Stream.WriteSyncSafeInteger(const AInt: Cardinal;
|
|
const ASize: Byte): Longint;
|
|
var
|
|
Value: PAnsiChar;
|
|
begin
|
|
GetMem(Value, ASize);
|
|
try
|
|
SyncSafe(AInt, Value^, ASize);
|
|
Result := Write(Value^, ASize);
|
|
finally
|
|
FreeMem(Value);
|
|
end;
|
|
end;
|
|
|
|
function TJvID3Stream.WriteSyncSafeInteger(const AInt: Cardinal): Longint;
|
|
var
|
|
Value: Cardinal = 0;
|
|
begin
|
|
SyncSafe(AInt, Value, 4);
|
|
Result := Write(Value, 4);
|
|
end;
|
|
|
|
function TJvID3Stream.WriteTerminatorA: Longint;
|
|
var
|
|
Ch: AnsiChar;
|
|
begin
|
|
Ch := #0;
|
|
Result := Write(Ch, 1);
|
|
end;
|
|
|
|
function TJvID3Stream.WriteTerminatorEnc: Longint;
|
|
begin
|
|
case DestEncoding of
|
|
ienISO_8859_1, ienUTF_8:
|
|
Result := WriteTerminatorA;
|
|
ienUTF_16, ienUTF_16BE:
|
|
Result := WriteTerminatorW;
|
|
else
|
|
Result := 0;
|
|
ID3Error(RsEID3UnknownEncoding);
|
|
end;
|
|
end;
|
|
|
|
function TJvID3Stream.WriteTerminatorW: Longint;
|
|
var
|
|
Ch: WideChar;
|
|
begin
|
|
Ch := WideNull;
|
|
Result := Write(Ch, 2);
|
|
end;
|
|
|
|
function TJvID3Stream.WriteUserString(const S1, S2: String): LongInt;
|
|
begin
|
|
case DestEncoding of
|
|
ienISO_8859_1:
|
|
Result := WriteUserStringA(UTF8ToISO_8859_1(S1), UTF8ToISO_8859_1(S2));
|
|
//Result := WriteUserStringA(UTF16ToAnsiString(S1), UTF16ToAnsiString(S2));
|
|
ienUTF_16, ienUTF_16BE:
|
|
Result := WriteUserStringW(UTF8Decode(S1), UTF8Decode(S2));
|
|
//Result := WriteUserStringW(S1, S2);
|
|
ienUTF_8:
|
|
Result := WriteUserStringUTF8(S1, S2);
|
|
else
|
|
Result := 0;
|
|
ID3Error(RsEID3UnknownEncoding);
|
|
end;
|
|
end;
|
|
{
|
|
function TJvID3Stream.WriteUserString(const S1, S2: WideString): Longint;
|
|
begin
|
|
case DestEncoding of
|
|
ienISO_8859_1:
|
|
Result := WriteUserStringA(UTF16ToAnsiString(S1), UTF16ToAnsiString(S2));
|
|
ienUTF_16, ienUTF_16BE:
|
|
Result := WriteUserStringW(S1, S2);
|
|
ienUTF_8:
|
|
Result := WriteUserStringUTF8(S1, S2);
|
|
else
|
|
Result := 0;
|
|
ID3Error(RsEID3UnknownEncoding);
|
|
end;
|
|
end;
|
|
}
|
|
function TJvID3Stream.WriteUserStringA(const SA1, SA2: AnsiString): Longint;
|
|
begin
|
|
Result := WriteStringA(SA1) + WriteTerminatorA + WriteStringA(SA2);
|
|
end;
|
|
|
|
function TJvID3Stream.WriteUserStringUTF8(const SA1, SA2: String): LongInt;
|
|
begin
|
|
Result := WriteUserStringA(SA1, SA2);
|
|
end;
|
|
{
|
|
function TJvID3Stream.WriteUserStringUTF8(const SW1, SW2: WideString): Longint;
|
|
var
|
|
SA1, SA2: AnsiString;
|
|
begin
|
|
SA1 := WideStringToUTF8(SW1);
|
|
SA2 := WideStringToUTF8(SW2);
|
|
Result := WriteUserStringA(SA1, SA2);
|
|
end;
|
|
}
|
|
function TJvID3Stream.WriteUserStringW(const SW1, SW2: WideString): Longint;
|
|
begin
|
|
Result := WriteStringW(SW1) + WriteTerminatorW + WriteStringW(SW2);
|
|
end;
|
|
|
|
|
|
//=== { TJvID3StringList } ===================================================
|
|
|
|
function TJvID3StringList.GetSeparatedText(const Separator: string): string;
|
|
var
|
|
I, L: Integer;
|
|
Size: Integer;
|
|
lCount: Integer;
|
|
SepLen: Integer;
|
|
P: PChar;
|
|
S: string;
|
|
begin
|
|
LCount := GetCount;
|
|
Size := 0;
|
|
SepLen := Length(Separator);
|
|
for I := 0 to lCount - 1 do
|
|
Inc(Size, Length(Get(I)) + SepLen);
|
|
|
|
// set one separator less, the last line does not need a trailing separator
|
|
SetLength(Result, Size - SepLen);
|
|
if Size > 0 then
|
|
begin
|
|
P := Pointer(Result);
|
|
I := 0;
|
|
while True do
|
|
begin
|
|
S := Get(I);
|
|
L := Length(S);
|
|
if L <> 0 then
|
|
begin
|
|
// add current string
|
|
System.Move(Pointer(S)^, P^, L * SizeOf(Char));
|
|
Inc(P, L);
|
|
end;
|
|
Inc(I);
|
|
if I = lCount then
|
|
Break;
|
|
|
|
// add separators
|
|
if SepLen <> 0 then
|
|
begin
|
|
System.Move(Pointer(Separator)^, P^, SepLen * SizeOf(Char));
|
|
Inc(P, SepLen);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
//=== { TJvID3TermsOfUseFrame } ==============================================
|
|
|
|
procedure TJvID3TermsOfUseFrame.Assign(Source: TPersistent);
|
|
begin
|
|
if Source is TJvID3TermsOfUseFrame then
|
|
begin
|
|
FText := TJvID3TermsOfUseFrame(Source).Text;
|
|
FLanguage := TJvID3TermsOfUseFrame(Source).Language;
|
|
end;
|
|
|
|
inherited Assign(Source);
|
|
end;
|
|
|
|
class function TJvID3TermsOfUseFrame.CanAddFrame(AController: TJvID3Controller;
|
|
AFrameID: TJvID3FrameID): Boolean;
|
|
begin
|
|
{ There may only be one 'USER' frame in a tag}
|
|
Result := ((AFrameID = fiTermsOfUse) and not AController.HasFrame(fiTermsOfUse)) or
|
|
inherited CanAddFrame(AController, AFrameID);
|
|
end;
|
|
|
|
function TJvID3TermsOfUseFrame.CheckFrame(const HandleError: TJvID3HandleError): Boolean;
|
|
begin
|
|
Result := CheckIsLanguageA(Self, FLanguage, HandleError);
|
|
|
|
{ If something has changed update the framesize }
|
|
if not Result and (HandleError = heAutoCorrect) then
|
|
begin
|
|
UpdateFrameSize;
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3TermsOfUseFrame.Clear;
|
|
begin
|
|
FText := '';
|
|
FLanguage := '';
|
|
inherited Clear;
|
|
end;
|
|
|
|
class function TJvID3TermsOfUseFrame.Find(AController: TJvID3Controller): TJvID3TermsOfUseFrame;
|
|
var
|
|
Frame: TJvID3Frame;
|
|
begin
|
|
Result := nil;
|
|
if not Assigned(AController) or not AController.Active then
|
|
Exit;
|
|
|
|
Frame := AController.Frames.FindFrame(fiTermsOfUse);
|
|
if Frame is TJvID3TermsOfUseFrame then
|
|
Result := TJvID3TermsOfUseFrame(Frame);
|
|
end;
|
|
|
|
class function TJvID3TermsOfUseFrame.FindOrCreate(AController: TJvID3Controller): TJvID3TermsOfUseFrame;
|
|
begin
|
|
if not Assigned(AController) then
|
|
ID3Error(RsEID3NoController);
|
|
|
|
Result := Find(AController);
|
|
if not Assigned(Result) then
|
|
Result := TJvID3TermsOfUseFrame(AController.AddFrame(fiTermsOfUse));
|
|
end;
|
|
|
|
function TJvID3TermsOfUseFrame.GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal;
|
|
begin
|
|
{ Text encoding $xx
|
|
Language $xx xx xx
|
|
The actual text <text string according to encoding>
|
|
}
|
|
Result := 1 + 3 + LengthEnc(Text, ToEncoding);
|
|
end;
|
|
|
|
function TJvID3TermsOfUseFrame.GetIsEmpty: Boolean;
|
|
begin
|
|
Result := (Text = '') and (Length(FLanguage) = 0);
|
|
end;
|
|
|
|
function TJvID3TermsOfUseFrame.MustWriteAsUTF: Boolean;
|
|
begin
|
|
Result := HasNonISO_8859_1Chars(Text);
|
|
end;
|
|
|
|
procedure TJvID3TermsOfUseFrame.ReadFrame;
|
|
begin
|
|
{ Text encoding $xx
|
|
Language $xx xx xx
|
|
The actual text <text string according to encoding>
|
|
}
|
|
with Stream do
|
|
begin
|
|
ReadEncoding;
|
|
ReadLanguage(FLanguage);
|
|
ReadStringEnc(FText);
|
|
end;
|
|
end;
|
|
|
|
function TJvID3TermsOfUseFrame.SameUniqueIDAs(const Frame: TJvID3Frame): Boolean;
|
|
begin
|
|
{ There may only be one 'USER' frame in a tag}
|
|
Result := (Assigned(Frame) and (Frame.FrameID = FrameID) and (FrameID = fiTermsOfUse)) or
|
|
inherited SameUniqueIDAs(Frame);
|
|
end;
|
|
|
|
procedure TJvID3TermsOfUseFrame.SetLanguage(const Value: AnsiString);
|
|
begin
|
|
if FLanguage <> Value then
|
|
begin
|
|
FLanguage := Value;
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3TermsOfUseFrame.SetText(const Value: String);
|
|
//procedure TJvID3TermsOfUseFrame.SetText(const Value: WideString);
|
|
begin
|
|
if Value <> FText then
|
|
begin
|
|
FText := Value;
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
function TJvID3TermsOfUseFrame.SupportsVersion(const AVersion: TJvID3Version): Boolean;
|
|
begin
|
|
case FrameID of
|
|
{ ** Not supported in 2.2 ** }
|
|
|
|
fiTermsOfUse:
|
|
Result := AVersion in [ive2_3, ive2_4];
|
|
else
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3TermsOfUseFrame.WriteFrame;
|
|
begin
|
|
{ Text encoding $xx
|
|
Language $xx xx xx
|
|
The actual text <text string according to encoding>
|
|
}
|
|
with Stream do
|
|
begin
|
|
WriteEncoding;
|
|
WriteLanguage(Language);
|
|
WriteStringEnc(Text);
|
|
end;
|
|
end;
|
|
|
|
|
|
//=== { TJvID3TextFrame } ====================================================
|
|
|
|
procedure TJvID3TextFrame.ChangeToVersion(const ANewVersion: TJvID3Version);
|
|
var
|
|
Year: Word;
|
|
LDate: TDateTime;
|
|
Frame: TJvID3Frame;
|
|
begin
|
|
if ANewVersion <> ive2_4 then
|
|
Exit;
|
|
|
|
{ Change
|
|
|
|
fiYear, fiDate, fiTime, fiRecordingDates frames into 1 fiRecordingTime frame }
|
|
|
|
if FrameID in [fiDate, fiTime] then
|
|
begin
|
|
if Assigned(FFrames.FindFrame(fiRecordingTime)) then
|
|
Exit;
|
|
|
|
{ 1. Determine the year from a fiYear frame}
|
|
Frame := TJvID3NumberFrame.Find(FController, fiYear);
|
|
if Assigned(Frame) then
|
|
Year := TJvID3NumberFrame(Frame).Value
|
|
else
|
|
{ hm, no year frame , just assume it's current year }
|
|
Year := YearOf(Date);
|
|
|
|
{ 2. Determine month + day from a fiDate frame }
|
|
Frame := TJvID3TextFrame.Find(FController, fiDate);
|
|
if Assigned(Frame) then
|
|
with TJvID3TextFrame(Frame) do
|
|
LDate := GetID3Date(Text, Encoding, Year)
|
|
else
|
|
try
|
|
{ hm, no date frame , just assume it's 1 jan }
|
|
LDate := EncodeDate(Year, 1, 1);
|
|
except
|
|
on EConvertError do
|
|
LDate := 0;
|
|
end;
|
|
|
|
{ 3. Determine hour + min from a fiTime frame}
|
|
Frame := TJvID3TextFrame.Find(FController, fiTime);
|
|
if Assigned(Frame) then
|
|
with TJvID3TextFrame(Frame) do
|
|
LDate := LDate + GetID3Time(Text, Encoding);
|
|
|
|
{ 4. Copy constructed date to a fiRecordingTime frame }
|
|
TJvID3TimestampFrame.FindOrCreate(FController, fiRecordingTime).Value := LDate;
|
|
end;
|
|
end;
|
|
|
|
function TJvID3TextFrame.CheckFrame(const HandleError: TJvID3HandleError): Boolean;
|
|
begin
|
|
case FrameID of
|
|
fiTime:
|
|
Result := CheckIsID3Time(Self, FText, HandleError);
|
|
fiDate:
|
|
Result := CheckIsID3Date(Self, FText, HandleError);
|
|
fiPartInSet:
|
|
Result := CheckIsID3PartInSet(Self, FText, HandleError);
|
|
fiTrackNum:
|
|
Result := CheckIsID3PartInSet(Self, FText, HandleError);
|
|
else
|
|
Result := True;
|
|
end;
|
|
|
|
{ If something has changed update the framesize }
|
|
if not Result and (HandleError = heAutoCorrect) then
|
|
begin
|
|
UpdateFrameSize;
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
class function TJvID3TextFrame.Find(AController: TJvID3Controller;
|
|
const AFrameID: TJvID3FrameID): TJvID3TextFrame;
|
|
var
|
|
Frame: TJvID3Frame;
|
|
begin
|
|
Result := nil;
|
|
if not Assigned(AController) or not AController.Active then
|
|
Exit;
|
|
|
|
Frame := AController.Frames.FindFrame(AFrameID);
|
|
if Frame is TJvID3TextFrame then
|
|
Result := TJvID3TextFrame(Frame);
|
|
end;
|
|
|
|
class function TJvID3TextFrame.FindOrCreate(AController: TJvID3Controller;
|
|
const AFrameID: TJvID3FrameID): TJvID3TextFrame;
|
|
begin
|
|
if not Assigned(AController) then
|
|
ID3Error(RsEID3NoController);
|
|
|
|
Result := Find(AController, AFrameID);
|
|
if not Assigned(Result) then
|
|
begin
|
|
AController.CheckFrameClass(TJvID3TextFrame, AFrameID);
|
|
Result := TJvID3TextFrame(AController.AddFrame(AFrameID));
|
|
end;
|
|
end;
|
|
|
|
function TJvID3TextFrame.Gettext: String;
|
|
//function TJvID3TextFrame.GetText: WideString;
|
|
begin
|
|
Result := FText;
|
|
end;
|
|
|
|
//procedure TJvID3TextFrame.SetText(const ANewText: WideString);
|
|
procedure TJvID3TextFrame.SetText(const ANewText: String);
|
|
begin
|
|
if ANewText <> FText then
|
|
begin
|
|
FText := ANewText;
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
|
|
//=== { TJvID3TimestampFrame } ===============================================
|
|
|
|
procedure TJvID3TimestampFrame.ChangeToVersion(const ANewVersion: TJvID3Version);
|
|
var
|
|
Year, Month, Day: Word;
|
|
Hour, Min: Word;
|
|
Dummy1, Dummy2: Word;
|
|
begin
|
|
{ Change
|
|
|
|
* fiRecordingTime into fiYear, fiDate, fiTime, fiRecordingDates
|
|
* fiOrigReleaseTime into fiOrigYear }
|
|
|
|
if IsEmpty or not (ANewVersion in [ive2_2, ive2_3]) then
|
|
Exit;
|
|
|
|
if FrameID = fiRecordingTime then
|
|
begin
|
|
{ Check if frames don't exists already }
|
|
if [fiYear, fiDate, fiTime] * FFrames.GetFrameIDs = [] then
|
|
begin
|
|
{ 1. Determine the Year, Month, Day, Hour and Min from this frame }
|
|
DecodeTime(Value, Hour, Min, Dummy1, Dummy2);
|
|
DecodeDate(Value, Year, Month, Day);
|
|
|
|
{ 2. Create a new fiYear frame for the Year }
|
|
TJvID3NumberFrame.FindOrCreate(FController, fiYear).Value := Year;
|
|
|
|
{ 3. Create a new fiDate frame [format = 'DDMM'] for the Day and Month }
|
|
TJvID3TextFrame.FindOrCreate(FController, fiDate).Text :=
|
|
Format('%.2d%.2d', [Day, Month]);
|
|
|
|
{ 4. Create a new fiTime frame [format = 'HHMM'] for the Hour and Min }
|
|
TJvID3TextFrame.FindOrCreate(FController, fiTime).Text :=
|
|
Format('%.2d%.2d', [Hour, Min]);
|
|
end;
|
|
end
|
|
else
|
|
if FrameID = fiOrigReleaseTime then
|
|
begin
|
|
{ Check if frames don't exists already }
|
|
if not (fiOrigYear in FFrames.GetFrameIDs) then
|
|
begin
|
|
DecodeDate(Value, Year, Dummy1, Dummy2);
|
|
|
|
{ We can only store the year in a fiOrigYear frame, ie no other frames
|
|
are supported in v2.3 }
|
|
TJvID3NumberFrame.FindOrCreate(FController, fiOrigYear).Value := Year;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TJvID3TimestampFrame.CheckFrame(const HandleError: TJvID3HandleError): Boolean;
|
|
begin
|
|
Result := True;
|
|
end;
|
|
|
|
class function TJvID3TimestampFrame.Find(AController: TJvID3Controller;
|
|
const AFrameID: TJvID3FrameID): TJvID3TimestampFrame;
|
|
var
|
|
Frame: TJvID3Frame;
|
|
begin
|
|
Result := nil;
|
|
if not Assigned(AController) or not AController.Active then
|
|
Exit;
|
|
|
|
Frame := AController.Frames.FindFrame(AFrameID);
|
|
if Frame is TJvID3TimestampFrame then
|
|
Result := TJvID3TimestampFrame(Frame);
|
|
end;
|
|
|
|
class function TJvID3TimestampFrame.FindOrCreate(AController: TJvID3Controller;
|
|
const AFrameID: TJvID3FrameID): TJvID3TimestampFrame;
|
|
begin
|
|
if not Assigned(AController) then
|
|
ID3Error(RsEID3NoController);
|
|
|
|
Result := Find(AController, AFrameID);
|
|
if not Assigned(Result) then
|
|
begin
|
|
AController.CheckFrameClass(TJvID3TimestampFrame, AFrameID);
|
|
Result := TJvID3TimestampFrame(AController.AddFrame(AFrameID));
|
|
end;
|
|
end;
|
|
|
|
{ The timestamp fields are based on a subset of ISO 8601. When being as
|
|
precise as possible the format of a time string is
|
|
yyyy-MM-ddTHH:mm:ss (year, "-", month, "-", day, "T", hour (out of
|
|
24), ":", minutes, ":", seconds), but the precision may be reduced by
|
|
removing as many time indicators as wanted. Hence valid timestamps
|
|
are
|
|
yyyy, yyyy-MM, yyyy-MM-dd, yyyy-MM-ddTHH, yyyy-MM-ddTHH:mm and
|
|
yyyy-MM-ddTHH:mm:ss. All time stamps are UTC. For durations, use
|
|
the slash character as described in 8601, and for multiple non-
|
|
contiguous dates, use multiple strings, if allowed by the frame
|
|
definition. }
|
|
|
|
//function TJvID3TimestampFrame.GetText: WideString;
|
|
function TJvID3TimeStampFrame.GetText: String;
|
|
var
|
|
Year, Month, Day, Hour, Min, Sec, Dummy: Word;
|
|
begin
|
|
DecodeDate(Value, Year, Month, Day);
|
|
DecodeTime(Value, Hour, Min, Sec, Dummy);
|
|
if Year > 9999 then
|
|
Year := 9999;
|
|
if (Hour = 0) and (Min = 0) and (Sec = 0) then
|
|
Result := Format('%.4d-%.2d-%.2d', [Year, Month, Day])
|
|
else
|
|
Result := Format('%.4d-%.2d-%.2dT%.2d:%.2d:%.2d', [Year, Month, Day, Hour, Min, Sec]);
|
|
end;
|
|
|
|
//procedure TJvID3TimestampFrame.SetText(const ANewText: WideString);
|
|
procedure TJvID3TimeStampFrame.SetText(const ANewText: String);
|
|
type
|
|
TimeKind = (tkYear, tkMonth, tkDay, tkHour, tkMin, tkSec);
|
|
const
|
|
{ 1234567890123456789
|
|
Format = yyyy-MM-ddTHH:mm:ss }
|
|
SepPos: array [TimeKind] of Byte = (5, 8, 11, 14, 17, 20);
|
|
var
|
|
//S: AnsiString;
|
|
S: String;
|
|
TimeArray: array [TimeKind] of Word;
|
|
BusyWith: TimeKind;
|
|
I: Byte;
|
|
begin
|
|
{ Max. 19 chars }
|
|
// S := UTF16ToAnsiString(Copy(ANewText, 1, 19));
|
|
S := Copy(ANewText, 1, 19);
|
|
|
|
FillChar(TimeArray{%H-}, SizeOf(TimeArray), #0);
|
|
TimeArray[tkMonth] := 1;
|
|
TimeArray[tkDay] := 1;
|
|
|
|
I := 1;
|
|
BusyWith := tkYear;
|
|
while I <= Length(S) do
|
|
begin
|
|
{ Use Timearray [Sec] as temp variable }
|
|
|
|
if I = SepPos[BusyWith] then
|
|
begin
|
|
TimeArray[BusyWith] := TimeArray[tkSec];
|
|
TimeArray[tkSec] := 0;
|
|
Inc(BusyWith);
|
|
end
|
|
else
|
|
if CharInSet(S[I], DigitSymbols) then
|
|
TimeArray[tkSec] := TimeArray[tkSec] * 10 + Ord(S[I]) - Ord('0')
|
|
else
|
|
Break;
|
|
|
|
Inc(I);
|
|
end;
|
|
|
|
if I = SepPos[BusyWith] then
|
|
begin
|
|
TimeArray[BusyWith] := TimeArray[tkSec];
|
|
TimeArray[tkSec] := 0;
|
|
//Inc(BusyWith);
|
|
end;
|
|
|
|
try
|
|
FValue := EncodeDate(TimeArray[tkYear], TimeArray[tkMonth], TimeArray[tkDay]);
|
|
if I > 11 then
|
|
FValue := FValue + EncodeTime(TimeArray[tkHour], TimeArray[tkMin], TimeArray[tkSec], 0)
|
|
except
|
|
on EConvertError do
|
|
FValue := 0;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3TimestampFrame.SetValue(const AValue: TDateTime);
|
|
begin
|
|
if AValue <> FValue then
|
|
begin
|
|
FValue := AValue;
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
|
|
//=== { TJvID3URLFrame } =====================================================
|
|
|
|
procedure TJvID3URLFrame.Assign(Source: TPersistent);
|
|
begin
|
|
if Source is TJvID3URLFrame then
|
|
FURL := TJvID3URLFrame(Source).URL;
|
|
|
|
inherited Assign(Source);
|
|
end;
|
|
|
|
class function TJvID3URLFrame.CanAddFrame(AController: TJvID3Controller;
|
|
AFrameID: TJvID3FrameID): Boolean;
|
|
begin
|
|
{ There may only be one URL link frame of its kind in an tag, except for
|
|
"WCOM", but not with the same content.
|
|
"WOAR", but not with the same content. }
|
|
case AFrameID of
|
|
fiWWWCommercialInfo, fiWWWArtist:
|
|
Result := True;
|
|
fiWWWCopyright, fiWWWAudioFile, fiWWWAudioSource, fiWWWRadioPage, fiWWWPayment, fiWWWPublisher:
|
|
Result := not AController.HasFrame(AFrameID);
|
|
else
|
|
Result := inherited CanAddFrame(AController, AFrameID);
|
|
end;
|
|
end;
|
|
|
|
function TJvID3URLFrame.CheckFrame(const HandleError: TJvID3HandleError): Boolean;
|
|
begin
|
|
Result := CheckIsURL(Self, FURL, HandleError);
|
|
|
|
{ If something has changed update the framesize }
|
|
if not Result and (HandleError = heAutoCorrect) then
|
|
begin
|
|
UpdateFrameSize;
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3URLFrame.Clear;
|
|
begin
|
|
FURL := '';
|
|
inherited Clear;
|
|
end;
|
|
|
|
class function TJvID3URLFrame.Find(AController: TJvID3Controller;
|
|
const AFrameID: TJvID3FrameID): TJvID3URLFrame;
|
|
var
|
|
Frame: TJvID3Frame;
|
|
begin
|
|
Result := nil;
|
|
if not Assigned(AController) or not AController.Active then
|
|
Exit;
|
|
|
|
Frame := AController.Frames.FindFrame(AFrameID);
|
|
if Frame is TJvID3URLFrame then
|
|
Result := TJvID3URLFrame(Frame)
|
|
end;
|
|
|
|
class function TJvID3URLFrame.FindOrCreate(AController: TJvID3Controller;
|
|
const AFrameID: TJvID3FrameID): TJvID3URLFrame;
|
|
begin
|
|
if not Assigned(AController) then
|
|
ID3Error(RsEID3NoController);
|
|
|
|
Result := Find(AController, AFrameID);
|
|
if not Assigned(Result) then
|
|
begin
|
|
AController.CheckFrameClass(TJvID3URLFrame, AFrameID);
|
|
Result := TJvID3URLFrame(AController.AddFrame(AFrameID));
|
|
end;
|
|
end;
|
|
|
|
function TJvID3URLFrame.GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal;
|
|
begin
|
|
Result := Length(URL);
|
|
end;
|
|
|
|
function TJvID3URLFrame.GetIsEmpty: Boolean;
|
|
begin
|
|
Result := Length(URL) = 0;
|
|
end;
|
|
|
|
procedure TJvID3URLFrame.ReadFrame;
|
|
begin
|
|
with Stream do
|
|
ReadStringA(FURL);
|
|
end;
|
|
|
|
function TJvID3URLFrame.SameUniqueIDAs(const Frame: TJvID3Frame): Boolean;
|
|
begin
|
|
{ There may only be one URL link frame of its kind in an tag, except for
|
|
"WCOM", but not with the same content.
|
|
"WOAR", but not with the same content. }
|
|
Result := (Frame is TJvID3URLFrame) and (Frame.FrameID = FrameID);
|
|
|
|
if Result then
|
|
Result :=
|
|
not (FrameID in [fiWWWCommercialInfo, fiWWWArtist]) or
|
|
AnsiSameStr(URL, TJvID3URLFrame(Frame).URL)
|
|
else
|
|
Result := inherited SameUniqueIDAs(Frame);
|
|
end;
|
|
|
|
procedure TJvID3URLFrame.SetURL(const Value: AnsiString);
|
|
begin
|
|
if FURL <> Value then
|
|
begin
|
|
FURL := Value;
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3URLFrame.WriteFrame;
|
|
begin
|
|
with Stream do
|
|
WriteStringA(URL);
|
|
end;
|
|
|
|
|
|
//=== { TJvID3URLUserFrame } =================================================
|
|
|
|
procedure TJvID3URLUserFrame.Assign(Source: TPersistent);
|
|
begin
|
|
if Source is TJvID3URLUserFrame then
|
|
begin
|
|
FDescription := TJvID3URLUserFrame(Source).Description;
|
|
FURL := TJvID3URLUserFrame(Source).URL;
|
|
end;
|
|
|
|
inherited Assign(Source);
|
|
end;
|
|
|
|
class function TJvID3URLUserFrame.CanAddFrame(AController: TJvID3Controller;
|
|
AFrameID: TJvID3FrameID): Boolean;
|
|
begin
|
|
{ There may be more than one "WXXX" frame in each tag, but only one
|
|
with the same description. }
|
|
Result := (AFrameID = fiWWWUser) or inherited CanAddFrame(AController, AFrameID);
|
|
end;
|
|
|
|
function TJvID3URLUserFrame.CheckFrame(const HandleError: TJvID3HandleError): Boolean;
|
|
begin
|
|
Result := CheckIsURL(Self, FURL, HandleError);
|
|
|
|
{ If something has changed update the framesize }
|
|
if not Result and (HandleError = heAutoCorrect) then
|
|
begin
|
|
UpdateFrameSize;
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3URLUserFrame.Clear;
|
|
begin
|
|
FDescription := '';
|
|
FURL := '';
|
|
inherited Clear;
|
|
end;
|
|
|
|
class function TJvID3URLUserFrame.Find(AController: TJvID3Controller;
|
|
const AIndex: Integer): TJvID3URLUserFrame;
|
|
var
|
|
FoundIndex: Integer;
|
|
Frame: TJvID3Frame = nil;
|
|
begin
|
|
Result := nil;
|
|
if not Assigned(AController) or not AController.Active then
|
|
Exit;
|
|
|
|
if not AController.FindFirstFrame(fiWWWUser, Frame) then
|
|
Exit;
|
|
|
|
FoundIndex := 0;
|
|
|
|
while Assigned(Frame) and (FoundIndex < AIndex) do
|
|
begin
|
|
AController.FindNextFrame(fiWWWUser, Frame);
|
|
Inc(FoundIndex);
|
|
end;
|
|
|
|
if Frame is TJvID3URLUserFrame then
|
|
Result := TJvID3URLUserFrame(Frame);
|
|
end;
|
|
|
|
class function TJvID3URLUserFrame.FindOrCreate(AController: TJvID3Controller;
|
|
const AIndex: Integer): TJvID3URLUserFrame;
|
|
begin
|
|
if not Assigned(AController) then
|
|
ID3Error(RsEID3NoController);
|
|
|
|
Result := Find(AController, AIndex);
|
|
if not Assigned(Result) then
|
|
Result := TJvID3URLUserFrame(AController.AddFrame(fiWWWUser));
|
|
end;
|
|
|
|
function TJvID3URLUserFrame.GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal;
|
|
begin
|
|
{ Text encoding $xx
|
|
Description <text string according to encoding> $00 (00)
|
|
Value <text string according to encoding>
|
|
}
|
|
Result := 1 +
|
|
LengthEnc(Description, ToEncoding) +
|
|
LengthTerminatorEnc(ToEncoding) +
|
|
Cardinal(Length(FURL));
|
|
end;
|
|
|
|
function TJvID3URLUserFrame.GetIsEmpty: Boolean;
|
|
begin
|
|
Result := (FURL = '') and (Description = '');
|
|
end;
|
|
|
|
function TJvID3URLUserFrame.MustWriteAsUTF: Boolean;
|
|
begin
|
|
Result := HasNonISO_8859_1Chars(Description);
|
|
end;
|
|
|
|
procedure TJvID3URLUserFrame.ReadFrame;
|
|
begin
|
|
with Stream do
|
|
begin
|
|
ReadEncoding;
|
|
ReadStringEnc(FDescription);
|
|
ReadStringA(FURL);
|
|
end;
|
|
end;
|
|
|
|
//procedure TJvID3URLUserFrame.SetDescription(const Value: WideString);
|
|
procedure TJvID3URLUserFrame.SetDescription(const Value: String);
|
|
begin
|
|
if Value <> FDescription then
|
|
begin
|
|
FDescription := Value;
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3URLUserFrame.SetURL(const Value: AnsiString);
|
|
begin
|
|
if FURL <> Value then
|
|
begin
|
|
FURL := Value;
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3URLUserFrame.WriteFrame;
|
|
begin
|
|
with Stream do
|
|
begin
|
|
WriteEncoding;
|
|
WriteStringEnc(Description);
|
|
WriteTerminatorEnc;
|
|
WriteStringA(URL);
|
|
end;
|
|
end;
|
|
|
|
|
|
//=== { TJvID3UserFrame } ====================================================
|
|
|
|
procedure TJvID3UserFrame.Assign(Source: TPersistent);
|
|
begin
|
|
if Source is TJvID3CustomTextFrame then
|
|
begin
|
|
FValue := TJvID3UserFrame(Source).Value;
|
|
FDescription := TJvID3UserFrame(Source).Description;
|
|
end;
|
|
inherited Assign(Source);
|
|
end;
|
|
|
|
class function TJvID3UserFrame.CanAddFrame(AController: TJvID3Controller;
|
|
AFrameID: TJvID3FrameID): Boolean;
|
|
begin
|
|
{ There may be more than one "TXXX" frame in each tag, but only one
|
|
with the same description. }
|
|
Result := (AFrameID = fiUserText) or
|
|
inherited CanAddFrame(AController, AFrameID);
|
|
end;
|
|
|
|
function TJvID3UserFrame.CheckFrame(const HandleError: TJvID3HandleError): Boolean;
|
|
begin
|
|
Result := True;
|
|
end;
|
|
|
|
procedure TJvID3UserFrame.Clear;
|
|
begin
|
|
FValue := '';
|
|
FDescription := '';
|
|
inherited Clear;
|
|
end;
|
|
|
|
class function TJvID3UserFrame.Find(AController: TJvID3Controller;
|
|
const AIndex: Integer): TJvID3UserFrame;
|
|
var
|
|
FoundIndex: Integer;
|
|
Frame: TJvID3Frame = nil;
|
|
begin
|
|
Result := nil;
|
|
|
|
if not Assigned(AController) or not AController.Active then
|
|
Exit;
|
|
|
|
if not AController.FindFirstFrame(fiUserText, Frame) then
|
|
Exit;
|
|
|
|
FoundIndex := 0;
|
|
|
|
while Assigned(Frame) and (FoundIndex < AIndex) do
|
|
begin
|
|
AController.FindNextFrame(fiUserText, Frame);
|
|
Inc(FoundIndex);
|
|
end;
|
|
|
|
if Frame is TJvID3UserFrame then
|
|
Result := TJvID3UserFrame(Frame);
|
|
end;
|
|
|
|
class function TJvID3UserFrame.FindOrCreate(AController: TJvID3Controller;
|
|
const AIndex: Integer): TJvID3UserFrame;
|
|
begin
|
|
if not Assigned(AController) then
|
|
ID3Error(RsEID3NoController);
|
|
|
|
Result := Find(AController, AIndex);
|
|
if not Assigned(Result) then
|
|
Result := TJvID3UserFrame(AController.AddFrame(fiUserText));
|
|
end;
|
|
|
|
function TJvID3UserFrame.GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal;
|
|
begin
|
|
{ Text encoding $xx
|
|
Description <text string according to encoding> $00 (00)
|
|
Value <text string according to encoding> }
|
|
Result := 1 +
|
|
LengthEnc(Description, ToEncoding) +
|
|
LengthTerminatorEnc(ToEncoding) +
|
|
LengthEnc(Value, ToEncoding);
|
|
end;
|
|
|
|
function TJvID3UserFrame.GetIsEmpty: Boolean;
|
|
begin
|
|
Result := (Value = '') and (Description = '');
|
|
end;
|
|
|
|
function TJvID3UserFrame.MustWriteAsUTF: Boolean;
|
|
begin
|
|
Result := HasNonISO_8859_1Chars(Value) or HasNonISO_8859_1Chars(Description)
|
|
end;
|
|
|
|
procedure TJvID3UserFrame.ReadFrame;
|
|
begin
|
|
with Stream do
|
|
begin
|
|
ReadEncoding;
|
|
ReadUserString(FDescription, FValue);
|
|
end;
|
|
end;
|
|
|
|
//procedure TJvID3UserFrame.SetDescription(const AValue: WideString);
|
|
procedure TJvID3UserFrame.SetDescription(const AValue: String);
|
|
begin
|
|
if AValue <> FDescription then
|
|
begin
|
|
FDescription := AValue;
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
//procedure TJvID3UserFrame.SetValue(const AValue: WideString);
|
|
procedure TJvID3UserFrame.SetValue(const AValue: String);
|
|
begin
|
|
if AValue <> FValue then
|
|
begin
|
|
FValue := AValue;
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvID3UserFrame.WriteFrame;
|
|
begin
|
|
with Stream do
|
|
begin
|
|
WriteEncoding;
|
|
WriteUserString(Description, Value);
|
|
end;
|
|
end;
|
|
|
|
|
|
end.
|