diff --git a/components/jvcllaz/design/JvMM/images/images.txt b/components/jvcllaz/design/JvMM/images/images.txt
index b5d93c1d1..67afb443b 100644
--- a/components/jvcllaz/design/JvMM/images/images.txt
+++ b/components/jvcllaz/design/JvMM/images/images.txt
@@ -1,4 +1,5 @@
tjvid3v1.bmp
+tjvid3v2.bmp
tjvgradient.bmp
tjvgradientheaderpanel.bmp
tjvspecialprogress.bmp
diff --git a/components/jvcllaz/design/JvMM/images/tjvid3v2.bmp b/components/jvcllaz/design/JvMM/images/tjvid3v2.bmp
new file mode 100644
index 000000000..55323a964
Binary files /dev/null and b/components/jvcllaz/design/JvMM/images/tjvid3v2.bmp differ
diff --git a/components/jvcllaz/design/JvMM/jvmmreg.pas b/components/jvcllaz/design/JvMM/jvmmreg.pas
index 1f41eea2f..b4ec8e98f 100644
--- a/components/jvcllaz/design/JvMM/jvmmreg.pas
+++ b/components/jvcllaz/design/JvMM/jvmmreg.pas
@@ -16,12 +16,12 @@ implementation
uses
Classes, JvDsgnConsts,
PropEdits, Controls,
- JvId3v1, JvGradient, JvGradientHeaderPanel, JvSpecialProgress;
+ JvId3v1, JvId3v2, JvGradient, JvGradientHeaderPanel, JvSpecialProgress;
procedure Register;
begin
RegisterComponents(RsPaletteJvcl, [
- TJvId3v1,
+ TJvId3v1, TJvId3v2,
TJvGradient, TJvGradientHeaderPanel,
TJvSpecialProgress
]);
diff --git a/components/jvcllaz/examples/JvID3v1/JvID3v1Demo.lpi b/components/jvcllaz/examples/JvID3v1/JvID3v1Demo.lpi
new file mode 100644
index 000000000..311e71979
--- /dev/null
+++ b/components/jvcllaz/examples/JvID3v1/JvID3v1Demo.lpi
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/jvcllaz/examples/JvID3v1/JvID3v1Demo.lpr b/components/jvcllaz/examples/JvID3v1/JvID3v1Demo.lpr
new file mode 100644
index 000000000..942ecf338
--- /dev/null
+++ b/components/jvcllaz/examples/JvID3v1/JvID3v1Demo.lpr
@@ -0,0 +1,16 @@
+program JvID3v1Demo;
+
+{$mode objfpc}{$H+}
+
+uses
+ Interfaces,
+ Forms,
+ JvID3v1MainFormU in 'JvID3v1MainFormU.pas' {JvID3v1MainForm};
+
+{$R *.res}
+
+begin
+ Application.Initialize;
+ Application.CreateForm(TJvID3v1MainForm, JvID3v1MainForm);
+ Application.Run;
+end.
diff --git a/components/jvcllaz/examples/JvID3v1/JvID3v1MainFormU.lfm b/components/jvcllaz/examples/JvID3v1/JvID3v1MainFormU.lfm
new file mode 100644
index 000000000..ec65eff10
--- /dev/null
+++ b/components/jvcllaz/examples/JvID3v1/JvID3v1MainFormU.lfm
@@ -0,0 +1,691 @@
+object JvID3v1MainForm: TJvID3v1MainForm
+ Left = 405
+ Height = 237
+ Top = 240
+ Width = 316
+ AllowDropFiles = True
+ AutoSize = True
+ BorderIcons = [biSystemMenu, biMinimize]
+ BorderStyle = bsSingle
+ Caption = 'TJvId3v1 example'
+ ClientHeight = 237
+ ClientWidth = 316
+ Color = clBtnFace
+ DefaultMonitor = dmDesktop
+ Font.Color = clWindowText
+ FormStyle = fsStayOnTop
+ OnCreate = FormCreate
+ OnDropFiles = FormDropFiles
+ Position = poScreenCenter
+ ShowHint = True
+ LCLVersion = '1.9.0.0'
+ Scaled = False
+ object lblArtist: TLabel
+ AnchorSideTop.Control = edtArtist
+ AnchorSideTop.Side = asrCenter
+ AnchorSideRight.Control = lblComment
+ AnchorSideRight.Side = asrBottom
+ Left = 30
+ Height = 15
+ Top = 131
+ Width = 28
+ Anchors = [akTop, akRight]
+ Caption = '&Artist'
+ FocusControl = edtArtist
+ ParentColor = False
+ end
+ object lblAlbum: TLabel
+ AnchorSideTop.Control = edtAlbum
+ AnchorSideTop.Side = asrCenter
+ AnchorSideRight.Control = lblComment
+ AnchorSideRight.Side = asrBottom
+ Left = 22
+ Height = 15
+ Top = 158
+ Width = 36
+ Anchors = [akTop, akRight]
+ Caption = 'Al&bum'
+ FocusControl = edtAlbum
+ ParentColor = False
+ end
+ object lblYear: TLabel
+ AnchorSideLeft.Side = asrBottom
+ AnchorSideTop.Control = edtYear
+ AnchorSideTop.Side = asrCenter
+ AnchorSideRight.Control = lblComment
+ AnchorSideRight.Side = asrBottom
+ Left = 36
+ Height = 15
+ Top = 185
+ Width = 22
+ Anchors = [akTop, akRight]
+ Caption = '&Year'
+ FocusControl = edtYear
+ ParentColor = False
+ end
+ object lblComment: TLabel
+ AnchorSideLeft.Control = Owner
+ AnchorSideTop.Control = edtComment
+ AnchorSideTop.Side = asrCenter
+ Left = 4
+ Height = 15
+ Top = 212
+ Width = 54
+ BorderSpacing.Left = 4
+ Caption = '&Comment'
+ FocusControl = edtComment
+ ParentColor = False
+ end
+ object lblGenre: TLabel
+ AnchorSideLeft.Control = edtYear
+ AnchorSideLeft.Side = asrBottom
+ AnchorSideTop.Control = lblYear
+ AnchorSideRight.Control = cmbGenre
+ Left = 132
+ Height = 15
+ Top = 185
+ Width = 51
+ Alignment = taRightJustify
+ Anchors = [akTop, akLeft, akRight]
+ BorderSpacing.Left = 12
+ BorderSpacing.Right = 4
+ Caption = '&Genre'
+ FocusControl = cmbGenre
+ ParentColor = False
+ end
+ object lblHasTag: TLabel
+ AnchorSideLeft.Control = JvFilenameEdit1
+ AnchorSideTop.Control = lblTrack
+ AnchorSideRight.Control = lblTitle
+ AnchorSideRight.Side = asrBottom
+ Left = 6
+ Height = 15
+ Top = 77
+ Width = 52
+ Anchors = [akTop, akRight]
+ Caption = 'lblHasTag'
+ ParentColor = False
+ end
+ object lblTitle: TLabel
+ AnchorSideTop.Control = edtTitle
+ AnchorSideTop.Side = asrCenter
+ AnchorSideRight.Control = lblComment
+ AnchorSideRight.Side = asrBottom
+ Left = 35
+ Height = 15
+ Top = 104
+ Width = 23
+ Anchors = [akTop, akRight]
+ Caption = '&Title'
+ FocusControl = edtTitle
+ ParentColor = False
+ end
+ object lblTrack: TLabel
+ AnchorSideTop.Control = sedTrack
+ AnchorSideTop.Side = asrCenter
+ AnchorSideRight.Control = sedTrack
+ Left = 221
+ Height = 15
+ Top = 77
+ Width = 38
+ Anchors = [akTop, akRight]
+ BorderSpacing.Right = 4
+ Caption = 'T&rack #'
+ FocusControl = sedTrack
+ ParentColor = False
+ end
+ object JvFilenameEdit1: TFileNameEdit
+ AnchorSideLeft.Control = Owner
+ AnchorSideTop.Control = ToolBar1
+ AnchorSideTop.Side = asrBottom
+ AnchorSideRight.Control = Owner
+ AnchorSideRight.Side = asrBottom
+ Left = 4
+ Height = 23
+ Top = 46
+ Width = 308
+ OnAcceptFileName = JvFilenameEdit1AcceptFileName
+ FilterIndex = 0
+ HideDirectories = False
+ ButtonWidth = 23
+ NumGlyphs = 1
+ Anchors = [akTop, akLeft, akRight]
+ BorderSpacing.Left = 4
+ BorderSpacing.Top = 4
+ BorderSpacing.Right = 4
+ MaxLength = 0
+ ParentShowHint = False
+ ShowHint = True
+ TabOrder = 0
+ OnKeyPress = JvFilenameEdit1KeyPress
+ end
+ object edtTitle: TEdit
+ AnchorSideLeft.Control = lblTitle
+ AnchorSideLeft.Side = asrBottom
+ AnchorSideTop.Control = sedTrack
+ AnchorSideTop.Side = asrBottom
+ AnchorSideRight.Control = JvFilenameEdit1
+ AnchorSideRight.Side = asrBottom
+ Left = 62
+ Height = 23
+ Top = 100
+ Width = 250
+ Anchors = [akTop, akLeft, akRight]
+ BorderSpacing.Left = 4
+ BorderSpacing.Top = 4
+ MaxLength = 30
+ TabOrder = 2
+ end
+ object edtAlbum: TEdit
+ AnchorSideLeft.Control = lblAlbum
+ AnchorSideLeft.Side = asrBottom
+ AnchorSideTop.Control = edtArtist
+ AnchorSideTop.Side = asrBottom
+ AnchorSideRight.Control = JvFilenameEdit1
+ AnchorSideRight.Side = asrBottom
+ Left = 62
+ Height = 23
+ Top = 154
+ Width = 250
+ Anchors = [akTop, akLeft, akRight]
+ BorderSpacing.Left = 4
+ BorderSpacing.Top = 4
+ MaxLength = 30
+ TabOrder = 4
+ end
+ object edtArtist: TEdit
+ AnchorSideLeft.Control = lblArtist
+ AnchorSideLeft.Side = asrBottom
+ AnchorSideTop.Control = edtTitle
+ AnchorSideTop.Side = asrBottom
+ AnchorSideRight.Control = JvFilenameEdit1
+ AnchorSideRight.Side = asrBottom
+ Left = 62
+ Height = 23
+ Top = 127
+ Width = 250
+ Anchors = [akTop, akLeft, akRight]
+ BorderSpacing.Left = 4
+ BorderSpacing.Top = 4
+ MaxLength = 30
+ TabOrder = 3
+ end
+ object edtYear: TEdit
+ AnchorSideLeft.Control = lblYear
+ AnchorSideLeft.Side = asrBottom
+ AnchorSideTop.Control = edtAlbum
+ AnchorSideTop.Side = asrBottom
+ Left = 62
+ Height = 23
+ Top = 181
+ Width = 58
+ BorderSpacing.Left = 4
+ BorderSpacing.Top = 4
+ MaxLength = 4
+ TabOrder = 5
+ end
+ object edtComment: TEdit
+ AnchorSideLeft.Control = lblComment
+ AnchorSideLeft.Side = asrBottom
+ AnchorSideTop.Control = edtYear
+ AnchorSideTop.Side = asrBottom
+ AnchorSideRight.Control = JvFilenameEdit1
+ AnchorSideRight.Side = asrBottom
+ Left = 62
+ Height = 23
+ Top = 208
+ Width = 250
+ Anchors = [akTop, akLeft, akRight]
+ BorderSpacing.Left = 4
+ BorderSpacing.Top = 4
+ BorderSpacing.Bottom = 4
+ MaxLength = 30
+ TabOrder = 7
+ end
+ object cmbGenre: TComboBox
+ AnchorSideTop.Control = edtAlbum
+ AnchorSideTop.Side = asrBottom
+ AnchorSideRight.Control = JvFilenameEdit1
+ AnchorSideRight.Side = asrBottom
+ Left = 187
+ Height = 23
+ Top = 181
+ Width = 125
+ Anchors = [akTop, akRight]
+ BorderSpacing.Top = 4
+ ItemHeight = 15
+ Sorted = True
+ Style = csDropDownList
+ TabOrder = 6
+ end
+ object ToolBar1: TToolBar
+ Left = 0
+ Height = 42
+ Top = 0
+ Width = 316
+ AutoSize = True
+ ButtonHeight = 40
+ ButtonWidth = 44
+ Caption = 'ToolBar1'
+ EdgeBorders = [ebBottom]
+ Images = ImageList1
+ ParentShowHint = False
+ ShowCaptions = True
+ ShowHint = True
+ TabOrder = 8
+ Wrapable = False
+ object ToolButton1: TToolButton
+ Left = 1
+ Hint = 'Reload the tag data from the file'
+ Top = 0
+ Action = actRefresh
+ ParentShowHint = False
+ ShowHint = True
+ end
+ object ToolButton2: TToolButton
+ Left = 46
+ Hint = 'Save changes of the tag to the file'
+ Top = 0
+ Action = actSave
+ ParentShowHint = False
+ ShowHint = True
+ end
+ object ToolButton3: TToolButton
+ Left = 90
+ Hint = 'Erase the tag of the file'
+ Top = 0
+ Action = actErase
+ ParentShowHint = False
+ ShowHint = True
+ end
+ object ToolButton4: TToolButton
+ Left = 134
+ Hint = 'Toggle ''Always on Top'''
+ Top = 0
+ Action = actOnTop
+ ParentShowHint = False
+ ShowHint = True
+ end
+ object ToolButton5: TToolButton
+ Left = 178
+ Hint = 'Show ''About'' dialog'
+ Top = 0
+ Action = actAbout
+ ParentShowHint = False
+ ShowHint = True
+ end
+ object ToolButton6: TToolButton
+ Left = 222
+ Hint = 'Close program'
+ Top = 0
+ Action = actExit
+ ParentShowHint = False
+ ShowHint = True
+ end
+ end
+ object sedTrack: TSpinEdit
+ AnchorSideTop.Control = JvFilenameEdit1
+ AnchorSideTop.Side = asrBottom
+ AnchorSideRight.Control = JvFilenameEdit1
+ AnchorSideRight.Side = asrBottom
+ Left = 263
+ Height = 23
+ Top = 73
+ Width = 49
+ Alignment = taRightJustify
+ Anchors = [akTop, akRight]
+ BorderSpacing.Top = 4
+ MaxValue = 255
+ TabOrder = 1
+ end
+ object JvId3v11: TJvID3v1
+ Active = False
+ left = 184
+ top = 112
+ end
+ object ActionList1: TActionList
+ Images = ImageList1
+ left = 104
+ top = 112
+ object actSave: TAction
+ Caption = 'Save'
+ ImageIndex = 0
+ OnExecute = actSaveExecute
+ end
+ object actRefresh: TAction
+ Caption = 'Refresh'
+ ImageIndex = 2
+ OnExecute = actRefreshExecute
+ end
+ object actErase: TAction
+ Caption = 'Erase'
+ ImageIndex = 1
+ OnExecute = actEraseExecute
+ end
+ object actExit: TAction
+ Caption = 'Exit'
+ ImageIndex = 3
+ OnExecute = actExitExecute
+ end
+ object actOnTop: TAction
+ Caption = 'On Top'
+ Checked = True
+ ImageIndex = 5
+ OnExecute = actOnTopExecute
+ end
+ object actAbout: TAction
+ Caption = 'About'
+ ImageIndex = 4
+ OnExecute = actAboutExecute
+ end
+ end
+ object ImageList1: TImageList
+ Height = 20
+ Width = 20
+ left = 72
+ top = 32
+ Bitmap = {
+ 4C69060000001400000014000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000FF0000
+ 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+ 00FF000000FF000000FF000000FF000000000000000000000000000000000000
+ 000000000000000000FF52B5F7FF52B5F7FF000000FFFFFFFFFFFFFFFFFFFFFF
+ FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000FF52B5F7FF52B5F7FF0000
+ 00FFD6A58CFF00000000000000000000000000000000000000FF52B5F7FF009C
+ FFFF000000FFDEBDD6FFDEBDD6FFDEBDD6FFDEBDD6FFDEBDD6FFDEBDD6FFDEBD
+ D6FF000000FF009CFFFF00639CFF000000FFC6948CFF00000000000000000000
+ 000000000000000000FF52B5F7FF009CFFFF000000FFDEBDD6FFDEBDD6FFDEBD
+ D6FFDEBDD6FFDEBDD6FFDEBDD6FFDEBDD6FF000000FF009CFFFF00639CFF0000
+ 00FFC6948CFF00000000000000000000000000000000000000FF52B5F7FF009C
+ FFFF000000FFDEBDD6FFDEBDD6FFDEBDD6FFDEBDD6FFDEBDD6FFDEBDD6FFDEBD
+ D6FF000000FF009CFFFF00639CFF000000FFC6948CFF00000000000000000000
+ 000000000000000000FF52B5F7FF009CFFFF000000FFDEBDD6FFDEBDD6FFDEBD
+ D6FFDEBDD6FFDEBDD6FFDEBDD6FFDEBDD6FF000000FF009CFFFF00639CFF0000
+ 00FFC6948CFF00000000000000000000000000000000000000FF52B5F7FF009C
+ FFFF000000FFDEBDD6FFDEBDD6FFDEBDD6FFDEBDD6FFDEBDD6FFDEBDD6FFDEBD
+ D6FF000000FF009CFFFF00639CFF000000FFC6948CFF00000000000000000000
+ 000000000000000000FF52B5F7FF009CFFFF000000FFDEBDD6FFDEBDD6FFDEBD
+ D6FFDEBDD6FFDEBDD6FFDEBDD6FFDEBDD6FF000000FF009CFFFF00639CFF0000
+ 00FFC6948CFF00000000000000000000000000000000000000FF52B5F7FF009C
+ FFFF009CFFFF000000FF000000FF000000FF000000FF000000FF000000FF0000
+ 00FF52B5F7FF009CFFFF00639CFF000000FFC6948CFF00000000000000000000
+ 000000000000000000FF52B5F7FF009CFFFF009CFFFF009CFFFF009CFFFF009C
+ FFFF009CFFFF009CFFFF009CFFFF009CFFFF009CFFFF009CFFFF00639CFF0000
+ 00FFC6948CFF00000000000000000000000000000000000000FF52B5F7FF009C
+ FFFF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+ 00FF000000FF009CFFFF00639CFF000000FFC6948CFF00000000000000000000
+ 000000000000000000FF52B5F7FF009CFFFF000000FFDEBDD6FFDEBDD6FFFFFF
+ FFFFDEBDD6FFDEBDD6FFBDBDBDFFBDBDBDFF000000FF009CFFFF00639CFF0000
+ 00FFC6948CFF00000000000000000000000000000000000000FF52B5F7FF009C
+ FFFF000000FFDEBDD6FFDEBDD6FFFFFFFFFFDEBDD6FFDEBDD6FFBDBDBDFFBDBD
+ BDFF000000FF009CFFFF00639CFF000000FFC6948CFF00000000000000000000
+ 00000000000000000000000000FF00639CFF000000FFDEBDD6FFDEBDD6FFFFFF
+ FFFFDEBDD6FFDEBDD6FFBDBDBDFFBDBDBDFF000000FF00639CFF00639CFF0000
+ 00FFC6948CFF0000000000000000000000000000000000000000000000000000
+ 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+ 00FF000000FF000000FF000000FFC6948CFF0000000000000000000000000000
+ 0000000000000000000000000000D6A58CFFC6948CFFC6948CFFC6948CFFC694
+ 8CFFC6948CFFC6948CFFC6948CFFC6948CFFC6948CFFC6948CFFC6948CFF0000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 00000000000000000000000000FF000000FF000000FF000000FF000000FF0000
+ 00FF000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000FF000000FF000000FF000000FFBDBD
+ BDFFFFFFFFFFBDBDBDFFBDBDBDFF000000FF000000FF000000FF000000FF0000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 00FFBDBDBDFFFFFFFFFFFFFFFFFFBDBDBDFFBDBDBDFFC6948CFFC6948CFF9C8C
+ 6BFF9C8C6BFF9C8C6BFF000000FF9C8C6BFF0000000000000000000000000000
+ 0000000000000000000000000000000000FF000000FF000000FF000000FF0000
+ 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF9C8C
+ 6BFF000000000000000000000000000000000000000000000000000000000000
+ 0000000000FF9C8C6BFF9C8C6BFF9C8C6BFF9C8C6BFF9C8C6BFF9C8C6BFF9C8C
+ 6BFF9C8C6BFF000000FF9C8C6BFF9C8C6BFF0000000000000000000000000000
+ 000000000000000000000000000000000000000000FFBDBDBDFFFFFFFFFFC694
+ 8CFFFFEFDEFF9C8C6BFFC6948CFF424242FF9C8C6BFF000000FF9C8C6BFF9C8C
+ 6BFF000000000000000000000000000000000000000000000000000000000000
+ 0000000000FFBDBDBDFFFFFFFFFFBDBDBDFFFFEFDEFFC6948CFFBDBDBDFF6363
+ 63FFD6A58CFF000000FF9C8C6BFF9C8C6BFF0000000000000000000000000000
+ 000000000000000000000000000000000000000000FFBDBDBDFFFFFFFFFFBDBD
+ BDFFFFEFDEFFC6948CFFBDBDBDFF636363FFD6A58CFF000000FF9C8C6BFF9C8C
+ 6BFF000000000000000000000000000000000000000000000000000000000000
+ 0000000000FFBDBDBDFFFFFFFFFFBDBDBDFFFFEFDEFFC6948CFFBDBDBDFF6363
+ 63FFD6A58CFF000000FF9C8C6BFF9C8C6BFF0000000000000000000000000000
+ 000000000000000000000000000000000000000000FFBDBDBDFFFFFFFFFFBDBD
+ BDFFFFEFDEFFC6948CFFBDBDBDFF636363FFD6A58CFF000000FF9C8C6BFF9C8C
+ 6BFF000000000000000000000000000000000000000000000000000000000000
+ 0000000000FFBDBDBDFFFFFFFFFFBDBDBDFFFFEFDEFFC6948CFFBDBDBDFF6363
+ 63FFD6A58CFF000000FF9C8C6BFF9C8C6BFF0000000000000000000000000000
+ 000000000000000000000000000000000000000000FFBDBDBDFFFFFFFFFFFFEF
+ DEFFBDBDBDFFBDBDBDFFC6948CFFC6948CFF636363FF000000FF9C8C6BFF9C8C
+ 6BFF000000000000000000000000000000000000000000000000000000000000
+ 0000000000FFBDBDBDFFFFFFFFFFBDBDBDFFBDBDBDFFC6948CFFC6948CFF6363
+ 63FF636363FF000000FF9C8C6BFF9C8C6BFF0000000000000000000000000000
+ 000000000000000000000000000000000000000000FF000000FF000000FF0000
+ 00FF000000FF000000FF000000FF000000FF000000FF000000FF9C8C6BFF9C8C
+ 6BFF000000000000000000000000000000000000000000000000000000000000
+ 0000000000009C8C6BFF9C8C6BFF9C8C6BFF9C8C6BFF9C8C6BFF9C8C6BFF9C8C
+ 6BFF9C8C6BFF9C8C6BFF9C8C6BFF000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 000000000000000000000000000000000000000000009C8C6BFFAD0000FFAD00
+ 00FFAD0000FFAD0000FFAD0000FF000000000000000000000000000000000000
+ 00000000000000000000000000000000000000000000FF2929FF000000000000
+ 00009C8C6BFFFF2929FF940000FF940000FF940000FF8C0000FF9C0000FFFF29
+ 29FFFF2929FFFF2929FF00000000000000000000000000000000000000000000
+ 0000000000007B0000FF00000000FF2929FF9C0000FFA50000FF8C0000FF8400
+ 00FF840000FF840000FF840000FF7B0000FF7B0000FFFF2929FF9C8C6BFF0000
+ 00000000000000000000000000000000000000000000840000FFA50000FFAD00
+ 00FF9C0000FF840000FF840000FF8C0000FF840000FF840000FF8C0000FF8400
+ 00FF840000FF840000FF842910FF9C8C6BFF0000000000000000000000000000
+ 0000000000008C0000FF9C0000FFAD0000FF9C0000FF840808FF9C8C6BFF9C8C
+ 6BFF9C8C6BFF9C8C6BFF9C8C6BFF8C0000FF8C0000FF8C0000FF842908FF9C8C
+ 6BFF00000000000000000000000000000000000000008C0000FF940008FF9C08
+ 08FF940000FF8C1810FF9C8C6BFF000000000000000000000000000000009C8C
+ 6BFF7B1000FF840000FF840000FF942929FF0000000000000000000000000000
+ 0000000000007B0000FF840000FF840000FF840000FF8C0000FFFF2929FF0000
+ 000000000000000000000000000000000000944A42FF8C3121FF942929FF8C29
+ 18FF9C8C6BFF00000000000000000000000000000000940018FF8C0808FF8C08
+ 08FF8C0808FF8C0000FF840000FF000000000000000000000000000000000000
+ 0000000000009C8C6BFF9C8C6BFF9C8C6BFF9C8C6BFF00000000000000000000
+ 000000000000000000009C8C6BFF9C8C6BFF9C8C6BFF9C8C6BFF9C8C6BFF9C8C
+ 6BFF0000000000000000840000FF8C0010FF8C0810FF8C0808FF840808FF8C08
+ 10FF0000000000000000000000000000000000000000A50000FFA50000FFA500
+ 00FFFF2929FF0000000000000000000000000000000000000000000000009C00
+ 00FF9C0000FF940000FF840000FF840000FF9C8C6BFF00000000000000000000
+ 000000000000A50000FF940008FF730000FFA50021FFFF2929FF000000000000
+ 0000000000000000000000000000BD0018FFAD0000FFA50808FF840000FF8408
+ 00FF9C8C6BFF0000000000000000000000000000000000000000A50000FF7B00
+ 00FF8C0000FFA50000FF0000000000000000000000000000000000000000A500
+ 08FF9C0000FF940000FF840000FF8C0800FF9C8C6BFF00000000000000000000
+ 00000000000000000000A50000FF7B0000FF8C0008FF8C0000FFA50000FFAD00
+ 00FFA50000FFAD0000FFA50000FF8C0000FF840000FF8C0000FF8C0000FF8C00
+ 00FF9C8C6BFF00000000000000000000000000000000000000009C8C6BFF8C00
+ 00FF8C0000FF840000FF8C0000FF8C0000FF8C0000FF8C0000FF8C0000FF8400
+ 00FF840000FF9C8C6BFF9C8C6BFF8C0000FF9C8C6BFF00000000000000000000
+ 00000000000000000000000000009C8C6BFF9C8C6BFF8C0000FF840000FF8400
+ 00FF840000FF840000FF840000FF8C0000FF9C8C6BFF9C8C6BFF9C8C6BFF9C8C
+ 6BFF9C8C6BFF0000000000000000000000000000000000000000000000000000
+ 00009C8C6BFF9C8C6BFF8C0000FF8C0000FF8C0000FF8C0000FF8C0000FF9C8C
+ 6BFF000000000000000000000000000000000000000000000000000000000000
+ 000000000000000000000000000000000000000000009C8C6BFF9C8C6BFF9C8C
+ 6BFF9C8C6BFF9C8C6BFF9C8C6BFF000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 00000000000000000000000000FF000000FF000000FF000000FF000000FF0000
+ 00FF000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000FF00FF
+ 00FF00FF00FF00FF00FF00FF00FF000000FF0000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 00000000000000000000000000FF000000FF000000FF000000FF000000FF0000
+ 00FF000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000840000FF840000FF840000FF840000FF840000FF840000FF840000FF8400
+ 00FF840000FF840000FF00000000000000000000000000000000000000000000
+ 000000000000000000000000000000000000840000FFFF00FFFF840084FFFF00
+ FFFF000000FFFFFF00FFFFFFFFFFFFFF00FFFFFFFFFF840000FF000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000840000FF840084FFFF00FFFF840084FF000000FFFFFFFFFFFFFF00FFFFFF
+ FFFFFFFF00FF840000FF00000000000000000000000000000000000000000000
+ 000000000000000000000000000000000000840000FFFF00FFFF840084FFFF00
+ FFFF000000FFFFFF00FFFFFFFFFFFFFF00FFFFFFFFFF840000FF000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000840000FF840084FFFF00FFFF840084FF000000FFFFFFFFFFFFFF00FFFFFF
+ FFFFFFFF00FF840000FF00000000000000000000000000000000000000000000
+ 000000000000000000000000000000000000840000FFFF00FFFF840084FFFF00
+ FFFF000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF840000FF000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000840000FF840084FFFF00FFFF840084FF000000FFFFFFFFFFFFFF00FFFFFF
+ FFFFFFFF00FF840000FF00000000000000000000000000000000000000000000
+ 000000000000000000000000000000000000840000FFFF00FFFF840084FFFF00
+ FFFF000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF840000FF000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000840000FF840084FFFF00FFFF840084FF000000FFFFFFFFFFFFFF00FFFFFF
+ FFFFFFFF00FF840000FF00000000000000000000000000000000000000000000
+ 000000000000000000000000000000000000840000FFFF00FFFF840084FFFF00
+ FFFF000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF840000FF000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000840000FF840084FFFF00FFFF840084FF000000FFFFFFFFFFFFFFFFFFFFFF
+ FFFFFFFFFFFF840000FF00000000000000000000000000000000000000000000
+ 000000000000000000000000000000000000840000FFFF00FFFF840084FF0000
+ 00FF000000FFC6C6C6FFFFFFFFFFFFFFFFFFFFFFFFFF840000FF000000000000
+ 0000000000000000000000000000840000FF840000FF840000FF840000FF8400
+ 00FF840000FF000000FF000000FF848484FF848484FF848484FFFFFFFFFFFFFF
+ FFFFFFFFFFFF840000FF840000FF840000FF840000FF840000FF840000FF8484
+ 84FF848484FF848484FF848484FF848484FF848484FF848484FF848484FF8484
+ 84FF848484FF848484FFC6C6C6FFC6C6C6FFFFFFFFFFC6C6C6FFFFFFFFFFC6C6
+ C6FF848484FF848484FF848484FF848484FF848484FF848484FF848484FF8484
+ 84FF848484FF848484FF848484FF848484FF848484FF848484FF848484FFFFFF
+ FFFFC6C6C6FFFFFFFFFFC6C6C6FFFFFFFFFFC6C6C6FF848484FF848484FF0000
+ 0000C6C6C6FFC6C6C6FFC6C6C6FFC6C6C6FFC6C6C6FFC6C6C6FFC6C6C6FFC6C6
+ C6FFC6C6C6FFC6C6C6FFC6C6C6FFC6C6C6FFC6C6C6FFC6C6C6FFC6C6C6FFC6C6
+ C6FFC6C6C6FFC6C6C6FFC6C6C6FF000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 00000000000000000000000000000000000000000000FFFFFFFFFFFFFFFFBD00
+ 00FFBD0000FFBD0000FFBD0000FFFFFFFFFF0000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000FFFFFFFFBD0000FFBD0000FFBD0000FFBD0000FFBD0000FFBD0000FFBD00
+ 00FFBDBD00FF0000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000FFFFFFFFBD0000FFBD0000FF00BDBDFF00FF
+ FFFF00FFFFFF00FFFFFF00FFFFFFBD0000FFBD0000FFBD0000FF000000000000
+ 0000000000000000000000000000000000000000000000000000FFFFFFFFBD00
+ 00FFBD0000FF00BDBDFF00FFFFFF00BDBDFF00BDBDFF00BDBDFF00FFFFFF00BD
+ BDFFBD0000FFBD0000FF848484FF000000000000000000000000000000000000
+ 00000000000000000000FFFFFFFFBD0000FFBD0000FF00BDBDFF00FFFFFFBD00
+ 00FFBD0000FFBD0000FF00FFFFFF00BDBDFFBD0000FFBD0000FF848484FF0000
+ 00000000000000000000000000000000000000000000FFFFFFFFBD0000FFBD00
+ 00FFBD0000FFBD0000FFBD0000FFBD0000FF00BDBDFF00FFFFFF00FFFFFFBD00
+ 00FFBD0000FFBD0000FFBD0000FF848484FF0000000000000000000000000000
+ 000000000000FFFFFFFFBD0000FFBD0000FFBD0000FFBD0000FFBD0000FF00BD
+ BDFF00FFFFFF00FFFFFF00BDBDFFBD0000FFBD0000FFBD0000FFBD0000FF8484
+ 84FF0000000000000000000000000000000000000000FFFFFFFFBD0000FFBD00
+ 00FFBD0000FFBD0000FFBD0000FF00FFFFFF00FFFFFF00BDBDFFBD0000FFBD00
+ 00FFBD0000FFBD0000FFBD0000FF848484FF0000000000000000000000000000
+ 000000000000FFFFFFFFBD0000FFBD0000FFBD0000FFBD0000FFBD0000FF00BD
+ BDFF00FFFFFF00BDBDFFBD0000FFBD0000FFBD0000FFBD0000FFBD0000FF8484
+ 84FF000000000000000000000000000000000000000000000000FFFFFFFFBD00
+ 00FFBD0000FFBD0000FFBD0000FF00BDBDFF00FFFFFF00BDBDFFBD0000FFBD00
+ 00FFBD0000FFBD0000FF848484FF000000000000000000000000000000000000
+ 0000000000000000000000000000BDBD00FFBD0000FFBD0000FFBD0000FF00FF
+ FFFF00FFFFFF00FFFFFFBD0000FFBD0000FFBD0000FFBD0000FF000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000BD0000FFBD0000FFBD0000FF00BDBDFF00FFFFFF00BDBDFFBD0000FFBD00
+ 00FFBD0000FFBD0000FF00000000000000000000000000000000000000000000
+ 00000000000000000000000000000000000000000000848484FF848484FFBD00
+ 00FFBD0000FFBD0000FFBD0000FF848484FF0000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000848484FF848484FF848484FF848484FF0000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 00000000000000000000F7CEA5FFF7CEA5FFF7CEA5FFF7CEA5FFF7CEA5FFF7CE
+ A5FFF7CEA5FFF7CEA5FFF7CEA5FFF7CEA5FFF7CEA5FFF7FFFFFF000000000000
+ 0000000000000000000000000000000000000000000000000000F7CEA5FFF7CE
+ A5FFF7CEA5FFF7CEA5FFF7CEA5FFF7CEA5FFF7CEA5FFF7CEA5FFF7CEA5FFF7CE
+ A5FFF7CEA5FFF7CEA5FF00000000000000000000000000000000000000000000
+ 00000000000000000000F7CEA5FFF7FFFFFFF7FFFFFFF7FFFFFFF7FFFFFFF7FF
+ FFFFF7FFFFFFF7FFFFFFF7FFFFFFF7FFFFFFF7FFFFFFF7CEA5FF000000000000
+ 0000000000000000000000000000000000000000000000000000F7FFFFFFFFFF
+ FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+ FFFFFFFFFFFFF7CEA5FF00000000000000000000000000000000000000000000
+ 00000000000000000000F7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+ FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7CEA5FF00000000FFFF
+ FFFFF7CEA5FFF7CEA5FFF7CEA5FFF7CEA5FFF7CEA5FFF7CEA5FFF7CEA5FFF7CE
+ A5FFF7CEA5FFF7CEA5FFF7CEA5FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+ FFFFFFFFFFFFF7CEA5FF00000000F7CEA5FFF7CEA5FFF7CEA5FFF7CEA5FFF7CE
+ A5FFF7CEA5FFF7CEA5FFF7CEA5FFF7CEA5FFF7CEA5FFF7CEA5FFF7CEA5FFFFFF
+ FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7CEA5FF00000000F7CE
+ A5FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+ FFFFFFFFFFFFFFFFFFFFC6A563FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+ FFFFFFFFFFFFC6A563FF00000000F7CEA5FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+ FFFFFFFFFFFFFFFFFFFFC68421FFC66300FFC66300FFC66300FFC66300FFC663
+ 00FFC66300FFC66300FFC66300FFC66300FFC66300FFC66300FFC68442FFF7CE
+ A5FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC68400FFC684
+ 00FFC68400FFC68400FFC68400FFC68400FFC68400FFC68400FFC68400FFC684
+ 00FFC68400FFC68400FFC66300FFF7CEA5FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+ FFFFFFFFFFFFFFFFFFFFF7CEA5FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+ FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC68421FFF7CE
+ A5FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC6C684FFFFFF
+ FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+ FFFFFFFFFFFFFFFFFFFFC68421FFF7CEA5FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+ FFFFFFFFFFFFFFFFFFFFC6C684FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+ FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC68421FFF7CE
+ A5FFF7CEA5FFF7CEA5FFF7CEA5FFF7CEA5FFF7CEA5FFF7CEA5FFC6C684FFFFFF
+ FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+ FFFFF7FFFFFFF7CEA5FFC68421FF000000000000000000000000000000000000
+ 00000000000000000000C6C684FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+ FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7CEA5FFF7CEA5FFC68421FF0000
+ 0000000000000000000000000000000000000000000000000000C6C684FFFFFF
+ FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFF7CE
+ A5FFF7CEA5FFF7CEA5FFC68421FF000000000000000000000000000000000000
+ 00000000000000000000C6C684FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+ FFFFFFFFFFFFF7FFFFFFF7CEA5FFF7CEA5FFF7CEA5FFF7CEA5FFC68421FF0000
+ 0000000000000000000000000000000000000000000000000000C68442FFC6A5
+ 84FFC6A584FFC6A584FFC6A584FFC6A563FFC6A563FFC6A563FFC6A563FFC6A5
+ 63FFC6A563FFC6A563FFC66300FF000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000
+ }
+ end
+end
diff --git a/components/jvcllaz/examples/JvID3v1/JvID3v1MainFormU.pas b/components/jvcllaz/examples/JvID3v1/JvID3v1MainFormU.pas
new file mode 100644
index 000000000..1f49e0222
--- /dev/null
+++ b/components/jvcllaz/examples/JvID3v1/JvID3v1MainFormU.pas
@@ -0,0 +1,254 @@
+{******************************************************************
+
+ JEDI-VCL Demo
+
+ Copyright (C) 2002 Project JEDI
+
+ Original author:
+
+ Contributor(s):
+
+ You may retrieve the latest version of this file at the JEDI-JVCL
+ home page, located at http://jvcl.delphi-jedi.org
+
+ The contents of this file are used with permission, 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_1Final.html
+
+ Software distributed under the License is distributed on an
+ "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ implied. See the License for the specific language governing
+ rights and limitations under the License.
+
+******************************************************************}
+
+unit JvID3v1MainFormU;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+ //Windows, Messages,
+ SysUtils, Classes, Graphics, Controls, Forms,
+ Dialogs, //JvComponent,
+ StdCtrls, //Mask, //JvToolEdit,
+ JvId3v1, ComCtrls, //ToolWin,
+ ActnList, //ImgList,
+ EditBtn, Spin;
+{
+ JvBaseDlg, JvTipOfDay, JvBalloonHint, JvMaskEdit, JvSpin, JvJVCLAboutForm,
+ JvExMask;
+}
+type
+
+ { TJvID3v1MainForm }
+
+ TJvID3v1MainForm = class(TForm)
+ JvFilenameEdit1: TFilenameEdit;
+ edtTitle: TEdit;
+ JvId3v11: TJvId3v1;
+ edtAlbum: TEdit;
+ edtArtist: TEdit;
+ edtYear: TEdit;
+ edtComment: TEdit;
+ cmbGenre: TComboBox;
+ lblArtist: TLabel;
+ lblAlbum: TLabel;
+ lblYear: TLabel;
+ lblComment: TLabel;
+ lblGenre: TLabel;
+ ActionList1: TActionList;
+ actSave: TAction;
+ actRefresh: TAction;
+ actErase: TAction;
+ actExit: TAction;
+ actOnTop: TAction;
+ actAbout: TAction;
+ ImageList1: TImageList;
+ ToolBar1: TToolBar;
+ ToolButton1: TToolButton;
+ ToolButton2: TToolButton;
+ ToolButton3: TToolButton;
+ ToolButton4: TToolButton;
+ ToolButton5: TToolButton;
+ ToolButton6: TToolButton;
+ lblHasTag: TLabel;
+// JvTipOfDay1: TJvTipOfDay;
+// JvJVCLAboutComponent1: TJvJVCLAboutComponent;
+// JvBalloonHint1: TJvBalloonHint;
+ sedTrack: TSpinEdit;
+ lblTitle: TLabel;
+ lblTrack: TLabel;
+ procedure actAboutExecute(Sender: TObject);
+ procedure actSaveExecute(Sender: TObject);
+ procedure actEraseExecute(Sender: TObject);
+ procedure actExitExecute(Sender: TObject);
+ procedure actRefreshExecute(Sender: TObject);
+ procedure actOnTopExecute(Sender: TObject);
+ procedure FormCreate(Sender: TObject);
+ procedure FormDropFiles(Sender: TObject; const FileNames: array of String);
+ procedure JvFilenameEdit1AcceptFileName(Sender: TObject; var Value: String);
+ procedure JvFilenameEdit1KeyPress(Sender: TObject; var Key: Char);
+ public
+ procedure ChangeFileNameTo(S: string);
+ procedure FillGenres(Strings: TStrings);
+ procedure UpdateCtrls;
+ procedure UpdateCaption;
+ end;
+
+var
+ JvID3v1MainForm: TJvID3v1MainForm;
+
+implementation
+
+uses
+ JvId3v2Types;
+
+{$R *.lfm}
+
+procedure TJvID3v1MainForm.ChangeFileNameTo(S: string);
+begin
+ JvFilenameEdit1.Text := S;
+ JvFilenameEdit1.Hint := S;
+ JvId3v11.FileName := S;
+ JvId3v11.Open;
+ UpdateCtrls;
+ UpdateCaption;
+ FocusControl(edtTitle);
+end;
+
+procedure TJvID3v1MainForm.FillGenres(Strings: TStrings);
+begin
+ ID3_Genres(Strings,true);
+end;
+
+procedure TJvID3v1MainForm.actSaveExecute(Sender: TObject);
+begin
+ if JvId3v11.FileName = '' then
+// JvBalloonHint1.ActivateHint(JvFilenameEdit1, 'First select a mp3 file', ikError, 'Error', 5000)
+ else
+ begin
+ JvId3v11.SongName := edtTitle.Text;
+ JvId3v11.Artist := edtArtist.Text;
+ JvId3v11.Album := edtAlbum.Text;
+ JvId3v11.Year := edtYear.Text;
+ JvId3v11.GenreAsString := cmbGenre.Text;
+ JvId3v11.Comment := edtComment.Text;
+ JvId3v11.AlbumTrack := sedTrack.Value; //AsInteger;
+
+ if JvId3v11.Commit then
+ UpdateCaption
+ else
+ {
+ JvBalloonHint1.ActivateHint(ToolButton2, 'Could not save changes.'#13+
+ 'The file is probably opened by another application.', ikError, 'Error')}
+ ;
+ end;
+end;
+
+procedure TJvID3v1MainForm.actEraseExecute(Sender: TObject);
+begin
+ if JvId3v11.FileName = '' then
+ //JvBalloonHint1.ActivateHint(JvFilenameEdit1, 'First select a mp3 file', ikError, 'Error', 5000)
+ else
+ begin
+ JvId3v11.Erase;
+ UpdateCtrls;
+ UpdateCaption;
+ end;
+end;
+
+procedure TJvID3v1MainForm.actExitExecute(Sender: TObject);
+begin
+ Close;
+end;
+
+procedure TJvID3v1MainForm.actRefreshExecute(Sender: TObject);
+begin
+ if JvId3v11.FileName = '' then
+ //JvBalloonHint1.ActivateHint(JvFilenameEdit1, 'First select a mp3 file', ikError, 'Error', 5000)
+ else
+ ChangeFileNameTo(JvId3v11.FileName);
+end;
+
+procedure TJvID3v1MainForm.actOnTopExecute(Sender: TObject);
+const
+ CStyle: array[Boolean] of TFormStyle = (fsNormal, fsStayOnTop);
+begin
+ //JvDragDrop1.AcceptDrag := False;
+ actOnTop.Checked := not actOnTop.Checked;
+ FormStyle := CStyle[actOnTop.Checked];
+ //JvDragDrop1.AcceptDrag := True;
+end;
+
+procedure TJvID3v1MainForm.FormCreate(Sender: TObject);
+begin
+ { This is put in the OnCreate and not in the OnShow event, because we change
+ Form1.FormStyle at run-time that will trigger the OnShow event }
+ FillGenres(cmbGenre.Items);
+ UpdateCaption;
+end;
+
+procedure TJvID3v1MainForm.FormDropFiles(Sender: TObject;
+ const FileNames: array of String);
+begin
+ if Length(FileNames) > 0 then
+ ChangeFileNameTo(FileNames[0]);
+end;
+
+procedure TJvID3v1MainForm.JvFilenameEdit1AcceptFileName(Sender: TObject;
+ var Value: String);
+begin
+ ChangeFileNameTo(Value);
+end;
+
+procedure TJvID3v1MainForm.JvFilenameEdit1KeyPress(Sender: TObject; var Key: Char);
+begin
+ if Key = #13 then
+ begin
+ if JvFilenameEdit1.Text = '' then
+ //JvBalloonHint1.ActivateHint(JvFilenameEdit1, 'Empty strings are no file names', ikError, 'Error', 5000)
+ else
+ ChangeFileNameTo(JvFilenameEdit1.FileName);
+ end;
+end;
+
+procedure TJvID3v1MainForm.UpdateCaption;
+const
+ CHasTagStr: array[Boolean] of string = ('No tag', 'Has Tag');
+ CHasTagColor: array[Boolean] of TColor = (clRed, clBlack);
+var
+ HasTag: Boolean;
+begin
+ if JvId3v11.FileName > '' then
+ begin
+ { Store TagPresent in variabele to prevent double checks whether the file
+ has a tag }
+ HasTag := JvId3v11.HasTag;
+ lblHasTag.Font.Color := CHasTagColor[HasTag];
+ lblHasTag.Caption := CHasTagStr[HasTag];
+ end
+ else
+ lblHasTag.Caption := '';
+end;
+
+procedure TJvID3v1MainForm.UpdateCtrls;
+begin
+ edtTitle.Text := JvId3v11.SongName;
+ edtAlbum.Text := JvId3v11.Album;
+ edtArtist.Text := JvId3v11.Artist;
+ edtYear.Text := JvId3v11.Year;
+ edtComment.Text := JvId3v11.Comment;
+ sedTrack.Value := JvId3v11.AlbumTrack;
+ cmbGenre.ItemIndex := cmbGenre.Items.IndexOfObject(TObject(PtrInt(JvId3v11.Genre)));
+end;
+
+procedure TJvID3v1MainForm.actAboutExecute(Sender: TObject);
+begin
+ //JvJVCLAboutComponent1.Execute;
+end;
+
+end.
diff --git a/components/jvcllaz/examples/JvID3v2/JvID3v2Demo.lpi b/components/jvcllaz/examples/JvID3v2/JvID3v2Demo.lpi
new file mode 100644
index 000000000..2e1c28d65
--- /dev/null
+++ b/components/jvcllaz/examples/JvID3v2/JvID3v2Demo.lpi
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/jvcllaz/examples/JvID3v2/JvID3v2Demo.lpr b/components/jvcllaz/examples/JvID3v2/JvID3v2Demo.lpr
new file mode 100644
index 000000000..05205d088
--- /dev/null
+++ b/components/jvcllaz/examples/JvID3v2/JvID3v2Demo.lpr
@@ -0,0 +1,16 @@
+program JvID3v2Demo;
+
+uses
+ Interfaces,
+ Forms,
+ JvID3v2MainFormU in 'JvID3v2MainFormU.pas' {JvID3v2MainForm},
+ JvID3v2EditFormU in 'JvID3v2EditFormU.pas' {JvID3v2EditForm};
+
+{$R *.res}
+
+begin
+ Application.Scaled:=True;
+ Application.Initialize;
+ Application.CreateForm(TJvID3v2MainForm, JvID3v2MainForm);
+ Application.Run;
+end.
diff --git a/components/jvcllaz/examples/JvID3v2/JvID3v2EditFormU.lfm b/components/jvcllaz/examples/JvID3v2/JvID3v2EditFormU.lfm
new file mode 100644
index 000000000..195676e96
--- /dev/null
+++ b/components/jvcllaz/examples/JvID3v2/JvID3v2EditFormU.lfm
@@ -0,0 +1,910 @@
+object JvID3v2EditForm: TJvID3v2EditForm
+ Left = 345
+ Height = 384
+ Top = 248
+ Width = 510
+ BorderStyle = bsDialog
+ Caption = 'JvID3v2EditForm'
+ ClientHeight = 384
+ ClientWidth = 510
+ Color = clBtnFace
+ Font.Color = clWindowText
+ OnCreate = FormCreate
+ Position = poMainFormCenter
+ LCLVersion = '1.9.0.0'
+ object PageControl1: TPageControl
+ Left = 121
+ Height = 344
+ Top = 40
+ Width = 389
+ TabStop = False
+ ActivePage = tshLyrics
+ Align = alClient
+ TabIndex = 1
+ TabOrder = 0
+ object tshWinampTags: TTabSheet
+ Caption = 'tshWinampTags'
+ ClientHeight = 316
+ ClientWidth = 381
+ TabVisible = False
+ object lblTitle: TLabel
+ AnchorSideTop.Control = edtTitle
+ AnchorSideTop.Side = asrCenter
+ AnchorSideRight.Control = lblEncodedBy
+ AnchorSideRight.Side = asrBottom
+ Left = 51
+ Height = 15
+ Top = 8
+ Width = 26
+ Anchors = [akTop, akRight]
+ Caption = '&Title:'
+ FocusControl = edtTitle
+ ParentColor = False
+ end
+ object lblArtist: TLabel
+ AnchorSideTop.Control = edtArtist
+ AnchorSideTop.Side = asrCenter
+ AnchorSideRight.Control = lblEncodedBy
+ AnchorSideRight.Side = asrBottom
+ Left = 46
+ Height = 15
+ Top = 35
+ Width = 31
+ Anchors = [akTop, akRight]
+ Caption = '&Artist:'
+ FocusControl = edtArtist
+ ParentColor = False
+ end
+ object lblAlbum: TLabel
+ AnchorSideTop.Control = edtAlbum
+ AnchorSideTop.Side = asrCenter
+ AnchorSideRight.Control = lblEncodedBy
+ AnchorSideRight.Side = asrBottom
+ Left = 38
+ Height = 15
+ Top = 62
+ Width = 39
+ Anchors = [akTop, akRight]
+ Caption = 'Al&bum:'
+ FocusControl = edtAlbum
+ ParentColor = False
+ end
+ object lblYear: TLabel
+ AnchorSideTop.Control = edtYear
+ AnchorSideTop.Side = asrCenter
+ AnchorSideRight.Control = lblEncodedBy
+ AnchorSideRight.Side = asrBottom
+ Left = 52
+ Height = 15
+ Top = 89
+ Width = 25
+ Anchors = [akTop, akRight]
+ Caption = '&Year:'
+ FocusControl = edtYear
+ ParentColor = False
+ end
+ object lblComposer: TLabel
+ AnchorSideTop.Control = edtComposer
+ AnchorSideTop.Side = asrCenter
+ AnchorSideRight.Control = lblEncodedBy
+ AnchorSideRight.Side = asrBottom
+ Left = 19
+ Height = 15
+ Top = 189
+ Width = 58
+ Anchors = [akTop, akRight]
+ Caption = 'Co&mposer:'
+ FocusControl = edtComposer
+ ParentColor = False
+ end
+ object lblOrigArtist: TLabel
+ AnchorSideTop.Control = edtOrigArtist
+ AnchorSideTop.Side = asrCenter
+ AnchorSideRight.Control = lblEncodedBy
+ AnchorSideRight.Side = asrBottom
+ Left = 17
+ Height = 15
+ Top = 216
+ Width = 60
+ Anchors = [akTop, akRight]
+ Caption = '&Orig. Artist:'
+ FocusControl = edtOrigArtist
+ ParentColor = False
+ end
+ object lblCopyright: TLabel
+ AnchorSideTop.Control = edtCopyright
+ AnchorSideTop.Side = asrCenter
+ AnchorSideRight.Control = lblEncodedBy
+ AnchorSideRight.Side = asrBottom
+ Left = 21
+ Height = 15
+ Top = 243
+ Width = 56
+ Anchors = [akTop, akRight]
+ Caption = 'Co&pyright:'
+ FocusControl = edtCopyright
+ ParentColor = False
+ end
+ object lblURL: TLabel
+ AnchorSideTop.Control = edtURL
+ AnchorSideTop.Side = asrCenter
+ AnchorSideRight.Control = lblEncodedBy
+ AnchorSideRight.Side = asrBottom
+ Left = 53
+ Height = 15
+ Top = 270
+ Width = 24
+ Anchors = [akTop, akRight]
+ Caption = '&URL:'
+ FocusControl = edtURL
+ ParentColor = False
+ end
+ object lblEncodedBy: TLabel
+ AnchorSideLeft.Control = tshWinampTags
+ AnchorSideTop.Control = edtEncodedBy
+ AnchorSideTop.Side = asrCenter
+ Left = 12
+ Height = 15
+ Top = 297
+ Width = 65
+ BorderSpacing.Left = 12
+ Caption = '&Encoded by:'
+ FocusControl = edtEncodedBy
+ ParentColor = False
+ end
+ object lblGenre: TLabel
+ AnchorSideLeft.Control = edtYear
+ AnchorSideLeft.Side = asrBottom
+ AnchorSideTop.Control = lblYear
+ Left = 164
+ Height = 15
+ Top = 89
+ Width = 34
+ BorderSpacing.Left = 16
+ Caption = '&Genre:'
+ FocusControl = cmbGenre
+ ParentColor = False
+ end
+ object lblComment: TLabel
+ AnchorSideTop.Control = memComment
+ AnchorSideRight.Control = lblEncodedBy
+ AnchorSideRight.Side = asrBottom
+ Left = 20
+ Height = 15
+ Top = 112
+ Width = 57
+ Anchors = [akTop, akRight]
+ Caption = '&Comment:'
+ FocusControl = memComment
+ ParentColor = False
+ end
+ object edtTitle: TEdit
+ AnchorSideLeft.Control = edtEncodedBy
+ AnchorSideTop.Control = tshWinampTags
+ AnchorSideRight.Control = edtEncodedBy
+ AnchorSideRight.Side = asrBottom
+ Left = 81
+ Height = 23
+ Top = 4
+ Width = 296
+ Anchors = [akTop, akLeft, akRight]
+ BorderSpacing.Top = 4
+ TabOrder = 0
+ end
+ object edtArtist: TEdit
+ AnchorSideLeft.Control = edtEncodedBy
+ AnchorSideTop.Control = edtTitle
+ AnchorSideTop.Side = asrBottom
+ AnchorSideRight.Control = edtEncodedBy
+ AnchorSideRight.Side = asrBottom
+ Left = 81
+ Height = 23
+ Top = 31
+ Width = 296
+ Anchors = [akTop, akLeft, akRight]
+ BorderSpacing.Top = 4
+ TabOrder = 1
+ end
+ object edtAlbum: TEdit
+ AnchorSideLeft.Control = edtEncodedBy
+ AnchorSideTop.Control = edtArtist
+ AnchorSideTop.Side = asrBottom
+ AnchorSideRight.Control = edtEncodedBy
+ AnchorSideRight.Side = asrBottom
+ Left = 81
+ Height = 23
+ Top = 58
+ Width = 296
+ Anchors = [akTop, akLeft, akRight]
+ BorderSpacing.Top = 4
+ TabOrder = 2
+ end
+ object edtYear: TEdit
+ AnchorSideLeft.Control = edtEncodedBy
+ AnchorSideTop.Control = edtAlbum
+ AnchorSideTop.Side = asrBottom
+ Left = 81
+ Height = 23
+ Top = 85
+ Width = 67
+ BorderSpacing.Top = 4
+ TabOrder = 3
+ end
+ object edtComposer: TEdit
+ AnchorSideLeft.Control = edtEncodedBy
+ AnchorSideTop.Control = memComment
+ AnchorSideTop.Side = asrBottom
+ AnchorSideRight.Control = edtEncodedBy
+ AnchorSideRight.Side = asrBottom
+ Left = 81
+ Height = 23
+ Top = 185
+ Width = 296
+ Anchors = [akTop, akLeft, akRight]
+ BorderSpacing.Top = 4
+ TabOrder = 6
+ end
+ object edtOrigArtist: TEdit
+ AnchorSideLeft.Control = edtEncodedBy
+ AnchorSideTop.Control = edtComposer
+ AnchorSideTop.Side = asrBottom
+ AnchorSideRight.Control = edtEncodedBy
+ AnchorSideRight.Side = asrBottom
+ Left = 81
+ Height = 23
+ Top = 212
+ Width = 296
+ Anchors = [akTop, akLeft, akRight]
+ BorderSpacing.Top = 4
+ TabOrder = 7
+ end
+ object edtCopyright: TEdit
+ AnchorSideLeft.Control = edtEncodedBy
+ AnchorSideTop.Control = edtOrigArtist
+ AnchorSideTop.Side = asrBottom
+ AnchorSideRight.Control = edtEncodedBy
+ AnchorSideRight.Side = asrBottom
+ Left = 81
+ Height = 23
+ Top = 239
+ Width = 296
+ Anchors = [akTop, akLeft, akRight]
+ BorderSpacing.Top = 4
+ TabOrder = 8
+ end
+ object edtURL: TEdit
+ AnchorSideLeft.Control = edtEncodedBy
+ AnchorSideTop.Control = edtCopyright
+ AnchorSideTop.Side = asrBottom
+ AnchorSideRight.Control = edtEncodedBy
+ AnchorSideRight.Side = asrBottom
+ Left = 81
+ Height = 23
+ Top = 266
+ Width = 296
+ Anchors = [akTop, akLeft, akRight]
+ BorderSpacing.Top = 4
+ TabOrder = 9
+ end
+ object edtEncodedBy: TEdit
+ AnchorSideLeft.Control = lblEncodedBy
+ AnchorSideLeft.Side = asrBottom
+ AnchorSideTop.Control = edtURL
+ AnchorSideTop.Side = asrBottom
+ AnchorSideRight.Control = tshWinampTags
+ AnchorSideRight.Side = asrBottom
+ Left = 81
+ Height = 23
+ Top = 293
+ Width = 296
+ Anchors = [akTop, akLeft, akRight]
+ BorderSpacing.Left = 4
+ BorderSpacing.Top = 4
+ BorderSpacing.Right = 4
+ BorderSpacing.Bottom = 4
+ TabOrder = 10
+ end
+ object cmbGenre: TComboBox
+ AnchorSideLeft.Control = lblGenre
+ AnchorSideLeft.Side = asrBottom
+ AnchorSideTop.Control = edtAlbum
+ AnchorSideTop.Side = asrBottom
+ AnchorSideRight.Control = edtEncodedBy
+ AnchorSideRight.Side = asrBottom
+ Left = 206
+ Height = 23
+ Top = 85
+ Width = 171
+ Anchors = [akTop, akLeft, akRight]
+ BorderSpacing.Left = 8
+ BorderSpacing.Top = 4
+ ItemHeight = 15
+ TabOrder = 4
+ end
+ object memComment: TMemo
+ AnchorSideLeft.Control = edtEncodedBy
+ AnchorSideTop.Control = edtYear
+ AnchorSideTop.Side = asrBottom
+ AnchorSideRight.Control = edtEncodedBy
+ AnchorSideRight.Side = asrBottom
+ Left = 81
+ Height = 69
+ Top = 112
+ Width = 296
+ Anchors = [akTop, akLeft, akRight]
+ BorderSpacing.Top = 4
+ Lines.Strings = (
+ ''
+ )
+ TabOrder = 5
+ end
+ end
+ object tshLyrics: TTabSheet
+ Caption = 'tshLyrics'
+ ClientHeight = 316
+ ClientWidth = 381
+ ImageIndex = 1
+ TabVisible = False
+ object lblLanguage: TLabel
+ AnchorSideLeft.Control = tshLyrics
+ AnchorSideTop.Control = cmbLanguage
+ AnchorSideTop.Side = asrCenter
+ Left = 4
+ Height = 15
+ Top = 8
+ Width = 55
+ BorderSpacing.Left = 4
+ Caption = 'Language:'
+ ParentColor = False
+ end
+ object lblDescription: TLabel
+ AnchorSideLeft.Control = memLyrics
+ AnchorSideTop.Control = edtDescription
+ AnchorSideTop.Side = asrCenter
+ Left = 4
+ Height = 15
+ Top = 240
+ Width = 63
+ Caption = 'Description:'
+ ParentColor = False
+ end
+ object lblWriter: TLabel
+ AnchorSideTop.Control = edtWriter
+ AnchorSideTop.Side = asrCenter
+ Left = 8
+ Height = 15
+ Top = 267
+ Width = 35
+ Caption = 'Writer:'
+ ParentColor = False
+ end
+ object cmbLanguage: TComboBox
+ AnchorSideLeft.Control = lblLanguage
+ AnchorSideLeft.Side = asrBottom
+ AnchorSideTop.Control = tshLyrics
+ Left = 67
+ Height = 23
+ Top = 4
+ Width = 145
+ BorderSpacing.Left = 8
+ BorderSpacing.Top = 4
+ ItemHeight = 15
+ Sorted = True
+ TabOrder = 0
+ end
+ object memLyrics: TMemo
+ AnchorSideLeft.Control = tshLyrics
+ AnchorSideTop.Control = cmbLanguage
+ AnchorSideTop.Side = asrBottom
+ AnchorSideRight.Control = tshLyrics
+ AnchorSideRight.Side = asrBottom
+ Left = 4
+ Height = 201
+ Top = 31
+ Width = 373
+ Anchors = [akTop, akLeft, akRight]
+ BorderSpacing.Left = 4
+ BorderSpacing.Top = 4
+ BorderSpacing.Right = 4
+ TabOrder = 1
+ end
+ object edtDescription: TEdit
+ AnchorSideLeft.Control = lblDescription
+ AnchorSideLeft.Side = asrBottom
+ AnchorSideTop.Control = memLyrics
+ AnchorSideTop.Side = asrBottom
+ AnchorSideRight.Control = memLyrics
+ AnchorSideRight.Side = asrBottom
+ Left = 75
+ Height = 23
+ Top = 236
+ Width = 302
+ Anchors = [akTop, akLeft, akRight]
+ BorderSpacing.Left = 8
+ BorderSpacing.Top = 4
+ TabOrder = 2
+ end
+ object edtWriter: TEdit
+ AnchorSideLeft.Control = edtDescription
+ AnchorSideTop.Control = edtDescription
+ AnchorSideTop.Side = asrBottom
+ AnchorSideRight.Control = memLyrics
+ AnchorSideRight.Side = asrBottom
+ Left = 75
+ Height = 23
+ Top = 263
+ Width = 302
+ Anchors = [akTop, akLeft, akRight]
+ BorderSpacing.Top = 4
+ TabOrder = 3
+ end
+ end
+ object tshPictures: TTabSheet
+ Caption = 'tshPictures'
+ ClientHeight = 316
+ ClientWidth = 381
+ ImageIndex = 2
+ TabVisible = False
+ object imgPicture: TImage
+ AnchorSideLeft.Control = lsvPictures
+ AnchorSideTop.Control = lsvPictures
+ AnchorSideTop.Side = asrBottom
+ Left = 4
+ Height = 120
+ Top = 170
+ Width = 120
+ BorderSpacing.Top = 8
+ Stretch = True
+ end
+ object lblPictureName: TLabel
+ AnchorSideLeft.Control = btnChange
+ AnchorSideTop.Control = edtPictureName
+ AnchorSideTop.Side = asrCenter
+ Left = 136
+ Height = 15
+ Top = 236
+ Width = 35
+ Caption = 'Name:'
+ ParentColor = False
+ end
+ object lblPictureType: TLabel
+ AnchorSideLeft.Control = lblPictureName
+ AnchorSideTop.Control = cmbPictureType
+ AnchorSideTop.Side = asrCenter
+ Left = 136
+ Height = 15
+ Top = 263
+ Width = 28
+ Caption = 'Type:'
+ ParentColor = False
+ end
+ object lsvPictures: TListView
+ AnchorSideLeft.Control = tshPictures
+ AnchorSideTop.Control = tshPictures
+ AnchorSideRight.Control = tshPictures
+ AnchorSideRight.Side = asrBottom
+ Left = 4
+ Height = 158
+ Top = 4
+ Width = 373
+ Anchors = [akTop, akLeft, akRight]
+ BorderSpacing.Left = 4
+ BorderSpacing.Top = 4
+ BorderSpacing.Right = 4
+ Columns = <
+ item
+ Caption = 'Name'
+ Width = 150
+ end
+ item
+ Caption = 'Type'
+ Width = 70
+ end
+ item
+ Caption = 'Format'
+ Width = 70
+ end
+ item
+ Caption = 'Size'
+ end>
+ HideSelection = False
+ ReadOnly = True
+ RowSelect = True
+ TabOrder = 0
+ ViewStyle = vsReport
+ OnClick = lsvPicturesClick
+ end
+ object edtPictureName: TEdit
+ AnchorSideLeft.Control = lblPictureName
+ AnchorSideLeft.Side = asrBottom
+ AnchorSideTop.Control = btnChange
+ AnchorSideTop.Side = asrBottom
+ AnchorSideRight.Control = lsvPictures
+ AnchorSideRight.Side = asrBottom
+ Left = 179
+ Height = 23
+ Top = 232
+ Width = 198
+ Anchors = [akTop, akLeft, akRight]
+ BorderSpacing.Left = 8
+ BorderSpacing.Top = 8
+ TabOrder = 5
+ end
+ object cmbPictureType: TComboBox
+ AnchorSideLeft.Control = edtPictureName
+ AnchorSideTop.Control = edtPictureName
+ AnchorSideTop.Side = asrBottom
+ AnchorSideRight.Control = lsvPictures
+ AnchorSideRight.Side = asrBottom
+ Left = 179
+ Height = 23
+ Top = 259
+ Width = 198
+ Anchors = [akTop, akLeft, akRight]
+ BorderSpacing.Top = 4
+ ItemHeight = 15
+ Sorted = True
+ TabOrder = 6
+ end
+ object btnChange: TButton
+ AnchorSideLeft.Control = btnAdd
+ AnchorSideTop.Control = btnAdd
+ AnchorSideTop.Side = asrBottom
+ AnchorSideRight.Control = btnAdd
+ AnchorSideRight.Side = asrBottom
+ Left = 136
+ Height = 25
+ Top = 199
+ Width = 75
+ Action = actChangePicture
+ Anchors = [akTop, akLeft, akRight]
+ BorderSpacing.Top = 4
+ TabOrder = 4
+ end
+ object btnAdd: TButton
+ AnchorSideLeft.Control = imgPicture
+ AnchorSideLeft.Side = asrBottom
+ AnchorSideTop.Control = imgPicture
+ Left = 136
+ Height = 25
+ Top = 170
+ Width = 75
+ Action = actAddPicture
+ BorderSpacing.Left = 12
+ TabOrder = 1
+ end
+ object btnDelete: TButton
+ AnchorSideLeft.Control = btnAdd
+ AnchorSideLeft.Side = asrBottom
+ AnchorSideTop.Control = btnAdd
+ Left = 215
+ Height = 25
+ Top = 170
+ Width = 75
+ Action = actDeletePicture
+ BorderSpacing.Left = 4
+ TabOrder = 2
+ end
+ object btnSave: TButton
+ AnchorSideLeft.Control = btnDelete
+ AnchorSideLeft.Side = asrBottom
+ AnchorSideTop.Control = btnAdd
+ Left = 294
+ Height = 25
+ Top = 170
+ Width = 75
+ Action = actSavePicture
+ BorderSpacing.Left = 4
+ TabOrder = 3
+ end
+ end
+ object tshAllFrames: TTabSheet
+ Caption = 'tshAllFrames'
+ ClientHeight = 316
+ ClientWidth = 381
+ ImageIndex = 3
+ TabVisible = False
+ object lsvAllFrames: TListView
+ Left = 4
+ Height = 308
+ Top = 4
+ Width = 373
+ Align = alClient
+ BorderSpacing.Around = 4
+ Columns = <
+ item
+ Caption = 'Name'
+ end
+ item
+ Caption = 'Supported'
+ Width = 70
+ end
+ item
+ Caption = 'Description'
+ Width = 226
+ end>
+ ParentShowHint = False
+ ReadOnly = True
+ RowSelect = True
+ ShowHint = True
+ TabOrder = 0
+ ViewStyle = vsReport
+ end
+ end
+ end
+ object lsbNavigator: TListBox
+ Left = 0
+ Height = 344
+ Top = 40
+ Width = 121
+ Align = alLeft
+ Items.Strings = (
+ 'Winamp tags'
+ 'Lyrics'
+ 'Pictures'
+ 'All Frames'
+ )
+ ItemHeight = 15
+ OnClick = lsbNavigatorClick
+ TabOrder = 1
+ end
+ object ToolBar1: TToolBar
+ Left = 0
+ Height = 40
+ Top = 0
+ Width = 510
+ ButtonHeight = 36
+ ButtonWidth = 72
+ Caption = 'ToolBar1'
+ Images = iml16
+ ShowCaptions = True
+ TabOrder = 2
+ object ToolButton1: TToolButton
+ Left = 1
+ Top = 2
+ Action = actOK
+ end
+ object ToolButton2: TToolButton
+ Left = 73
+ Top = 2
+ Action = actCancel
+ end
+ object ToolButton3: TToolButton
+ Left = 145
+ Top = 2
+ Action = actRemove
+ end
+ object ToolButton5: TToolButton
+ Left = 217
+ Top = 2
+ Action = actCopyFromv1
+ end
+ object ToolButton4: TToolButton
+ Left = 299
+ Top = 2
+ Action = actCopyTov1
+ end
+ end
+ object acl16: TActionList
+ Images = iml16
+ left = 40
+ top = 112
+ object actOK: TAction
+ Caption = 'OK'
+ ImageIndex = 1
+ OnExecute = actOKExecute
+ end
+ object actCancel: TAction
+ Caption = 'Cancel'
+ ImageIndex = 0
+ OnExecute = actCancelExecute
+ end
+ object actRemove: TAction
+ Caption = 'Remove'
+ ImageIndex = 2
+ OnExecute = actRemoveExecute
+ end
+ object actAddPicture: TAction
+ Caption = 'Add'
+ OnExecute = actAddPictureExecute
+ end
+ object actDeletePicture: TAction
+ Caption = 'Delete'
+ OnExecute = actDeletePictureExecute
+ OnUpdate = ItemSelected
+ end
+ object actSavePicture: TAction
+ Caption = 'Save'
+ OnExecute = actSavePictureExecute
+ OnUpdate = ItemSelected
+ end
+ object actChangePicture: TAction
+ Caption = 'Change'
+ OnExecute = actChangePictureExecute
+ OnUpdate = ItemSelected
+ end
+ object actCopyTov1: TAction
+ Caption = 'Copy to v1'
+ ImageIndex = 4
+ OnExecute = actCopyTov1Execute
+ end
+ object actCopyFromv1: TAction
+ Caption = 'Copy From v1'
+ ImageIndex = 3
+ OnExecute = actCopyFromv1Execute
+ end
+ end
+ object iml16: TImageList
+ left = 40
+ top = 176
+ Bitmap = {
+ 4C69050000001000000010000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000FF0000
+ 00FF0000000000000000000000000000000000000000000000FF000000FF9C8C
+ 6FFF0000000000000000000000000000000000000000000000FF9999FFFF9999
+ FFFF000000FF000000000000000000000000000000FF9999FFFF9999FFFF0000
+ 00FF9C8C6FFF00000000000000000000000000000000000000FF9999FFFF0000
+ FFFF2600C4FF000000FF00000000000000FF9999FFFF0000FFFF000099FF0000
+ 00FF9C8C6FFF0000000000000000000000000000000000000000000000FF2600
+ C4FF0000FFFF2600C4FF000000FF9999FFFF0000FFFF000099FF000000FF9C8C
+ 6FFF9C8C6FFF0000000000000000000000000000000000000000000000000000
+ 00FF2600C4FF0000FFFF9999FFFF0000FFFF000099FF000000FF9C8C6FFF9C8C
+ 6FFF000000000000000000000000000000000000000000000000000000000000
+ 0000000000FF0000FFFF0000FFFF0000FFFF000000FF9C8C6FFF9C8C6FFF0000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 00FF9999FFFF0000FFFF2600C4FF0000FFFF2600C4FF000000FF9C8C6FFF0000
+ 0000000000000000000000000000000000000000000000000000000000FF9999
+ FFFF0000FFFF000099FF000000FF2600C4FF0000FFFF2600C4FF000000FF9C8C
+ 6FFF0000000000000000000000000000000000000000000000FF9999FFFF0000
+ FFFF000099FF000000FF9C8C6FFF000000FF2600C4FF0000FFFF000099FF0000
+ 00FF9C8C6FFF00000000000000000000000000000000000000FF9999FFFF0000
+ 99FF000000FF9C8C6FFF9C8C6FFF9C8C6FFF000000FF000099FF000099FF0000
+ 00FF9C8C6FFF000000000000000000000000000000009C8C6FFF000000FF0000
+ 00FF9C8C6FFF9C8C6FFF00000000000000009C8C6FFF000000FF000000FF9C8C
+ 6FFF0000000000000000000000000000000000000000000000009C8C6FFF9C8C
+ 6FFF9C8C6FFF000000000000000000000000000000009C8C6FFF9C8C6FFF0000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 00FF000000FF0000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000FF00C0
+ 92FF19A64DFF000000FF00000000000000000000000000000000000000000000
+ 00000000000000000000000000000000000000000000000000FF00C092FF19A6
+ 4DFF0C4D24FF000000FF00000000000000000000000000000000000000000000
+ 000000000000000000000000000000000000000000FF00C092FF19A64DFF0C4D
+ 24FF000000FF9C8C6FFF000000000000000000000000000000FF000000FF0000
+ 0000000000000000000000000000000000FF00C092FF19A64DFF0C4D24FF0000
+ 00FF9C8C6FFF9C8C6FFF0000000000000000000000FF19A64DFF19A64DFF0000
+ 00FF0000000000000000000000FF00C092FF19A64DFF0C4D24FF000000FF9C8C
+ 6FFF9C8C6FFF000000000000000000000000000000FF19A64DFF00C092FF0000
+ 00FF000000FF000000FF00C092FF19A64DFF0C4D24FF000000FF9C8C6FFF9C8C
+ 6FFF000000000000000000000000000000009C8C6FFF000000FF19A64DFF00C0
+ 92FF000000FF00C092FF19A64DFF0C4D24FF000000FF9C8C6FFF9C8C6FFF0000
+ 00000000000000000000000000000000000000000000000000FF19A64DFF19A6
+ 4DFF00C092FF19A64DFF0C4D24FF000000FF9C8C6FFF9C8C6FFF000000000000
+ 000000000000000000000000000000000000000000009C8C6FFF000000FF19A6
+ 4DFF19A64DFF0C4D24FF000000FF9C8C6FFF9C8C6FFF00000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000FF0C4D
+ 24FF0C4D24FF000000FF9C8C6FFF9C8C6FFF0000000000000000000000000000
+ 00000000000000000000000000000000000000000000000000009C8C6FFF0000
+ 00FF000000FF9C8C6FFF9C8C6FFF000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000009C8C
+ 6FFF9C8C6FFF9C8C6FFF00000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000FF000000FF000000FF000000FF000000FF000000FF000000000000
+ 00000000000000000000000000000000000000000000000000FF000000FF0000
+ 00FF000000FFBFBFBFFFFFFFFFFFBFBFBFFFBFBFBFFF000000FF000000FF0000
+ 00FF000000FF00000000000000000000000000000000000000FFBFBFBFFFFFFF
+ FFFFFFFFFFFFBFBFBFFFBFBFBFFFC0928FFFC0928FFF9C8C6FFF9C8C6FFF9C8C
+ 6FFF000000FF9C8C6FFF000000000000000000000000000000FF000000FF0000
+ 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+ 00FF000000FF9C8C6FFF00000000000000000000000000000000000000FF9C8C
+ 6FFF9C8C6FFF9C8C6FFF9C8C6FFF9C8C6FFF9C8C6FFF9C8C6FFF9C8C6FFF0000
+ 00FF9C8C6FFF9C8C6FFF00000000000000000000000000000000000000FFBFBF
+ BFFFFFFFFFFFC0928FFFF9EED9FF9C8C6FFFC0928FFF404040FF9C8C6FFF0000
+ 00FF9C8C6FFF9C8C6FFF00000000000000000000000000000000000000FFBFBF
+ BFFFFFFFFFFFBFBFBFFFF9EED9FFC0928FFFBFBFBFFF666666FFD1A78FFF0000
+ 00FF9C8C6FFF9C8C6FFF00000000000000000000000000000000000000FFBFBF
+ BFFFFFFFFFFFBFBFBFFFF9EED9FFC0928FFFBFBFBFFF666666FFD1A78FFF0000
+ 00FF9C8C6FFF9C8C6FFF00000000000000000000000000000000000000FFBFBF
+ BFFFFFFFFFFFBFBFBFFFF9EED9FFC0928FFFBFBFBFFF666666FFD1A78FFF0000
+ 00FF9C8C6FFF9C8C6FFF00000000000000000000000000000000000000FFBFBF
+ BFFFFFFFFFFFBFBFBFFFF9EED9FFC0928FFFBFBFBFFF666666FFD1A78FFF0000
+ 00FF9C8C6FFF9C8C6FFF00000000000000000000000000000000000000FFBFBF
+ BFFFFFFFFFFFBFBFBFFFF9EED9FFC0928FFFBFBFBFFF666666FFD1A78FFF0000
+ 00FF9C8C6FFF9C8C6FFF00000000000000000000000000000000000000FFBFBF
+ BFFFFFFFFFFFF9EED9FFBFBFBFFFBFBFBFFFC0928FFFC0928FFF666666FF0000
+ 00FF9C8C6FFF9C8C6FFF00000000000000000000000000000000000000FFBFBF
+ BFFFFFFFFFFFBFBFBFFFBFBFBFFFC0928FFFC0928FFF666666FF666666FF0000
+ 00FF9C8C6FFF9C8C6FFF00000000000000000000000000000000000000FF0000
+ 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+ 00FF9C8C6FFF9C8C6FFF00000000000000000000000000000000000000009C8C
+ 6FFF9C8C6FFF9C8C6FFF9C8C6FFF9C8C6FFF9C8C6FFF9C8C6FFF9C8C6FFF9C8C
+ 6FFF9C8C6FFF0000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 00000000000000000000000000000000000000000000000000FF000000FF0000
+ 00FF000000FF000000FF00000000000000000000000000000000000000000000
+ 000000000000000000000000000000000000336666FF6D9FC7FFCCCCFFFFE6E6
+ E6FFCCCCFFFFA6A6A6FF000000FF000000000000000000000000000000FF0000
+ 00FF000000000000000000000000336666FF4E7EA6FF6D9FC7FF6D9FC7FF0000
+ 00FF6D9FC7FF6D9FC7FF4E7EA6FF000000FF0000000000000000000000FF00FF
+ FFFF000000FF0000000000000000204A6EFF4E7EA6FF4E7EA6FF000000FF0000
+ 00FF6D9FC7FF57B3F7FF4E7EA6FF000000FF000000FF000000FF000000FF00FF
+ FFFF00FFFFFF000000FF000000004E7EA6FF57B3F7FF57B3F7FF57B3F7FF0000
+ 00FF57B3F7FF57B3F7FF0099FFFF000000FFD6FFFFFFD6FFFFFFD6FFFFFF00FF
+ FFFF00FFFFFF00FFFFFF000000FF4E7EA6FF57B3F7FF57B3F7FF57B3F7FF0000
+ 00FF58D2E8FF58D2E8FF57B3F7FF336666FF00FFFFFF00FFFFFF00FFFFFF00FF
+ FFFF00FFFFFF58D2E8FF000000FF4E7EA6FF57B3F7FF58D2E8FF99CCFFFF0000
+ 00FF58D2E8FF58D2E8FF57B3F7FF336666FF000000FF000000FF000000FF00FF
+ FFFF58D2E8FF000000FF9C8C6FFF4E7EA6FF6D9FC7FF58D2E8FF9EF0FFFF0000
+ 00FF9EF0FFFF9EF0FFFF58D2E8FF336666FF9C8C6FFF9C8C6FFF000000FF58D2
+ E8FF000000FF9C8C6FFF00000000000000004E7EA6FF6BD2B8FF58D2E8FF9EF0
+ FFFF9EF0FFFF6BD2B8FF336666FF000000000000000000000000000000FF0000
+ 00FF9C8C6FFF00000000000000000000000000000000336666FF4E7EA6FF6699
+ 99FF4E7EA6FF336666FF00000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 000000000000000000000000000000000000000000FF000000FF000000FF0000
+ 00FF000000FF00000000000000000000000000000000000000FF000000FF0000
+ 00000000000000000000000000003C696EFF8591BBFFC5CDEBFFD9DFF6FFC6D5
+ EFFF929FC7FF000000FF000000000000000000000000000000FF00FFFFFF0000
+ 00FF00000000000000003C696EFF3F5EA6FF6DA0DEFF82A0D9FF000000FF7AAF
+ D1FF6EA8D8FF4F7ABDFF000000FF000000FF000000FF000000FF00FFFFFF00FF
+ FFFF000000FF00000000335493FF3775C7FF3985D0FF000000FF000000FF489B
+ C5FF3C9BD3FF3281D0FF000000FFD6FFFFFFD6FFFFFFD6FFFFFF00FFFFFF00FF
+ FFFF00FFFFFF000000FF2860A2FF318EDCFF3C9EEAFF579FEAFF000000FF49B1
+ DDFF3AB1E9FF2D98E7FF000000FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FF
+ FFFF58D2E8FF000000FF3477B1FF3BA9EDFF55C3FFFF75C6FFFF000000FF64CC
+ E6FF55CDF4FF42B4F5FF437377FF000000FF000000FF000000FF00FFFFFF58D2
+ E8FF000000FF9C8C6FFF407FACFF4AB7F0FF65D7FFFF87DAFFFF000000FF78E0
+ EAFF68E2F7FF56C7F9FF437377FF9C8C6FFF9C8C6FFF000000FF58D2E8FF0000
+ 00FF9C8C6FFF000000004D7D99FF50ACD6FF74E3FCFF91EBFFFF000000FF80EE
+ ECFF7AF0F9FF66CCEDFF437377FF0000000000000000000000FF000000FF9C8C
+ 6FFF000000000000000000000000477383FF66BFCFFF7AE4ECFF84F2F7FF7CEB
+ EDFF6BCAD3FF3C696EFF00000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000003C696EFF447F83FF519192FF4A86
+ 87FF437377FF0000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000
+ }
+ end
+ object JvID3v21: TJvID3v2
+ Active = False
+ ProcessPictures = True
+ left = 40
+ top = 240
+ end
+end
diff --git a/components/jvcllaz/examples/JvID3v2/JvID3v2EditFormU.pas b/components/jvcllaz/examples/JvID3v2/JvID3v2EditFormU.pas
new file mode 100644
index 000000000..27ac141aa
--- /dev/null
+++ b/components/jvcllaz/examples/JvID3v2/JvID3v2EditFormU.pas
@@ -0,0 +1,720 @@
+{******************************************************************
+
+ JEDI-VCL Demo
+
+ Copyright (C) 2002 Project JEDI
+
+ Original author:
+
+ Contributor(s):
+
+ You may retrieve the latest version of this file at the JEDI-JVCL
+ home page, located at http://jvcl.delphi-jedi.org
+
+ The contents of this file are used with permission, 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_1Final.html
+
+ Software distributed under the License is distributed on an
+ "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ implied. See the License for the specific language governing
+ rights and limitations under the License.
+
+******************************************************************}
+
+unit JvID3v2EditFormU;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+ //Windows,
+ SysUtils, Classes, Graphics, Controls, Forms,
+ Dialogs, ComCtrls, ActnList, StdCtrls, ExtCtrls,
+ JvID3v2Base, JvId3v2; //, JvComponent, ImgList, ToolWin; //, JvComponentBase;
+
+type
+ TJvID3v2EditForm = class(TForm)
+ PageControl1: TPageControl;
+ lsbNavigator: TListBox;
+ ToolBar1: TToolBar;
+ tshWinampTags: TTabSheet;
+ tshLyrics: TTabSheet;
+ tshPictures: TTabSheet;
+ lblTitle: TLabel;
+ lblArtist: TLabel;
+ lblAlbum: TLabel;
+ lblYear: TLabel;
+ lblComposer: TLabel;
+ lblOrigArtist: TLabel;
+ lblCopyright: TLabel;
+ lblURL: TLabel;
+ lblEncodedBy: TLabel;
+ edtTitle: TEdit;
+ edtArtist: TEdit;
+ edtAlbum: TEdit;
+ edtYear: TEdit;
+ edtComposer: TEdit;
+ edtOrigArtist: TEdit;
+ edtCopyright: TEdit;
+ edtURL: TEdit;
+ edtEncodedBy: TEdit;
+ cmbGenre: TComboBox;
+ lblGenre: TLabel;
+ memComment: TMemo;
+ lblComment: TLabel;
+ acl16: TActionList;
+ iml16: TImageList;
+ actOK: TAction;
+ actCancel: TAction;
+ actRemove: TAction;
+ ToolButton1: TToolButton;
+ ToolButton2: TToolButton;
+ ToolButton3: TToolButton;
+ JvID3v21: TJvID3v2;
+ lblLanguage: TLabel;
+ cmbLanguage: TComboBox;
+ memLyrics: TMemo;
+ lblDescription: TLabel;
+ lblWriter: TLabel;
+ edtDescription: TEdit;
+ edtWriter: TEdit;
+ lsvPictures: TListView;
+ imgPicture: TImage;
+ actAddPicture: TAction;
+ actDeletePicture: TAction;
+ actSavePicture: TAction;
+ lblPictureName: TLabel;
+ lblPictureType: TLabel;
+ edtPictureName: TEdit;
+ cmbPictureType: TComboBox;
+ tshAllFrames: TTabSheet;
+ lsvAllFrames: TListView;
+ btnChange: TButton;
+ actChangePicture: TAction;
+ btnAdd: TButton;
+ btnDelete: TButton;
+ btnSave: TButton;
+ ToolButton4: TToolButton;
+ ToolButton5: TToolButton;
+ actCopyTov1: TAction;
+ actCopyFromv1: TAction;
+ procedure actOKExecute(Sender: TObject);
+ procedure actCancelExecute(Sender: TObject);
+ procedure actRemoveExecute(Sender: TObject);
+ procedure actAddPictureExecute(Sender: TObject);
+ procedure actDeletePictureExecute(Sender: TObject);
+ procedure actSavePictureExecute(Sender: TObject);
+ procedure lsvPicturesClick(Sender: TObject);
+ procedure lsbNavigatorClick(Sender: TObject);
+ procedure actChangePictureExecute(Sender: TObject);
+ procedure ItemSelected(Sender: TObject);
+ procedure actCopyTov1Execute(Sender: TObject);
+ procedure actCopyFromv1Execute(Sender: TObject);
+ procedure FormCreate(Sender: TObject);
+ procedure lsvAllFramesInfoTip(Sender: TObject; Item: TListItem;
+ var InfoTip: String);
+ private
+ FTagDeleted: Boolean;
+ protected
+ procedure Init;
+ procedure Final;
+
+ procedure InitAllFramesTab;
+
+ procedure TagToCtrls;
+ procedure CtrlsToTag;
+ procedure FillPictureTypes(Strings: TStrings);
+
+ class function Instance: TJvID3v2EditForm;
+ public
+ class function Execute(const AFileName: string): Boolean;
+ end;
+
+implementation
+
+uses
+ ExtDlgs,
+ JvID3v2Types;
+
+{$R *.lfm}
+
+var
+ CFrameDescriptions: array[TJvID3FrameID] of string = (
+ 'Error', {fiErrorFrame}
+ 'Padding', {fiPaddingFrame}
+ 'No known frame', {fiNoFrame}
+ 'Audio encryption', {fiAudioCrypto}
+ 'Attached picture', {fiPicture}
+ 'Audio seek point index', {fiAudioSeekPoint}
+ 'Comments', {fiComment}
+ 'Commercial frame', {fiCommercial}
+ 'Encryption method registration', {fiCryptoReg}
+ 'Equalisation (2)', {fiEqualization2}
+ 'Equalization', {fiEqualization}
+ 'Event timing codes', {fiEventTiming}
+ 'General encapsulated object', {fiGeneralObject}
+ 'Group identification registration', {fiGroupingReg}
+ 'Involved people list', {fiInvolvedPeople}
+ 'Linked information', {fiLinkedInfo}
+ 'Music CD identifier', {fiCDID}
+ 'MPEG location lookup table', {fiMPEGLookup}
+ 'Ownership frame', {fiOwnership}
+ 'Private frame', {fiPrivate}
+ 'Play counter', {fiPlayCounter}
+ 'Popularimeter', {fiPopularimeter}
+ 'Position synchronisation frame', {fiPositionsync}
+ 'Recommended buffer size', {fiBufferSize}
+ 'Relative volume adjustment (2)', {fiVolumeAdj2}
+ 'Relative volume adjustment', {fiVolumeAdj}
+ 'Reverb', {fiReverb}
+ 'Seek frame', {fiSeekFrame}
+ 'Signature frame', {fiSignature}
+ 'Synchronized lyric/text', {fiSyncedLyrics}
+ 'Synchronized tempo codes', {fiSyncedTempo}
+ 'Album/Movie/Show title', {fiAlbum}
+ 'BPM (beats per minute)', {fiBPM}
+ 'Composer', {fiComposer}
+ 'Content type', {fiContentType}
+ 'Copyright message', {fiCopyright}
+ 'Date', {fiDate}
+ 'Encoding time', {fiEncodingTime}
+ 'Playlist delay', {fiPlaylistDelay}
+ 'Original release time', {fiOrigReleaseTime}
+ 'Recording time', {fiRecordingTime}
+ 'Release time', {fiReleaseTime}
+ 'Tagging time', {fiTaggingTime}
+ 'Involved people list', {fiInvolvedPeople2}
+ 'Encoded by', {fiEncodedBy}
+ 'Lyricist/Text writer', {fiLyricist}
+ 'File type', {fiFileType}
+ 'Time', {fiTime}
+ 'Content group description', {fiContentGroup}
+ 'Title/songname/content description', {fiTitle}
+ 'Subtitle/Description refinement', {fiSubTitle}
+ 'Initial key', {fiInitialKey}
+ 'Language(s)', {fiLanguage}
+ 'Length', {fiSongLen}
+ 'Musician credits list', {fiMusicianCreditList}
+ 'Media type', {fiMediaType}
+ 'Mood', {fiMood}
+ 'Original album/movie/show title', {fiOrigAlbum}
+ 'Original filename', {fiOrigFileName}
+ 'Original lyricist(s)/text writer(s)', {fiOrigLyricist}
+ 'Original artist(s)/performer(s)', {fiOrigArtist}
+ 'Original release year', {fiOrigYear}
+ 'File owner/licensee', {fiFileOwner}
+ 'Lead performer(s)/Soloist(s)', {fiLeadArtist}
+ 'Band/orchestra/accompaniment', {fiBand}
+ 'Conductor/performer refinement', {fiConductor}
+ 'Interpreted, remixed, or otherwise modified by', {fiMixArtist}
+ 'Part of a set', {fiPartInSet}
+ 'Produced notice', {fiProducedNotice}
+ 'Publisher', {fiPublisher}
+ 'Track number/Position in set', {fiTrackNum}
+ 'Recording dates', {fiRecordingDates}
+ 'Internet radio station name', {fiNetRadioStation}
+ 'Internet radio station owner', {fiNetRadioOwner}
+ 'Size', {fiSize}
+ 'Album sort order', {fiAlbumSortOrder}
+ 'Performer sort order', {fiPerformerSortOrder}
+ 'Title sort order', {fiTitleSortOrder}
+ 'ISRC (international standard recording code)', {fiISRC}
+ 'Software/Hardware and settings used for encoding', {fiEncoderSettings}
+ 'Set subtitle', {fiSetSubTitle}
+ 'User defined text information', {fiUserText}
+ 'Year', {fiYear}
+ 'Unique file identifier', {fiUniqueFileID}
+ 'Terms of use', {fiTermsOfUse}
+ 'Unsynchronized lyric/text transcription', {fiUnsyncedLyrics}
+ 'Commercial information', {fiWWWCommercialInfo}
+ 'Copyright/Legal information', {fiWWWCopyright}
+ 'Official audio file webpage', {fiWWWAudioFile}
+ 'Official artist/performer webpage', {fiWWWArtist}
+ 'Official audio source webpage', {fiWWWAudioSource}
+ 'Official internet radio station homepage', {fiWWWRadioPage}
+ 'Payment', {fiWWWPayment}
+ 'Official publisher webpage', {fiWWWPublisher}
+ 'User defined URL link', {fiWWWUser}
+ 'Encrypted meta frame', {fiMetaCrypto}
+ 'Compressed meta frame' {fiMetaCompression}
+ );
+
+ CPictureTypeStr: array[TJvID3PictureType] of string = (
+ 'Other',
+ '32x32 pixels ''file icon'' (PNG only)',
+ 'Other file icon',
+ 'Cover (front)',
+ 'Cover (back)',
+ 'Leaflet page',
+ 'Media (e.g. lable side of CD)',
+ 'Lead artist/lead performer/soloist',
+ 'Artist/performer',
+ 'Conductor',
+ 'Band/Orchestra',
+ 'Composer',
+ 'Lyricist/text writer',
+ 'Recording Location',
+ 'During recording',
+ 'During performance',
+ 'Movie/video screen capture',
+ 'A bright coloured fish',
+ 'Illustration',
+ 'Band/artist logotype',
+ 'Publisher/Studio logotype'
+ );
+
+procedure SetPictureListItemTo(ListItem: TListItem; Frame: TJvID3PictureFrame);
+begin
+ with ListItem, Frame do
+ begin
+ Caption := Description;
+ while SubItems.Count < 3 do
+ SubItems.Add('');
+ SubItems[0] := CPictureTypeStr[PictureType]; //Type
+ SubItems[1] := string(MIMEType); //Format
+ SubItems[2] := IntToStr(DataSize); //Size
+ Data := Frame;
+ end;
+end;
+
+procedure TJvID3v2EditForm.FormCreate(Sender: TObject);
+begin
+ FillPictureTypes(cmbPictureType.Items);
+ ISO_639_2Names(cmbLanguage.Items);
+ ID3_Genres(cmbGenre.Items);
+end;
+
+procedure TJvID3v2EditForm.actOKExecute(Sender: TObject);
+var
+ HasTag: Boolean;
+ Version: TJvID3Version;
+ lCursor: TCursor;
+begin
+ lCursor := Screen.Cursor;
+ Screen.Cursor := crHourGlass;
+ try
+ CtrlsToTag;
+ JvID3v21.Frames.RemoveEmptyFrames;
+
+ HasTag := True;
+ if JvID3v21.FrameCount = 0 then
+ GetID3v2Version(JvID3v21.FileName, HasTag, Version);
+
+ if HasTag then
+ JvID3v21.Commit;
+ ModalResult := mrOk;
+ finally
+ Screen.Cursor := lCursor;
+ end;
+end;
+
+procedure TJvID3v2EditForm.actCancelExecute(Sender: TObject);
+begin
+ ModalResult := mrCancel;
+end;
+
+procedure TJvID3v2EditForm.actRemoveExecute(Sender: TObject);
+var
+ lCursor: TCursor;
+begin
+ if MessageDlg('Remove tag?', mtConfirmation, mbOKCancel, 0) <> mrOk then
+ Exit;
+
+ lCursor := Screen.Cursor;
+ Screen.Cursor := crHourGlass;
+ try
+ JvID3v21.Erase;
+ FTagDeleted := True;
+ TagToCtrls;
+ finally
+ Screen.Cursor := lCursor;
+ end;
+end;
+
+class function TJvID3v2EditForm.Execute(const AFileName: string): Boolean;
+begin
+ with TJvID3v2EditForm.Instance do
+ try
+ JvID3v21.FileName := AFileName;
+ Init;
+ try
+ Result := (ShowModal = mrOk) or FTagDeleted;
+ finally
+ Final;
+ end;
+ finally
+ Hide;
+ end;
+end;
+
+procedure TJvID3v2EditForm.Init;
+begin
+ Caption := Format('Edit ''%s''', [ExtractFileName(JvID3v21.FileName)]);
+
+ JvID3v21.Open;
+
+ TagToCtrls;
+
+ imgPicture.Picture.Assign(nil);
+
+ lsbNavigator.ItemIndex := 0;
+ PageControl1.ActivePage := tshWinampTags;
+end;
+
+function ChangeYear(const ADateTime: TDateTime; const NewYear: Word): TDateTime;
+var
+ OldYear, Month, Day: Word;
+begin
+ DecodeDate(ADateTime, OldYear, Month, Day);
+ Result := EncodeDate(NewYear, Month, Day);
+end;
+
+procedure TJvID3v2EditForm.CtrlsToTag;
+
+ //procedure SetFirstOfList(Strings: {$IFDEF COMPILER12_UP}TStrings{$ELSE}TWideStrings{$ENDIF COMPILER12_UP}; const S: string);
+ procedure SetFirstOfList(Strings: TStrings; const S: String);
+ begin
+ if Strings.Count > 0 then
+ Strings[0] := S
+ else
+ Strings.Add(S);
+ end;
+
+begin
+ { WinAmp tags }
+
+ { WinAmp treats some tags as single line tags; mimic this behaviour by
+ using function SetFirstOfList }
+ JvID3v21.Texts.Title := edtTitle.Text;
+ SetFirstOfList(JvID3v21.Texts.LeadArtist, edtArtist.Text);
+ JvID3v21.Texts.Album := edtAlbum.Text;
+ { The 'year' tag is replaced by the 'recordingtime' tag in v2.4 }
+ if JvID3v21.WriteVersion = ive2_4 then
+ JvID3v21.Texts.RecordingTime := ChangeYear(JvID3v21.Texts.RecordingTime, StrToIntDef(edtYear.Text, 0))
+ else
+ JvID3v21.Texts.Year := StrToIntDef(edtYear.Text, 0);
+ SetFirstOfList(JvID3v21.Texts.ContentType, NiceGenreToGenre(cmbGenre.Text));
+ { Note that WinAmp doesn't care about other properties than Text of TJvID3ContentFrame }
+ TJvID3ContentFrame.FindOrCreate(JvID3v21, fiComment).Text := memComment.Lines.Text;
+ SetFirstOfList(JvID3v21.Texts.Composer, edtComposer.Text);
+ SetFirstOfList(JvID3v21.Texts.OrigArtist, edtOrigArtist.Text);
+ JvID3v21.Texts.Copyright := edtCopyright.Text;
+ { Note that WinAmp doesn't care about other properties than URL of TJvID3URLUserFrame }
+ TJvID3URLUserFrame.FindOrCreate(JvID3v21, 0).URL := AnsiString(edtURL.Text);
+ JvID3v21.Texts.EncodedBy := edtEncodedBy.Text;
+
+ { Lyrics }
+ with TJvID3ContentFrame.FindOrCreate(JvID3v21, fiUnsyncedLyrics) do
+ begin
+ Language := ISO_639_2NameToCode(cmbLanguage.Text);
+ Text := memLyrics.Lines.Text;
+ Description := edtDescription.Text;
+ end;
+ SetFirstOfList(JvID3v21.Texts.Lyricist, edtWriter.Text);
+end;
+
+function YearOf(const ADateTime: TDateTime): Word;
+var
+ D1, D2: Word;
+begin
+ DecodeDate(ADateTime, Result, D1, D2);
+end;
+
+procedure TJvID3v2EditForm.TagToCtrls;
+
+ //function GetFirstOfList(Strings: {$IFDEF COMPILER12_UP}TStrings{$ELSE}TWideStrings{$ENDIF COMPILER12_UP}): string;
+ function GetFirstOfList(Strings: TStrings): String;
+ begin
+ if Strings.Count > 0 then
+ Result := Strings[0]
+ else
+ Result := '';
+ end;
+
+var
+ Frame: TJvID3Frame;
+begin
+ { Determine which frames are in the tag before calls to JvID3v21.Texts.xxx and
+ FindOrCreate because those functions might create frames. }
+ InitAllFramesTab;
+
+ { WinAmp tags }
+
+ { WinAmp treats some tags as single line tags; mimic this behaviour by
+ using function GetFirstOfList }
+ edtTitle.Text := JvID3v21.Texts.Title;
+ edtArtist.Text := GetFirstOfList(JvID3v21.Texts.LeadArtist);
+ edtAlbum.Text := JvID3v21.Texts.Album;
+ { The 'year' tag is replaced by the 'recordingtime' tag in v2.4 }
+ if JvID3v21.Version = ive2_4 then
+ edtYear.Text := IntToStr(YearOf(JvID3v21.Texts.RecordingTime))
+ else
+ edtYear.Text := IntToStr(JvID3v21.Texts.Year);
+ cmbGenre.Text := GenreToNiceGenre(GetFirstOfList(JvID3v21.Texts.ContentType));
+ { Note that WinAmp doesn't care about other properties than Text of TJvID3ContentFrame }
+ memComment.Lines.Text := TJvID3ContentFrame.FindOrCreate(JvID3v21, fiComment).Text;
+ edtComposer.Text := GetFirstOfList(JvID3v21.Texts.Composer);
+ edtOrigArtist.Text := GetFirstOfList(JvID3v21.Texts.OrigArtist);
+ edtCopyright.Text := JvID3v21.Texts.Copyright;
+ { Note that WinAmp doesn't care about other properties than URL of TJvID3URLUserFrame }
+ edtURL.Text := string(TJvID3URLUserFrame.FindOrCreate(JvID3v21, 0).URL);
+ edtEncodedBy.Text := JvID3v21.Texts.EncodedBy;
+
+ { Lyrics }
+ with TJvID3ContentFrame.FindOrCreate(JvID3v21, fiUnsyncedLyrics) do
+ begin
+ cmbLanguage.ItemIndex := cmbLanguage.Items.IndexOf(string(ISO_639_2CodeToName(Language)));
+ memLyrics.Lines.Text := Text;
+ edtDescription.Text := Description;
+ end;
+ edtWriter.Text := GetFirstOfList(JvID3v21.Texts.Lyricist);
+
+ { Pictures }
+ lsvPictures.Items.BeginUpdate;
+ try
+ lsvPictures.Items.Clear;
+ if JvID3v21.FindFirstFrame(fiPicture, Frame) then
+ repeat
+ if Frame is TJvID3PictureFrame then
+ SetPictureListItemTo(lsvPictures.Items.Add, TJvID3PictureFrame(Frame));
+ until not JvID3v21.FindNextFrame(fiPicture, Frame);
+ finally
+ lsvPictures.Items.EndUpdate;
+ end;
+end;
+
+procedure TJvID3v2EditForm.actAddPictureExecute(Sender: TObject);
+var
+ Frame: TJvID3PictureFrame;
+begin
+ if cmbPictureType.ItemIndex < 0 then
+ begin
+ MessageDlg('Select a picture type', mtError, [mbOK], 0);
+ FocusControl(cmbPictureType);
+ Exit;
+ end;
+
+ with TOpenPictureDialog.Create(Application) do
+ try
+ if not Execute then
+ Exit;
+
+ Frame := TJvID3PictureFrame(JvID3v21.AddFrame(fiPicture));
+ with Frame do
+ begin
+ with cmbPictureType do
+ PictureType := TJvID3PictureType(Items.Objects[ItemIndex]);
+ Description := edtPictureName.Text;
+ MIMEType := AnsiString(ExtToMIMEType(ExtractFileExt(FileName)));
+ LoadFromFile(FileName);
+
+ lsvPictures.Items.BeginUpdate;
+ try
+ SetPictureListItemTo(lsvPictures.Items.Add, Frame);
+ finally
+ lsvPictures.Items.EndUpdate;
+ end;
+ end;
+ finally
+ Free;
+ end;
+end;
+
+procedure TJvID3v2EditForm.actDeletePictureExecute(Sender: TObject);
+begin
+ if not Assigned(lsvPictures.Selected) then
+ Exit;
+
+ JvID3v21.Frames.Remove(TJvID3Frame(lsvPictures.Selected.Data));
+ lsvPictures.Items.Delete(lsvPictures.Selected.Index);
+ imgPicture.Picture.Assign(nil);
+end;
+
+procedure TJvID3v2EditForm.actSavePictureExecute(Sender: TObject);
+var
+ Frame: TJvID3PictureFrame;
+begin
+ if not Assigned(lsvPictures.Selected) then
+ Exit;
+
+ Frame := TJvID3PictureFrame(lsvPictures.Selected.Data);
+
+ if Assigned(Frame) and (Frame.DataSize > 0) and (Frame.MIMEType <> '-->') then
+ with TSavePictureDialog.Create(Application) do
+ try
+ if Execute then
+ Frame.SaveToFile(FileName);
+ finally
+ Free;
+ end;
+end;
+
+procedure TJvID3v2EditForm.lsvPicturesClick(Sender: TObject);
+var
+ Frame: TJvID3PictureFrame;
+begin
+ if Assigned(lsvPictures.Selected) then
+ Frame := TJvID3PictureFrame(lsvPictures.Selected.Data)
+ else
+ Frame := nil;
+
+ if Assigned(Frame) then
+ begin
+ edtPictureName.Text := Frame.Description;
+ with cmbPictureType do
+ ItemIndex := Items.IndexOfObject(TObject(Frame.PictureType));
+ end;
+
+ imgPicture.Picture.Assign(Frame);
+end;
+
+procedure TJvID3v2EditForm.lsbNavigatorClick(Sender: TObject);
+begin
+ case lsbNavigator.ItemIndex of
+ 0: PageControl1.ActivePage := tshWinampTags;
+ 1: PageControl1.ActivePage := tshLyrics;
+ 2: PageControl1.ActivePage := tshPictures;
+ 3: PageControl1.ActivePage := tshAllFrames;
+ end;
+end;
+
+procedure TJvID3v2EditForm.FillPictureTypes(Strings: TStrings);
+var
+ PictureType: TJvID3PictureType;
+begin
+ Strings.BeginUpdate;
+ try
+ Strings.Clear;
+ for PictureType := Low(TJvID3PictureType) to High(TJvID3PictureType) do
+ Strings.AddObject(CPictureTypeStr[PictureType], TObject(PictureType));
+ finally
+ Strings.EndUpdate;
+ end;
+end;
+
+procedure TJvID3v2EditForm.InitAllFramesTab;
+var
+ I: Integer;
+ ListItem: TListItem;
+begin
+ lsvAllFrames.Items.BeginUpdate;
+ try
+ lsvAllFrames.Items.Clear;
+ for I := 0 to JvID3v21.FrameCount - 1 do
+ with JvID3v21.Frames[I] do
+ begin
+ ListItem := lsvAllFrames.Items.Add;
+ ListItem.Caption := string(FrameName);
+ if ClassType <> TJvID3SkipFrame then
+ ListItem.SubItems.Add('Yes')
+ else
+ ListItem.SubItems.Add('No');
+ ListItem.SubItems.Add(CFrameDescriptions[FrameID]);
+ ListItem.Data := JvID3v21.Frames[I];
+ end;
+ finally
+ lsvAllFrames.Items.EndUpdate;
+ end;
+end;
+
+procedure TJvID3v2EditForm.actChangePictureExecute(Sender: TObject);
+var
+ Frame: TJvID3PictureFrame;
+begin
+ if not Assigned(lsvPictures.Selected) then
+ Exit;
+
+ if cmbPictureType.ItemIndex < 0 then
+ begin
+ MessageDlg('Select a picture type', mtError, [mbOK], 0);
+ FocusControl(cmbPictureType);
+ Exit;
+ end;
+
+ Frame := TJvID3PictureFrame(lsvPictures.Selected.Data);
+
+ with Frame do
+ begin
+ with cmbPictureType do
+ PictureType := TJvID3PictureType(Items.Objects[ItemIndex]);
+ Description := edtPictureName.Text;
+
+ lsvPictures.Items.BeginUpdate;
+ try
+ SetPictureListItemTo(lsvPictures.Selected, Frame);
+ finally
+ lsvPictures.Items.EndUpdate;
+ end;
+ end;
+end;
+
+procedure TJvID3v2EditForm.ItemSelected(Sender: TObject);
+begin
+ if Sender is TAction then
+ TAction(Sender).Enabled := Assigned(lsvPictures.Selected);
+end;
+
+procedure TJvID3v2EditForm.actCopyTov1Execute(Sender: TObject);
+begin
+ if not JvID3v21.CopyToID3v1 then
+ ShowMessage('Error');
+end;
+
+procedure TJvID3v2EditForm.actCopyFromv1Execute(Sender: TObject);
+begin
+ if JvID3v21.CopyFromID3v1 then
+ TagToCtrls
+ else
+ ShowMessage('Error');
+end;
+
+var
+ GInstance: TJvID3v2EditForm = nil;
+
+class function TJvID3v2EditForm.Instance: TJvID3v2EditForm;
+begin
+ if not Assigned(GInstance) then
+ GInstance := TJvID3v2EditForm.Create(Application);
+
+ Result := GInstance;
+end;
+
+procedure TJvID3v2EditForm.Final;
+begin
+ JvID3v21.Close;
+end;
+
+procedure TJvID3v2EditForm.lsvAllFramesInfoTip(Sender: TObject;
+ Item: TListItem; var InfoTip: String);
+var
+ Frame: TJvID3Frame;
+ S: string;
+begin
+ Frame := TJvID3Frame(Item.Data);
+ if Frame is TJvID3TextFrame then
+ InfoTip := TJvID3TextFrame(Frame).Text
+ else if Frame is TJvID3NumberFrame then
+ InfoTip := IntToStr(TJvID3NumberFrame(Frame).Value)
+ else if Frame is TJvID3UserFrame then
+ with Frame as TJvID3UserFrame do
+ InfoTip := Format('%s: %s', [Description, Value])
+ else if Frame is TJvID3PictureFrame then
+ with Frame as TJvID3PictureFrame do
+ InfoTIp := Format('%s (%s) %d bytes', [Description, MIMEType, DataSize])
+ else if Frame is TJvID3TimestampFrame then
+ InfoTip := DateTimeToStr(TJvID3TimestampFrame(Frame).Value)
+ else if Frame is TJvID3ContentFrame then
+ InfoTip := TJvID3ContentFrame(Frame).Text
+ else if Frame is TJvID3SimpleListFrame then
+ begin
+ S := TJvID3SimpleListFrame(Frame).List.GetText;
+ Delete(S, Length(S) - 1, 2);
+ InfoTip := S;
+ end;
+end;
+
+end.
diff --git a/components/jvcllaz/examples/JvID3v2/JvID3v2MainFormU.lfm b/components/jvcllaz/examples/JvID3v2/JvID3v2MainFormU.lfm
new file mode 100644
index 000000000..2f18429ae
--- /dev/null
+++ b/components/jvcllaz/examples/JvID3v2/JvID3v2MainFormU.lfm
@@ -0,0 +1,192 @@
+object JvID3v2MainForm: TJvID3v2MainForm
+ Left = 442
+ Height = 392
+ Top = 277
+ Width = 557
+ Caption = 'JvID3v2 example'
+ ClientHeight = 392
+ ClientWidth = 557
+ Color = clBtnFace
+ Constraints.MinHeight = 150
+ Constraints.MinWidth = 200
+ DefaultMonitor = dmDesktop
+ Font.Color = clWindowText
+ Icon.Data = {
+ 3E01000000000100010010101000010010002801000016000000280000001000
+ 0000200000000100040000000000C00000000000000000000000000000000000
+ 000000000000000080000080000000808000800000008000800080800000C0C0
+ C000808080000000FF0000FF000000FFFF00FF000000FF00FF00FFFF0000FFFF
+ FF00000000000000000000000BBBB0000000000BB000BB000000000BB0000B00
+ 0000000BBB000BB00000000BBB000BB00000000000000BB00000000000000BB0
+ 0000000000000BB00000000000000BB00000000000000BB00000000000000BB0
+ 0000000000000BB0000000000000BBBB00000000000BBBBBB000000000000000
+ 0000FFFF0000F87F0000E73F0000E7BF0000E39F0000E39F0000FF9F0000FF9F
+ 0000FF9F0000FF9F0000FF9F0000FF9F0000FF9F0000FF0F0000FE070000FFFF
+ 0000
+ }
+ Position = poScreenCenter
+ LCLVersion = '1.9.0.0'
+ Scaled = False
+ object Splitter1: TSplitter
+ Left = 185
+ Height = 392
+ Top = 0
+ Width = 5
+ end
+ object ListView1: TListView
+ Left = 190
+ Height = 392
+ Top = 0
+ Width = 367
+ Align = alClient
+ Columns = <
+ item
+ Caption = 'Type'
+ end
+ item
+ Caption = 'File name'
+ Width = 300
+ end>
+ ReadOnly = True
+ RowSelect = True
+ TabOrder = 0
+ ViewStyle = vsReport
+ OnDblClick = ListView1DblClick
+ end
+ object Panel1: TPanel
+ Left = 0
+ Height = 392
+ Top = 0
+ Width = 185
+ Align = alLeft
+ ClientHeight = 392
+ ClientWidth = 185
+ TabOrder = 1
+ object ShellTreeView: TShellTreeView
+ Left = 1
+ Height = 390
+ Top = 1
+ Width = 183
+ Align = alClient
+ FileSortType = fstNone
+ Images = ImageList1
+ ReadOnly = True
+ TabOrder = 0
+ OnChange = ShellTreeViewChange
+ OnGetImageIndex = ShellTreeViewGetImageIndex
+ OnGetSelectedIndex = ShellTreeViewGetSelectedIndex
+ OnSelectionChanged = ShellTreeViewSelectionChanged
+ Options = [tvoAutoItemHeight, tvoHideSelection, tvoKeepCollapsedNodes, tvoReadOnly, tvoShowButtons, tvoShowLines, tvoShowRoot, tvoToolTips, tvoThemedDraw]
+ ObjectTypes = [otFolders]
+ end
+ end
+ object JvID3v21: TJvID3v2
+ Active = False
+ ProcessPictures = True
+ left = 320
+ top = 80
+ end
+ object ImageList1: TImageList
+ left = 320
+ top = 148
+ Bitmap = {
+ 4C69030000001000000010000000FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF
+ FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF
+ FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF
+ FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF
+ FF00FFFFFF00FFFFFF00FFFFFF009F9D9B009D9A98009E9C9A009E9C9AFFA5A3
+ A1FFA5A3A1FFA5A3A1FFA5A3A1FFA5A3A1FFA5A3A1FFA5A3A1FFA5A3A1FF9E9C
+ 9AFF9E9C9A009D9A98009F9D9B009F9D9B009B989600999795FFE4E3E2FFC6C4
+ C2FFC6C4C2FFC6C4C2FFC6C4C2FFC6C4C2FFC6C4C2FFC6C4C2FFC6C4C2FFE4E3
+ E2FF999795FF9B9896009F9D9B009D9B9900969390FFE5E4E3FFC7C4C2FFC7C4
+ C2FFC7C5C3FFC7C5C3FFC7C5C3FFC7C5C3FFC7C5C3FFC7C5C3FFC7C4C2FFC7C4
+ C2FFE5E4E3FF969390FF9D9B99009A9896AAEFEEEFFFEBEAEAFFEAE9EAFFEAE9
+ EAFFEAE9EAFFEAE9EAFFEAE9EAFFEAE9EAFFEAE9EAFFEAE9EAFFEAE9EAFFEAE9
+ EAFFEBEAEAFFEFEEEFFF9A9896AA969492FFE3E1E0FF9D9997FF9D9997FF9C99
+ 97FF9C9997FF9C9997FF9C9997FF9C9997FF9C9997FF9C9997FF9C9997FF9D99
+ 97FF9D9997FFE3E1E0FF969492FF93918FFFDCDBD9FFA6A3A0FFE6E4E4FFE5E3
+ E4FFE5E4E4FFE6E4E4FFE6E4E4FFE6E4E4FFE6E4E4FFE5E4E4FFE5E3E4FFE6E4
+ E4FFA6A3A0FFDCDBD9FF93918FFF908E8CFFD7D5D4FFAEACAAFFE1DFE0FFB0AD
+ ABFFB1AEACFFB2AEACFFB2AEACFFB2AEACFFB2AEACFFB1AEACFFB0ADABFFE1DF
+ E0FFAEACAAFFD7D5D4FF908E8CFF8D8A88FFD4D2D1FFB7B4B2FFD2D0D0FFC3C1
+ BFFFC4C2C0FFC4C2C0FFC4C2C0FFC4C2C0FFC4C2C0FFC4C2C0FFC3C1BFFFD2D0
+ D0FFB7B4B2FFD4D2D1FF8D8A88FF8A8785FFD2D0CFFFC1BFBDFFAEACAAFFAFAD
+ ABFFAFADABFFAFADABFFAFADABFFAFADABFFAFADABFFAFADABFFAFADABFFAEAC
+ AAFFC1BFBDFFD2D0CFFF8A8785FF888583FFD6D4D2FFCECCCAFFCECCCAFFCECC
+ CAFFCECCCAFFCECCCAFFCECCCAFFCECCCAFFCECCCAFFCECCCAFFCECCCAFFCECC
+ CAFFCECCCAFFD6D4D2FF888583FF7D7A78C084817FFF83807EFF83807EFF8380
+ 7EFF83807EFF83807EFF83807EFF83807EFF83807EFF83807EFF83807EFF8380
+ 7EFF83807EFF84817FFF7D7A78C0000000230000003300000033000000330000
+ 0033000000330000003300000033000000330000003300000033000000330000
+ 0033000000330000003300000023FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF
+ FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF
+ FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF
+ FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF
+ FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF
+ FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF
+ FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF
+ FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF
+ FF00FFFFFF00FFFFFF00FFFFFF004398D2B03D94D0FF3A92CFFF3A92CFFF3D94
+ D0FF4197D1D24398D2004498D2004498D2004498D2004498D2004499D2004499
+ D300459AD300469AD300469AD3003D94D0FFDCFCFFFFD8F7FFFFD8F7FFFFDBFA
+ FFFF358ECDFF3991CEFF3A92CFFF3A92CFFF3A92CFFF3A92CFFF3B92CFFF3D94
+ D0FF4398D2D7469AD300469AD3003B92CFFFD5F7FFFF60D1F9FF61D0F8FFB4EB
+ FDFFD9F6FFFFDAF8FFFFDAF8FFFFDBF9FFFFDCFAFFFFDCFAFFFFDCFBFFFFE0FF
+ FFFF3E95D0FF4599D333469AD3003B92CFFFCAF6FFFF69D5F9FF6CD5F9FF6BD5
+ F9FF69D5F9FF69D5FAFF6AD7FBFF68D4FAFF5EC7F1FF5EC7F2FF5DC8F2FFB4E3
+ F8FF3D94D0FF3F8FC669469AD3003C92CFFFC0F3FFFF71DAFBFF74DBFBFF75DB
+ FCFF75DBFCFF76DCFCFF73DAFAFF449CD4FF378CCBFF368CCBFF358CCCFF348D
+ CCFF3890CEFF3D94D0FF4398D2EB3D92CFFFB9F4FFFF73DBFBFF6BCCF2FF6CCD
+ F3FF6CCEF3FF6DCEF3FF479CD4FF56BAE9FFDAF8FFFFD7F6FFFFD6F6FFFFD5F6
+ FFFFD5F7FFFFDBFCFFFF3E94D0FF3E94D0FFABF0FFFF449DD6FF368CCBFF368C
+ CBFF368CCBFF378BCBFF5CBEEAFF6FD9FBFF6AD6FAFF68D5F9FF67D4F9FF66D4
+ F9FF82DEFCFFAAE0F6FF3885BCB94095D0FF8AD7F5FF44A1D8FFDDFDFFFFDAFA
+ FFFFDBFAFFFFDEFAFFFF74DCFCFF76DBFAFF75DAFAFF74DAFAFF74DAFAFF72D9
+ FAFFA1E8FFFF7CBFE6FF306F9C5E4296D1FF6BBEE8FF6DBDE6FFBBF2FFFF75DE
+ FDFF77DEFCFF78DEFCFF7BDFFCFF7DDFFCFF7DDFFCFF7DDFFCFF7CDFFCFF80E0
+ FDFFADF0FFFF4D9DD3FF0000000E4398D2FF4FA6D9FF8EDAF5FFA2EEFFFF82E5
+ FEFF84E5FEFF84E5FEFF85E6FEFF85E6FEFF85E6FEFF85E6FEFF84E6FEFF96EB
+ FFFF8CD8F5FF3985BCB84499D2004499D2FF3F94D0FFABFBFFFF9BF3FFFF92F1
+ FFFF93F1FFFF93F1FFFF93F1FFFF93F1FFFF93F1FFFF93F1FFFF93F1FFFFA6F8
+ FFFF65B8E3FF31709D5F469AD3004598D1F24398D2FF4094D0FF3E92CFFF3E92
+ CEFF3F92CEFF3F92CEFF3F92CEFF3F92CEFF3F92CEFF3F92CEFF3F92CEFF3F93
+ CFFF4194CEF00000000E469AD300000000300000003300000033000000330000
+ 0033000000330000003300000033000000330000003300000033000000330000
+ 00330000002F0000000000000000FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF
+ FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF
+ FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF
+ FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF
+ FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF
+ FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF
+ FF00FFFFFF00FFFFFF00FFFFFF00B9841AB0B78012FFB67E0EFFB67E0EFFB780
+ 11FFB98318D2BA851C00BA851C00BA851C00BA851C00BA851C00BA851C00BA86
+ 1D00BB871E00FFFFFF00BB871F00B78012FFF7F5EDFFF5F1E5FFF5F0E5FFF6F3
+ EAFFB47A06FFB67D0CFFB67E0EFFB67E0EFFB67E0EFFB67E0EFFB67E0FFFB780
+ 12FFB9841AD7FFFFFF00BB871F00B67E0FFFF5F1E4FFD1B87DFFD1B97DFFE9DF
+ C4FFF5F1E4FFF6F3E7FFF6F3E8FFF6F4EAFFF7F5EBFFF7F5ECFFF7F5ECFFF9FA
+ F4FFB88114FFFFFFFF00BB871F00B67E0EFFF2EBDCFFD5BE87FFD6BE88FFD6BE
+ 87FFD5BD87FFD5BE88FFD6C08AFFD5BC86FFCFAF6CFFCFB06DFFCFAF6DFFE7D9
+ B8FFB78012FFFFFFFF00BB871E00B67E0FFFF1E9D7FFD9C490FFDAC693FFDAC6
+ 94FFDAC694FFDBC796FFD9C591FFBC8821FFB47905FFB37904FFB37803FFB379
+ 04FFB57C0AFFB78012FFB9841AEBB77F10FFEFE8D5FFDAC594FFD3B77AFFD4B8
+ 7CFFD4B87CFFD5B97EFFBC8A23FFC8A357FFF6F2E7FFF5F0E3FFF4F0E2FFF4EF
+ E2FFF4F0E4FFF7F6EEFFB88113FFB78012FFEBE1C9FFBB8822FFB37904FFB378
+ 03FFB37803FFB37803FFCBA85DFFD8C28EFFD6BF88FFD5BD85FFD5BD85FFD4BD
+ 84FFDDC99DFFE6D4B1FFA57310B9B88215FFDEC796FFBD8D29FFF9F6EEFFF7F3
+ E9FFF7F3E9FFF8F4EAFFDBC695FFDBC594FFDBC592FFDBC492FFDBC491FFDAC3
+ 90FFE7DABAFFD1B16EFF8961105EB98318FFD0AC64FFCEAB64FFEFE6D2FFDCC9
+ 98FFDCC999FFDDC99AFFDECB9CFFDECB9DFFDECB9DFFDECB9DFFDECB9DFFDFCC
+ A1FFECE1C9FFBC8B24FF0000000EBA841AFFC19234FFDFCA9BFFE9DEC0FFE2D0
+ A6FFE2D1A7FFE2D1A8FFE3D1A8FFE3D1A8FFE3D1A9FFE3D1A9FFE3D1A8FFE8DA
+ B9FFDEC797FFA57310B8BA851D00BA851CFFB88113FFF2EBD9FFEDE2C6FFEBDF
+ BFFFEBDFBFFFEBDFBFFFEBDFBFFFEBDFBFFFEBDFBFFFEBDFBFFFEBDFBFFFF1E7
+ D1FFCCA65AFF8B62125FBB871E00B9851EF2B9851AFFB88114FFB67F10FFB67F
+ 10FFB67F10FFB67F10FFB67F10FFB67F10FFB67F10FFB67F10FFB67F10FFB780
+ 12FFB68117F00000000EBB871F00000000300000003300000033000000330000
+ 0033000000330000003300000033000000330000003300000033000000330000
+ 00330000002F0000000000000000FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF
+ FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF
+ FF00FFFFFF00FFFFFF00FFFFFF00
+ }
+ end
+end
diff --git a/components/jvcllaz/examples/JvID3v2/JvID3v2MainFormU.pas b/components/jvcllaz/examples/JvID3v2/JvID3v2MainFormU.pas
new file mode 100644
index 000000000..96ba4f7da
--- /dev/null
+++ b/components/jvcllaz/examples/JvID3v2/JvID3v2MainFormU.pas
@@ -0,0 +1,189 @@
+{******************************************************************
+
+ JEDI-VCL Demo
+
+ Copyright (C) 2002 Project JEDI
+
+ Original author:
+
+ Contributor(s):
+
+ You may retrieve the latest version of this file at the JEDI-JVCL
+ home page, located at http://jvcl.delphi-jedi.org
+
+ The contents of this file are used with permission, 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_1Final.html
+
+ Software distributed under the License is distributed on an
+ "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ implied. See the License for the specific language governing
+ rights and limitations under the License.
+
+******************************************************************}
+
+unit JvID3v2MainFormU;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+ Windows, SysUtils, FileUtil, Classes, Controls, Forms, //JvSearchFiles,
+ ComCtrls,
+ //JvDriveCtrls,
+ ExtCtrls, JvID3v2Base, JvId3v2, JvID3v2Types, //JvComponent,
+ StdCtrls, ShellCtrls; //, JvListBox, JvCombobox, JvExStdCtrls, JvComponentBase;
+
+type
+
+ { TJvID3v2MainForm }
+
+ TJvID3v2MainForm = class(TForm)
+ ImageList1: TImageList;
+ ListView1: TListView;
+ Splitter1: TSplitter;
+ JvID3v21: TJvID3v2;
+ Panel1: TPanel;
+ ShellTreeView: TShellTreeView;
+ procedure ListView1DblClick(Sender: TObject);
+ procedure ShellTreeViewChange(Sender: TObject);
+ procedure ShellTreeViewGetImageIndex(Sender: TObject; Node: TTreeNode);
+ procedure ShellTreeViewGetSelectedIndex(Sender: TObject; Node: TTreeNode);
+ procedure ShellTreeViewSelectionChanged(Sender: TObject);
+ private
+ FDir: string;
+ procedure FileFoundHandler(AIterator: TFileIterator);
+ public
+ procedure UpdateItem(Item: TListItem; const AFileName: string);
+ procedure ChangeToDir(const ANewDir: string);
+ end;
+
+var
+ JvID3v2MainForm: TJvID3v2MainForm;
+
+implementation
+
+uses
+ LazFileUtils,
+ JvID3v2EditFormU;
+
+{$R *.lfm}
+
+procedure TJvID3v2MainForm.ChangeToDir(const ANewDir: string);
+var
+ lCursor: TCursor;
+ searcher: TFileSearcher;
+begin
+ if ANewDir = FDir then
+ Exit;
+
+ FDir := ANewDir;
+
+ lCursor := Screen.Cursor;
+ Screen.Cursor := crHourGlass;
+ searcher := TFileSearcher.Create;
+ try
+ searcher.OnFileFound := @FileFoundHandler;
+ ListView1.Items.BeginUpdate;
+ try
+ ListView1.Items.Clear;
+ searcher.Search(ANewDir, '*.mp3', false);
+ finally
+ ListView1.Items.EndUpdate;
+ end;
+ finally
+ searcher.Free;
+ Screen.Cursor := lCursor;
+ end;
+end;
+
+procedure TJvID3v2MainForm.ShellTreeViewChange(Sender: TObject);
+begin
+ ChangeToDir(ShellTreeView.Path);
+end;
+
+procedure TJvID3v2MainForm.ShellTreeViewGetImageIndex(Sender: TObject;
+ Node: TTreeNode);
+begin
+ if Node.Level = 0 then
+ Node.ImageIndex := 0
+ else
+ Node.ImageIndex := 1;
+end;
+
+procedure TJvID3v2MainForm.ShellTreeViewGetSelectedIndex(Sender: TObject;
+ Node: TTreeNode);
+begin
+ if Node.Level = 0 then
+ Node.SelectedIndex := 0
+ else
+ Node.SelectedIndex := 2;
+end;
+
+procedure TJvID3v2MainForm.ShellTreeViewSelectionChanged(Sender: TObject);
+begin
+ ChangeToDir(ShellTreeView.Path);
+end;
+
+procedure TJvID3v2MainForm.FileFoundHandler(AIterator: TFileIterator);
+var
+ Item: TListItem;
+ HasTag: Boolean;
+ Version: TJvID3Version;
+begin
+ Item := ListView1.Items.Add;
+ GetID3v2Version(AIterator.FileName, HasTag, Version);
+ if HasTag then
+ case Version of
+ iveLowerThan2_2: Item.Caption := '<2.2';
+ ive2_2: Item.Caption := '2.2';
+ ive2_3: Item.Caption := '2.3';
+ ive2_4: Item.Caption := '2.4';
+ iveHigherThan2_4: Item.Caption := '>2.4'
+ else
+ Item.Caption := '?';
+ end
+ else
+ Item.Caption := '-';
+ Item.SubItems.Add(ExtractFileName(AIterator.Filename));
+end;
+
+procedure TJvID3v2MainForm.ListView1DblClick(Sender: TObject);
+var
+ lFileName: string;
+begin
+ if Assigned(ListView1.Selected) then
+ begin
+ //LFileName := IncludeTrailingPathDelimiter(ShellTreeView.Directory) +
+ lFileName := AppendPathDelim(ShellTreeView.Path) + ListView1.Selected.SubItems[0];
+ if TJvID3v2EditForm.Execute(lFileName) then
+ UpdateItem(ListView1.Selected, lFileName);
+ end;
+end;
+
+procedure TJvID3v2MainForm.UpdateItem(Item: TListItem; const AFileName: string);
+var
+ HasTag: Boolean;
+ Version: TJvID3Version;
+begin
+ GetID3v2Version(AFileName, HasTag, Version);
+ if HasTag then
+ case Version of
+ iveLowerThan2_2: Item.Caption := '<2.2';
+ ive2_2: Item.Caption := '2.2';
+ ive2_3: Item.Caption := '2.3';
+ ive2_4: Item.Caption := '2.4';
+ iveHigherThan2_4: Item.Caption := '>2.4'
+ else
+ Item.Caption := '?';
+ end
+ else
+ Item.Caption := '-';
+
+ Item.SubItems[0] := ExtractFileName(AFileName);
+end;
+
+end.
diff --git a/components/jvcllaz/packages/jvmmlazr.lpk b/components/jvcllaz/packages/jvmmlazr.lpk
index ad8b10f3f..47c095844 100644
--- a/components/jvcllaz/packages/jvmmlazr.lpk
+++ b/components/jvcllaz/packages/jvmmlazr.lpk
@@ -15,7 +15,7 @@
-
+
@@ -32,6 +32,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/jvcllaz/resource/jvmmreg.res b/components/jvcllaz/resource/jvmmreg.res
index 32f11e20e..1494ce6d6 100644
Binary files a/components/jvcllaz/resource/jvmmreg.res and b/components/jvcllaz/resource/jvmmreg.res differ
diff --git a/components/jvcllaz/run/JvCore/JvJCLUtils.pas b/components/jvcllaz/run/JvCore/JvJCLUtils.pas
index 17aee2ff4..37fd61a40 100644
--- a/components/jvcllaz/run/JvCore/JvJCLUtils.pas
+++ b/components/jvcllaz/run/JvCore/JvJCLUtils.pas
@@ -66,6 +66,8 @@ const
USDecimalSeparator = '.';
WideNull = WideChar(#0);
+ BOM_LSB_FIRST = WideChar($FEFF);
+ BOM_MSB_FIRST = WideChar($FFFE);
(******************** NOT CONVERTED
{$IFDEF UNIX}
@@ -1203,6 +1205,9 @@ function ReverseBytes(Value: Word): Word; overload; // taken from JclLogic
function ReverseBytes(Value: Integer): Integer; overload;
function ReverseBytes(Value: Cardinal): Cardinal; overload;
+function BEtoN(const AValue: WideString): WideString; overload;
+function NtoBE(const AValue: WideString): WideString; overload;
+
// taken from JclFileUtils
function FindUnusedFileName(FileName: string; const FileExt: string; NumberPrefix: string = ''): string;
@@ -9788,6 +9793,35 @@ begin
Result := (Value shr 24) or (Value shl 24) or ((Value and $00FF0000) shr 8) or ((Value and $0000FF00) shl 8);
end;
+// from fpexif
+function BEtoN(const AValue: WideString): WideString;
+{$IFNDEF ENDIAN_BIG}
+var
+ i: Integer;
+{$ENDIF}
+begin
+ {$IFDEF ENDIAN_BIG}
+ Result := AValue;
+ {$ELSE}
+ SetLength(Result, Length(AValue));
+ for i:=1 to Length(AValue) do
+ Result[i] := WideChar(BEToN(PDWord(@AValue[i])^));
+ {$ENDIF}
+end;
+
+function NtoBE(const AValue: WideString): WideString;
+var
+ i: Integer;
+begin
+ {$IFDEF ENDIAN_BIG}
+ Result := AValue;
+ {$ELSE}
+ SetLength(Result, Length(AValue));
+ for i:=1 to Length(AValue) do
+ Result[i] := WideChar(NtoBE(PDWord(@AValue[i])^));
+ {$ENDIF}
+end;
+
// from JclLogic
function ReverseBytes(Value: Cardinal): Cardinal;
begin
diff --git a/components/jvcllaz/run/JvMM/JvID3v2Base.pas b/components/jvcllaz/run/JvMM/JvID3v2Base.pas
new file mode 100644
index 000000000..345565bf9
--- /dev/null
+++ b/components/jvcllaz/run/JvMM/JvID3v2Base.pas
@@ -0,0 +1,9167 @@
+{-----------------------------------------------------------------------------
+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: Integer);
+
+ { 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 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 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(AController: TJvID3Controller; AFrameID: TJvID3FrameID): Boolean; virtual;
+ function CheckFrame(const 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 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): 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 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 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 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): 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 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): 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 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 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 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 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 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(Event: TJvID3Event; 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;
+ var 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(Frame: TJvID3Frame; var S: AnsiString; const 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 := 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 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: {$IFDEF COMPILER12_UP}TStrings{$ELSE}JclUnicode.TWideStrings{$ENDIF COMPILER12_UP};
+ const HandleError: TJvID3HandleError): Boolean; }
+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: {$IFDEF COMPILER12_UP}TStrings{$ELSE}JclUnicode.TWideStrings{$ENDIF COMPILER12_UP};
+ const ASeparator: WideChar;
+ const HandleError: TJvID3HandleError): Boolean; }
+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 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 (TBytes(Source)[I] and $7F); // $7F = %01111111
+ end;
+end;
+
+procedure UnSyncSafe(var Source; const SourceSize: Integer; var 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: WideString; const ALength: Integer;
+ Strings: {$IFDEF COMPILER12_UP}TStrings{$ELSE}JclUnicode.TWideStrings{$ENDIF COMPILER12_UP}); }
+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: WideString;
+ Strings: {$IFDEF COMPILER12_UP}TStrings{$ELSE}JclUnicode.TWideStrings{$ENDIF COMPILER12_UP}); }
+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
+ if (AStream.Read(Header, SizeOf(Header)) = SizeOf(Header)) and
+ (Header.Identifier = cID3HeaderId) then
+ begin
+ UnSyncSafe(Header.Size, 4, Result);
+ Inc(Result, 10);
+ end
+ else
+ Result := 0;
+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, 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, 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(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;
+ var Version: TJvID3Version);
+var
+ Header: TID3v2HeaderRec;
+begin
+ with TFileStream.Create(AFileName, fmOpenRead or fmShareDenyWrite) do
+ try
+ HasTag := (Read(Header, 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;
+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 $00
+ Preview start $xx xx
+ Preview length $xx xx
+ Encryption info }
+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 $00
+ Preview start $xx xx
+ Preview length $xx xx
+ Encryption info }
+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 $00
+ Preview start $xx xx
+ Preview length $xx xx
+ Encryption info
+ }
+ 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. $00 (00)
+ The actual text
+ }
+ 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;
+var
+ I: Integer;
+begin
+ for I := 1 to Length(S) do
+ if Ord(S[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. $00 (00)
+ The actual text
+ }
+
+ 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
+ SameStr(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. $00 (00)
+ The actual text
+ }
+
+ 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)
+ 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 - 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(FFileName, 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, 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;
+ LFlag: Byte;
+ FlagDataLength: Byte;
+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, 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, 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, Longint(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, Flag1: Byte;
+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;
+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.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;
+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
+ not SameStr(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 $00
+ FileName $00 (00)
+ Content description $00 (00)
+ Encapsulated object
+ }
+ 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 $00
+ FileName $00 (00)
+ Content description $00 (00)
+ Encapsulated object
+ }
+ 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 := SameStr(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 $00
+ FileName $00 (00)
+ Content description $00 (00)
+ Encapsulated object
+ }
+ 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, 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, 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 $00
+ Date of purch.
+ Seller }
+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 $00
+ Date of purch.
+ Seller }
+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 $00
+ Date of purch.
+ Seller
+ }
+ 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;
+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: $00
+ Picture type: $xx
+ Description: $00 (00)
+ Picture 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;
+begin
+ { Text encoding $xx
+ MIME type $00
+ Picture type $xx
+ Description $00 (00)
+ Picture 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
+ SameStr(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 $00
+ Picture type $xx
+ Description $00 (00)
+ Picture 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;
+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 $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 $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 $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 := 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;
+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: Integer);
+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;
+ 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;
+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, SA2: AnsiString;
+ SW1, 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;
+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
+ }
+ 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
+ }
+ 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
+ }
+ 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, 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;
+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 $00 (00)
+ Value
+ }
+ 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;
+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 $00 (00)
+ Value }
+ 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.
diff --git a/components/jvcllaz/run/JvMM/JvId3v1.pas b/components/jvcllaz/run/JvMM/JvId3v1.pas
index b08a1bdd7..6fa1775f2 100644
--- a/components/jvcllaz/run/JvMM/JvId3v1.pas
+++ b/components/jvcllaz/run/JvMM/JvId3v1.pas
@@ -47,11 +47,11 @@ type
TJvID3v1 = class(TComponent) //TJvComponent)
private
- FSongName: AnsiString;
- FArtist: AnsiString;
- FAlbum: AnsiString;
- FComment: AnsiString;
- FYear: AnsiString;
+ FSongName: String;
+ FArtist: String;
+ FAlbum: String;
+ FComment: String;
+ FYear: String;
FGenre: Byte;
FFileName: TFileName;
FActive: Boolean;
@@ -82,11 +82,11 @@ type
property Active: Boolean read FActive write SetActive;
property FileName: TFileName read FFileName write SetFileName;
{ Do not store dummies }
- property SongName: AnsiString read FSongName write FSongName stored False;
- property Artist: AnsiString read FArtist write FArtist stored False;
- property Album: AnsiString read FAlbum write FAlbum stored False;
- property Year: AnsiString read FYear write FYear stored False;
- property Comment: AnsiString read FComment write FComment stored False;
+ property SongName: String read FSongName write FSongName stored False;
+ property Artist: String read FArtist write FArtist stored False;
+ property Album: String read FAlbum write FAlbum stored False;
+ property Year: String read FYear write FYear stored False;
+ property Comment: String read FComment write FComment stored False;
property Genre: Byte read FGenre write FGenre stored False;
property GenreAsString: string read GetGenreAsString write SetGenreAsString stored False;
property AlbumTrack: Byte read FAlbumTrack write FAlbumTrack stored False;
@@ -101,7 +101,7 @@ function WriteID3v1Tag(const AFileName: string; const ATag: TID3v1Tag): Boolean;
implementation
uses
- Math,
+ Math, LConvEncoding,
JvId3v2Types, JvTypes, JvResources;
const
@@ -110,6 +110,7 @@ const
CTagSize = 128;
CTagIDSize = 3;
+
//=== Global procedures ======================================================
function HasID3v1Tag(const AFileName: string): Boolean;
@@ -196,6 +197,7 @@ begin
end;
end;
+
//=== Local procedures =======================================================
procedure AnsiStringToPAnsiChar(const Source: AnsiString; Dest: PAnsiChar; const MaxLength: Integer);
@@ -215,6 +217,7 @@ begin
SetString(Result, Q, P - Q);
end;
+
//=== { TJvID3v1 } ===========================================================
procedure TJvID3v1.Loaded;
@@ -249,11 +252,11 @@ begin
// Set new Tag
Move(CID3v1Tag[0], lTag.Identifier[0], 3);
- AnsiStringToPAnsiChar(SongName, @lTag.SongName, 30);
- AnsiStringToPAnsiChar(Artist, @lTag.Artist, 30);
- AnsiStringToPAnsiChar(Album, @lTag.Album, 30);
+ AnsiStringToPAnsiChar(UTF8ToISO_8859_1(SongName), @lTag.SongName, 30);
+ AnsiStringToPAnsiChar(UTF8ToISO_8859_1(Artist), @lTag.Artist, 30);
+ AnsiStringToPAnsiChar(UTF8ToISO_8859_1(Album), @lTag.Album, 30);
AnsiStringToPAnsiChar(Year, @lTag.Year, 4);
- AnsiStringToPAnsiChar(Comment, @lTag.Comment, 30);
+ AnsiStringToPAnsiChar(UTF8ToISO_8859_1(Comment), @lTag.Comment, 30);
lTag.Genre := FGenre;
if lTag.Comment[28] = #0 then
lTag.Comment[29] := AnsiChar(FAlbumTrack);
@@ -322,11 +325,11 @@ begin
if Result then
begin
- FSongName := PAnsiCharToAnsiString(@lTag.SongName, 30);
- FArtist := PAnsiCharToAnsiString(@lTag.Artist, 30);
- FAlbum := PAnsiCharToAnsiString(@lTag.Album, 30);
+ FSongName := ISO_8859_1ToUTF8(PAnsiCharToAnsiString(@lTag.SongName, 30));
+ FArtist := ISO_8859_1ToUTF8(PAnsiCharToAnsiString(@lTag.Artist, 30));
+ FAlbum := ISO_8859_1ToUTF8(PAnsiCharToAnsiString(@lTag.Album, 30));
FYear := PAnsiCharToAnsiString(@lTag.Year, 4);
- FComment := PAnsiCharToAnsiString(@lTag.Comment, 30);
+ FComment := ISO_8859_1ToUTF8(PAnsiCharToAnsiString(@lTag.Comment, 30));
// (p3) missing genre added
FGenre := lTag.Genre;
if lTag.Comment[28] = #0 then
diff --git a/components/jvcllaz/run/JvMM/JvId3v2.pas b/components/jvcllaz/run/JvMM/JvId3v2.pas
new file mode 100644
index 000000000..da424049d
--- /dev/null
+++ b/components/jvcllaz/run/JvMM/JvId3v2.pas
@@ -0,0 +1,1165 @@
+{-----------------------------------------------------------------------------
+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: JvID3v2.PAS, released on 2001-02-28.
+
+The Initial Developer of the Original Code is Sébastien Buysse [sbuysse att buypin dott com]
+Portions created by Sébastien Buysse are Copyright (C) 2001 Sébastien Buysse.
+All Rights Reserved.
+
+Contributor(s): Michael Beck [mbeck att bigfoot dott com].
+
+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:
+-----------------------------------------------------------------------------}
+// $Id$
+
+unit JvId3v2;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+ Classes, Graphics, Controls,
+ (*
+ {$IFNDEF COMPILER12_UP}
+ JclUnicode,
+ {$ENDIF ~COMPILER12_UP}
+ *)
+ JvId3v2Types, JvID3v2Base;
+
+type
+ TJvID3Persistent = class(TPersistent)
+ private
+ FController: TJvID3Controller;
+ public
+ constructor Create(AController: TJvID3Controller);
+ end;
+
+ TJvID3Text = class(TJvID3Persistent)
+ private
+ FDummyList: TStrings;
+ //FDummyList: {$IFDEF COMPILER12_UP}TStrings{$ELSE}TWideStrings{$ENDIF COMPILER12_UP};
+ function GetDateTime(const FrameID: TJvID3FrameID): TDateTime;
+ function GetList(const FrameID: TJvID3FrameID): TStrings;
+ //function GetList(const FrameID: Integer{TJvID3FrameID}): {$IFDEF COMPILER12_UP}TStrings{$ELSE}TWideStrings{$ENDIF COMPILER12_UP};
+ function GetNumber(const FrameID: TJvID3FrameID): Cardinal;
+ //function GetText(const FrameID: Integer{TJvID3FrameID}): WideString;
+ function GetText(const FrameID: TJvID3FrameID): String;
+ procedure SetDateTime(const FrameID: TJvID3FrameID; const Value: TDateTime);
+ procedure SetList(const FrameID: TJvID3FrameID; const Value: TStrings);
+ //procedure SetList(const FrameID: Integer{TJvID3FrameID}; const Value: {$IFDEF COMPILER12_UP}TStrings{$ELSE}TWideStrings{$ENDIF COMPILER12_UP});
+ procedure SetNumber(const FrameID: TJvID3FrameID; const Value: Cardinal);
+ //procedure SetText(const FrameID: Integer{TJvID3FrameID}; const Value: WideString);
+ procedure SetText(const FrameID: TJvID3FrameID; const Value: String);
+ function GetBPM: Cardinal;
+ procedure SetBPM(const Value: Cardinal);
+ public
+ constructor Create(AController: TJvID3Controller);
+ destructor Destroy; override;
+ published
+ { Do not store dummies }
+ //property Album: WideString index fiAlbum read GetText write SetText stored False;
+ property Album: String index fiAlbum read GetText write SetText stored False;
+ //property AlbumSortOrder: WideString index fiAlbumSortOrder read GetText write SetText stored False;
+ property AlbumSortOrder: String index fiAlbumSortOrder read GetText write SetText stored False;
+ property Band: String index fiBand read GetText write SetText stored False;
+ //property Band: WideString index fiBand read GetText write SetText stored False;
+ property BPM: Cardinal read GetBPM write SetBPM stored False;
+ property BPMStr: String index fiBPM read GetText write SetText stored False;
+ //property BPMStr: WideString index fiBPM read GetText write SetText stored False;
+ property Composer: TStrings index fiComposer read GetList write SetList stored False;
+ //property Composer: {$IFDEF COMPILER12_UP}TStrings{$ELSE}TWideStrings{$ENDIF COMPILER12_UP} index fiComposer read GetList write SetList stored False;
+ property Conductor: String index fiConductor read GetText write SetText stored False;
+ //property Conductor: WideString index fiConductor read GetText write SetText stored False;
+ property ContentType: TStrings index fiContentType read GetList write SetList stored False;
+ //property ContentType: {$IFDEF COMPILER12_UP}TStrings{$ELSE}TWideStrings{$ENDIF COMPILER12_UP} index fiContentType read GetList write SetList stored False;
+ property ContentGroup: String index fiContentGroup read GetText write SetText stored False;
+ //property ContentGroup: WideString index fiContentGroup read GetText write SetText stored False;
+ property Copyright: String index fiCopyright read GetText write SetText stored False;
+ //property Copyright: WideString index fiCopyright read GetText write SetText stored False;
+ property Date: String index fiDate read GetText write SetText stored False;
+ //property Date: WideString index fiDate read GetText write SetText stored False;
+ property EncodedBy: String index fiEncodedBy read GetText write SetText stored False;
+ //property EncodedBy: WideString index fiEncodedBy read GetText write SetText stored False;
+ property EncoderSettings: String index fiEncoderSettings read GetText write SetText stored False;
+ //property EncoderSettings: WideString index fiEncoderSettings read GetText write SetText stored False;
+ property EncodingTime: TDateTime index fiEncodingTime read GetDateTime write SetDateTime stored False;
+ property FileOwner: String index fiFileOwner read GetText write SetText stored False;
+ //property FileOwner: WideString index fiFileOwner read GetText write SetText stored False;
+ property FileType: String index fiFileType read GetText write SetText stored False;
+ //property FileType: WideString index fiFileType read GetText write SetText stored False;
+ property InitialKey: String index fiInitialKey read GetText write SetText stored False;
+ //property InitialKey: WideString index fiInitialKey read GetText write SetText stored False;
+ property ISRC: String index fiISRC read GetText write SetText stored False;
+ //property ISRC: WideString index fiISRC read GetText write SetText stored False;
+ property Language: TStrings index fiLanguage read GetList write SetList stored False;
+ //property Language: {$IFDEF COMPILER12_UP}TStrings{$ELSE}TWideStrings{$ENDIF COMPILER12_UP} index fiLanguage read GetList write SetList stored False;
+ property LeadArtist: TStrings index fiLeadArtist read GetList write SetList stored False;
+ //property LeadArtist: {$IFDEF COMPILER12_UP}TStrings{$ELSE}TWideStrings{$ENDIF COMPILER12_UP} index fiLeadArtist read GetList write SetList stored False;
+ property Lyricist: TStrings index fiLyricist read GetList write SetList stored False;
+ //property Lyricist: {$IFDEF COMPILER12_UP}TStrings{$ELSE}TWideStrings{$ENDIF COMPILER12_UP} index fiLyricist read GetList write SetList stored False;
+ property MediaType: String index fiMediaType read GetText write SetText stored False;
+ //property MediaType: WideString index fiMediaType read GetText write SetText stored False;
+ property MixArtist: String index fiMixArtist read GetText write SetText stored False;
+ //property MixArtist: WideString index fiMixArtist read GetText write SetText stored False;
+ property Mood: String index fiMood read GetText write SetText stored False;
+ //property Mood: WideString index fiMood read GetText write SetText stored False;
+ property NetRadioOwner: String index fiNetRadioOwner read GetText write SetText stored False;
+ //property NetRadioOwner: WideString index fiNetRadioOwner read GetText write SetText stored False;
+ property NetRadioStation: String index fiNetRadioStation read GetText write SetText stored False;
+ //property NetRadioStation: WideString index fiNetRadioStation read GetText write SetText stored False;
+ property OrigAlbum: String index fiOrigAlbum read GetText write SetText stored False;
+ //property OrigAlbum: WideString index fiOrigAlbum read GetText write SetText stored False;
+ property OrigArtist: TStrings index fiOrigArtist read GetList write SetList stored False;
+ //property OrigArtist: {$IFDEF COMPILER12_UP}TStrings{$ELSE}TWideStrings{$ENDIF COMPILER12_UP} index fiOrigArtist read GetList write SetList stored False;
+ property OrigFileName: String index fiOrigFileName read GetText write SetText stored False;
+ //property OrigFileName: WideString index fiOrigFileName read GetText write SetText stored False;
+ property OrigLyricist: TStrings index fiOrigLyricist read GetList write SetList stored False;
+ //property OrigLyricist: {$IFDEF COMPILER12_UP}TStrings{$ELSE}TWideStrings{$ENDIF COMPILER12_UP} index fiOrigLyricist read GetList write SetList stored False;
+ property OrigReleaseTime: TDateTime index fiOrigReleaseTime read GetDateTime write SetDateTime stored False;
+ property OrigYear: Cardinal index fiOrigYear read GetNumber write SetNumber stored False;
+ property PartInSet: String index fiPartInSet read GetText write SetText stored False;
+ //property PartInSet: WideString index fiPartInSet read GetText write SetText stored False;
+ property PerformerSortOrder: String index fiPerformerSortOrder read GetText write SetText stored False;
+ //property PerformerSortOrder: WideString index fiPerformerSortOrder read GetText write SetText stored False;
+ property PlaylistDelay: Cardinal index fiPlaylistDelay read GetNumber write SetNumber stored False;
+ property ProducedNotice: String index fiProducedNotice read GetText write SetText stored False;
+ //property ProducedNotice: WideString index fiProducedNotice read GetText write SetText stored False;
+ property Publisher: String index fiPublisher read GetText write SetText stored False;
+ //property Publisher: WideString index fiPublisher read GetText write SetText stored False;
+ property RecordingDates: String index fiRecordingDates read GetText write SetText stored False;
+ //property RecordingDates: WideString index fiRecordingDates read GetText write SetText stored False;
+ property RecordingTime: TDateTime index fiRecordingTime read GetDateTime write SetDateTime stored False;
+ property ReleaseTime: TDateTime index fiReleaseTime read GetDateTime write SetDateTime stored False;
+ property SetSubTitle: String index fiSetSubTitle read GetText write SetText stored False;
+ //property SetSubTitle: WideString index fiSetSubTitle read GetText write SetText stored False;
+ property Size: Cardinal index fiSize read GetNumber write SetNumber stored False;
+ property SongLen: Cardinal index fiSongLen read GetNumber write SetNumber stored False;
+ property SubTitle: String index fiSubTitle read GetText write SetText stored False;
+ //property SubTitle: WideString index fiSubTitle read GetText write SetText stored False;
+ property TaggingTime: TDateTime index fiTaggingTime read GetDateTime write SetDateTime stored False;
+ property Time: String index fiTime read GetText write SetText stored False;
+ //property Time: WideString index fiTime read GetText write SetText stored False;
+ property Title: String index fiTitle read GetText write SetText stored False;
+ //property Title: WideString index fiTitle read GetText write SetText stored False;
+ property TitleSortOrder: String index fiTitleSortOrder read GetText write SetText stored False;
+ //property TitleSortOrder: WideString index fiTitleSortOrder read GetText write SetText stored False;
+ property TrackNum: String index fiTrackNum read GetText write SetText stored False;
+ //property TrackNum: WideString index fiTrackNum read GetText write SetText stored False;
+ property Year: Cardinal index fiYear read GetNumber write SetNumber stored False;
+ end;
+
+ TJvID3Web = class(TJvID3Persistent)
+ private
+ function GetText(const FrameID: TJvID3FrameID): AnsiString;
+ procedure SetText(const FrameID: TJvID3FrameID; const Value: AnsiString);
+ published
+ { Do not store dummies }
+ property Artist: AnsiString index fiWWWArtist read GetText write SetText stored False;
+ property AudioFile: AnsiString index fiWWWAudioFile read GetText write SetText stored False;
+ property AudioSource: AnsiString index fiWWWAudioSource read GetText write SetText stored False;
+ property CommercialInfo: AnsiString index fiWWWCommercialInfo read GetText write SetText stored False;
+ property Copyright: AnsiString index fiWWWCopyright read GetText write SetText stored False;
+ property Payment: AnsiString index fiWWWPayment read GetText write SetText stored False;
+ property Publisher: AnsiString index fiWWWPublisher read GetText write SetText stored False;
+ property RadioPage: AnsiString index fiWWWRadioPage read GetText write SetText stored False;
+ end;
+
+ TJvID3UDText = class(TJvID3Persistent)
+ private
+ FDummyI: Integer;
+ FItemIndex: Integer;
+ //function GetDescription: WideString;
+ function GetDescription: String;
+ function GetItemCount: Integer;
+ function GetItemIndex: Integer;
+ //function GetValue: WideString;
+ function GetValue: String;
+ //procedure SetDescription(const Value: WideString);
+ procedure SetDescription(const Value: String);
+ procedure SetItemIndex(const Value: Integer);
+ procedure SetValue(const Value: String);
+ //procedure SetValue(const Value: WideString);
+ public
+ procedure Add(const ADescription, AValue: String);
+ //procedure Add(const ADescription, AValue: WideString);
+ published
+ property ItemIndex: Integer read GetItemIndex write SetItemIndex;
+ { Do not store dummies }
+ //property Description: WideString read GetDescription write SetDescription stored False;
+ //property Value: WideString read GetValue write SetValue stored False;
+ property Description: String read GetDescription write SetDescription stored False;
+ property Value: String read GetValue write SetValue stored False;
+ property ItemCount: Integer read GetItemCount write FDummyI stored False;
+ end;
+
+ TJvID3UDUrl = class(TJvID3Persistent)
+ private
+ FItemIndex: Integer;
+ FDummyI: Integer;
+ //function GetDescription: WideString;
+ function GetDescription: String;
+ function GetItemCount: Integer;
+ function GetItemIndex: Integer;
+ function GetURL: AnsiString;
+ procedure SetDescription(const Value: String);
+ //procedure SetDescription(const Value: WideString);
+ procedure SetItemIndex(const Value: Integer);
+ procedure SetURL(const Value: AnsiString);
+ public
+ procedure Add(const ADescription: String; const AURL: AnsiString);
+ //procedure Add(const ADescription: WideString; const AURL: AnsiString);
+ published
+ property ItemIndex: Integer read GetItemIndex write SetItemIndex;
+ { Do not store dummies }
+ //property Description: WideString read GetDescription write SetDescription stored False;
+ property Description: String read GetDescription write SetDescription stored False;
+ property URL: AnsiString read GetURL write SetURL stored False;
+ property ItemCount: Integer read GetItemCount write FDummyI stored False;
+ end;
+
+ TJvID3Pictures = class(TJvID3Persistent)
+ private
+ FPictures: array[TJvID3PictureType] of TPicture;
+ FUpdating: Boolean;
+ function GetPicture(const AType: TJvID3PictureType): TPicture;
+ procedure SetPicture(const AType: TJvID3PictureType; const Value: TPicture);
+ procedure PictureChanged(Sender: TObject);
+ procedure PictureToFrame(const AType: TJvID3PictureType);
+ procedure RetrievePictures;
+ procedure RemovePictures;
+ public
+ constructor Create(AController: TJvID3Controller); virtual;
+ destructor Destroy; override;
+ published
+ property Other: TPicture index ptOther read GetPicture write SetPicture stored False;
+ property FileIcon: TPicture index ptFileIcon read GetPicture write SetPicture stored False;
+ property OtherFileIcon: TPicture index ptOtherFileIcon read GetPicture write SetPicture stored False;
+ property CoverFront: TPicture index ptCoverFront read GetPicture write SetPicture stored False;
+ property CoverBack: TPicture index ptCoverBack read GetPicture write SetPicture stored False;
+ property LeafletPage: TPicture index ptLeafletPage read GetPicture write SetPicture stored False;
+ property Media: TPicture index ptMedia read GetPicture write SetPicture stored False;
+ property LeadArtist: TPicture index ptLeadArtist read GetPicture write SetPicture stored False;
+ property Artist: TPicture index ptArtist read GetPicture write SetPicture stored False;
+ property Conductor: TPicture index ptConductor read GetPicture write SetPicture stored False;
+ property Band: TPicture index ptBand read GetPicture write SetPicture stored False;
+ property Composer: TPicture index ptComposer read GetPicture write SetPicture stored False;
+ property Lyricist: TPicture index ptLyricist read GetPicture write SetPicture stored False;
+ property RecordingLocation: TPicture index ptRecordingLocation read GetPicture write SetPicture stored False;
+ property DuringRecording: TPicture index ptDuringRecording read GetPicture write SetPicture stored False;
+ property DuringPerformance: TPicture index ptDuringPerformance read GetPicture write SetPicture stored False;
+ property MovieVideoScreenCapture: TPicture index ptMovieVideoScreenCapture read GetPicture write SetPicture stored
+ False;
+ property BrightColouredFish: TPicture index ptBrightColouredFish read GetPicture write SetPicture stored False;
+ property Illustration: TPicture index ptIllustration read GetPicture write SetPicture stored False;
+ property BandLogotype: TPicture index ptBandLogotype read GetPicture write SetPicture stored False;
+ property PublisherLogotype: TPicture index ptPublisherLogotype read GetPicture write SetPicture stored False;
+ end;
+
+ TJvID3PicturesDesc = class(TJvID3Persistent)
+ private
+ function GetText(const AType: TJvID3PictureType): String;
+ procedure SetText(const AType: TJvID3PictureType; const Value: String);
+ //function GetText(const AType: Integer{TJvID3PictureType}): WideString;
+ //procedure SetText(const AType: Integer{TJvID3PictureType}; const Value: WideString);
+ published
+ property Other: String index ptOther read GetText write SetText stored False;
+ //property Other: WideString index ptOther read GetText write SetText stored False;
+ property FileIcon: String index ptFileIcon read GetText write SetText stored False;
+ //property FileIcon: WideString index ptFileIcon read GetText write SetText stored False;
+ property OtherFileIcon: String index ptOtherFileIcon read GetText write SetText stored False;
+ //property OtherFileIcon: WideString index ptOtherFileIcon read GetText write SetText stored False;
+ property CoverFront: String index ptCoverFront read GetText write SetText stored False;
+ //property CoverFront: WideString index ptCoverFront read GetText write SetText stored False;
+ property CoverBack: String index ptCoverBack read GetText write SetText stored False;
+ //property CoverBack: WideString index ptCoverBack read GetText write SetText stored False;
+ property LeafletPage: String index ptLeafletPage read GetText write SetText stored False;
+ //property LeafletPage: WideString index ptLeafletPage read GetText write SetText stored False;
+ property Media: String index ptMedia read GetText write SetText stored False;
+ //property Media: WideString index ptMedia read GetText write SetText stored False;
+ property LeadArtist: String index ptLeadArtist read GetText write SetText stored False;
+ //property LeadArtist: WideString index ptLeadArtist read GetText write SetText stored False;
+ property Artist: String index ptArtist read GetText write SetText stored False;
+ //property Artist: WideString index ptArtist read GetText write SetText stored False;
+ property Conductor: String index ptConductor read GetText write SetText stored False;
+ //property Conductor: WideString index ptConductor read GetText write SetText stored False;
+ property Band: String index ptBand read GetText write SetText stored False;
+ //property Band: WideString index ptBand read GetText write SetText stored False;
+ property Composer: String index ptComposer read GetText write SetText stored False;
+ //property Composer: WideString index ptComposer read GetText write SetText stored False;
+ property Lyricist: String index ptLyricist read GetText write SetText stored False;
+ //property Lyricist: WideString index ptLyricist read GetText write SetText stored False;
+ property RecordingLocation: String index ptRecordingLocation read GetText write SetText stored False;
+ //property RecordingLocation: WideString index ptRecordingLocation read GetText write SetText stored False;
+ property DuringRecording: String index ptDuringRecording read GetText write SetText stored False;
+ //property DuringRecording: WideString index ptDuringRecording read GetText write SetText stored False;
+ property DuringPerformance: String index ptDuringPerformance read GetText write SetText stored False;
+ //property DuringPerformance: WideString index ptDuringPerformance read GetText write SetText stored False;
+ property MovieVideoScreenCapture: String index ptMovieVideoScreenCapture read GetText write SetText stored False;
+ //property MovieVideoScreenCapture: WideString index ptMovieVideoScreenCapture read GetText write SetText stored False;
+ property BrightColouredFish: String index ptBrightColouredFish read GetText write SetText stored False;
+ //property BrightColouredFish: WideString index ptBrightColouredFish read GetText write SetText stored False;
+ property Illustration: String index ptIllustration read GetText write SetText stored False;
+ //property Illustration: WideString index ptIllustration read GetText write SetText stored False;
+ property BandLogotype: String index ptBandLogotype read GetText write SetText stored False;
+ //property BandLogotype: WideString index ptBandLogotype read GetText write SetText stored False;
+ property PublisherLogotype: String index ptPublisherLogotype read GetText write SetText stored False;
+ //property PublisherLogotype: WideString index ptPublisherLogotype read GetText write SetText stored False;
+ end;
+
+ TJvID3Images = class(TJvID3Persistent)
+ private
+ FPictures: TJvID3Pictures;
+ FInfos: TJvID3PicturesDesc;
+ public
+ constructor Create(AController: TJvID3Controller);
+ destructor Destroy; override;
+ published
+ property Pictures: TJvID3Pictures read FPictures;
+ property Infos: TJvID3PicturesDesc read FInfos;
+ end;
+
+ TJvID3Ipl = class(TJvID3Persistent)
+ private
+ FDummyI: Integer;
+ FItemIndex: Integer;
+ function GetItemCount: Integer;
+ function GetJob: String;
+ //function GetJob: WideString;
+ function GetPerson: String;
+ //function GetPerson: WideString;
+ procedure SetItemIndex(const Value: Integer);
+ procedure SetJob(const Value: String);
+ //procedure SetJob(const Value: WideString);
+ procedure SetPerson(const Value: String);
+ //procedure SetPerson(const Value: WideString);
+ published
+ property ItemIndex: Integer read FItemIndex write SetItemIndex;
+ { Do not store dummies }
+ property Job: String read GetJob write SetJob stored False;
+ //property Job: WideString read GetJob write SetJob stored False;
+ property Person: String read GetPerson write SetPerson stored False;
+ //property Person: WideString read GetPerson write SetPerson stored False;
+ property ItemCount: Integer read GetItemCount write FDummyI stored False;
+ end;
+
+ TJvID3Owner = class(TJvID3Persistent)
+ private
+ function GetDatePurchased: TDateTime;
+ function GetPrice: AnsiString;
+ function GetSeller: String;
+ //function GetSeller: WideString;
+ procedure SetDatePurchased(const Value: TDateTime);
+ procedure SetPrice(const Value: AnsiString);
+ procedure SetSeller(const Value: String);
+ //procedure SetSeller(const Value: WideString);
+ published
+ { Do not store dummies }
+ property Price: AnsiString read GetPrice write SetPrice stored False;
+ property DatePurchased: TDateTime read GetDatePurchased write SetDatePurchased stored False;
+ property Seller: String read GetSeller write SetSeller stored False;
+ //property Seller: WideString read GetSeller write SetSeller stored False;
+ end;
+
+ TJvID3Popularimeter = class(TJvID3Persistent)
+ private
+ function GetCounter: Cardinal;
+ function GetRating: Byte;
+ function GetEMailAddress: AnsiString;
+ procedure SetCounter(const Value: Cardinal);
+ procedure SetRating(const Value: Byte);
+ procedure SetEMailAddress(const Value: AnsiString);
+ published
+ { Do not store dummies }
+ property EMailAddress: AnsiString read GetEMailAddress write SetEMailAddress stored False;
+ property Rating: Byte read GetRating write SetRating stored False;
+ property Counter: Cardinal read GetCounter write SetCounter stored False;
+ end;
+
+ {$IFDEF RTL230_UP}
+ [ComponentPlatformsAttribute(pidWin32 or pidWin64 or pidOSX32)]
+ {$ENDIF RTL230_UP}
+ TJvID3v2 = class(TJvID3Controller)
+ private
+ FID3Text: TJvID3Text;
+ FWeb: TJvID3Web;
+ FUserDefinedText: TJvID3UDText;
+ FUserDefinedWeb: TJvID3UDUrl;
+ FInvolvedPeople: TJvID3Ipl;
+ FImages: TJvID3Images;
+ FOwner: TJvID3Owner;
+ FPopularimeter: TJvID3Popularimeter;
+ FProcessPictures: Boolean;
+ function GetPlayCounter: Cardinal;
+ procedure SetPlayCounter(const Value: Cardinal);
+ protected
+ procedure ActiveChanged(Sender: TObject; Activated: Boolean);
+ public
+ constructor Create(AOwner: TComponent); override;
+ destructor Destroy; override;
+
+ property Header;
+ property ExtendedHeader;
+ published
+ { Do not store dummies }
+ property Texts: TJvID3Text read FID3Text;
+ property ProcessPictures: Boolean read FProcessPictures write FProcessPictures stored True;
+ property UserDefinedText: TJvID3UDText read FUserDefinedText;
+ property Web: TJvID3Web read FWeb;
+ property UserDefinedWeb: TJvID3UDUrl read FUserDefinedWeb;
+ property InvolvedPeople: TJvID3Ipl read FInvolvedPeople;
+ property Images: TJvID3Images read FImages;
+ property PlayCounter: Cardinal read GetPlayCounter write SetPlayCounter stored False;
+ property Owner: TJvID3Owner read FOwner;
+ property Popularimeter: TJvID3Popularimeter read FPopularimeter;
+ property Version;
+ property FileInfo;
+ end;
+
+
+implementation
+
+uses
+ SysUtils, Math,
+ JvResources; //, JclSysUtils;
+
+//=== Local procedures =======================================================
+
+function ExtractMIMETypeFromClassName(AClassName: string): AnsiString;
+begin
+ AClassName := AnsiLowerCase(AClassName);
+
+ if (Pos('jpg', AClassName) > 0) or (Pos('jpeg', AClassName) > 0) then
+ Result := 'image/jpeg'
+ else
+ if (Pos('bmp', AClassName) > 0) or (Pos('bitmap', AClassName) > 0) then
+ Result := 'image/bitmap'
+ else
+ if (Pos('gif', AClassName) > 0) then
+ Result := 'image/gif'
+ else
+ Result := 'image/';
+end;
+
+//=== { TJvID3Images } =======================================================
+
+constructor TJvID3Images.Create(AController: TJvID3Controller);
+begin
+ inherited Create(AController);
+ FPictures := TJvID3Pictures.Create(AController);
+ FInfos := TJvID3PicturesDesc.Create(AController);
+end;
+
+destructor TJvID3Images.Destroy;
+begin
+ FPictures.Free;
+ FInfos.Free;
+ inherited Destroy;
+end;
+
+//=== { TJvID3Ipl } ==========================================================
+
+function TJvID3Ipl.GetItemCount: Integer;
+var
+ Frame: TJvID3DoubleListFrame;
+begin
+ if not FController.Active then
+ Result := 0
+ else
+ begin
+ Frame := TJvID3DoubleListFrame.Find(FController, fiInvolvedPeople);
+ if Assigned(Frame) then
+ Result := Frame.List.Count
+ else
+ Result := 0;
+ end;
+end;
+
+//function TJvID3Ipl.GetJob: WideString;
+function TJvID3Ipl.GetJob: String;
+var
+ Frame: TJvID3DoubleListFrame;
+begin
+ if ItemIndex < 0 then
+ Result := ''
+ else
+ begin
+ Frame := TJvID3DoubleListFrame.Find(FController, fiInvolvedPeople);
+ if Assigned(Frame) and (ItemIndex < Frame.List.Count) then
+ Result := Frame.Values[ItemIndex]
+ else
+ Result := '';
+ end;
+end;
+
+//function TJvID3Ipl.GetPerson: WideString;
+function TJvID3Ipl.GetPerson: String;
+var
+ Frame: TJvID3DoubleListFrame;
+begin
+ if ItemIndex < 0 then
+ Result := ''
+ else
+ begin
+ Frame := TJvID3DoubleListFrame.Find(FController, fiInvolvedPeople);
+ if Assigned(Frame) and (ItemIndex < Frame.List.Count) then
+ Result := Frame.List.Names[ItemIndex]
+ else
+ Result := '';
+ end;
+end;
+
+procedure TJvID3Ipl.SetItemIndex(const Value: Integer);
+begin
+ if Value <> FItemIndex then
+ begin
+ FItemIndex := Min(Value, ItemCount - 1);
+ end;
+end;
+
+//procedure TJvID3Ipl.SetJob(const Value: WideString);
+procedure TJvID3Ipl.SetJob(const Value: String);
+var
+// LPerson: WideString;
+ LPerson: String;
+ Frame: TJvID3DoubleListFrame;
+begin
+ if FController.Active and (ItemIndex >= 0) then
+ begin
+ Frame := TJvID3DoubleListFrame.FindOrCreate(FController, fiInvolvedPeople);
+ if (0 <= ItemIndex) and (ItemIndex < Frame.List.Count) then
+ begin
+ LPerson := Frame.List.Names[ItemIndex];
+ Frame.List[ItemIndex] := Format('%s=%s', [LPerson, Value]);
+ end;
+ end;
+end;
+
+//procedure TJvID3Ipl.SetPerson(const Value: WideString);
+procedure TJvID3Ipl.SetPerson(const Value: String);
+var
+ //LJob: WideString;
+ LJob: String;
+ Frame: TJvID3DoubleListFrame;
+begin
+ if FController.Active and (ItemIndex >= 0) then
+ begin
+ Frame := TJvID3DoubleListFrame.FindOrCreate(FController, fiInvolvedPeople);
+ if (0 <= ItemIndex) and (ItemIndex < Frame.List.Count) then
+ begin
+ LJob := Frame.Values[ItemIndex];
+ Frame.List[ItemIndex] := Format('%s=%s', [Value, LJob]);
+ end;
+ end;
+end;
+
+
+//=== { TJvID3Owner } ========================================================
+
+function TJvID3Owner.GetDatePurchased: TDateTime;
+var
+ Frame: TJvID3OwnershipFrame;
+begin
+ Frame := TJvID3OwnershipFrame.Find(FController);
+ if Assigned(Frame) then
+ Result := Frame.DateOfPurch
+ else
+ Result := 0;
+end;
+
+function TJvID3Owner.GetPrice: AnsiString;
+var
+ Frame: TJvID3OwnershipFrame;
+begin
+ Frame := TJvID3OwnershipFrame.Find(FController);
+ if Assigned(Frame) then
+ Result := Frame.PricePayed
+ else
+ Result := '';
+end;
+
+//function TJvID3Owner.GetSeller: WideString;
+function TJvID3Owner.GetSeller: String;
+var
+ Frame: TJvID3OwnershipFrame;
+begin
+ Frame := TJvID3OwnershipFrame.Find(FController);
+ if Assigned(Frame) then
+ Result := Frame.Seller
+ else
+ Result := '';
+end;
+
+procedure TJvID3Owner.SetDatePurchased(const Value: TDateTime);
+begin
+ if FController.Active then
+ TJvID3OwnershipFrame.FindOrCreate(FController).DateOfPurch := Value;
+end;
+
+procedure TJvID3Owner.SetPrice(const Value: AnsiString);
+begin
+ if FController.Active then
+ TJvID3OwnershipFrame.FindOrCreate(FController).PricePayed := Value;
+end;
+
+//procedure TJvID3Owner.SetSeller(const Value: WideString);
+procedure TJvID3Owner.SetSeller(const Value: String);
+begin
+ if FController.Active then
+ TJvID3OwnershipFrame.FindOrCreate(FController).Seller := Value;
+end;
+
+//=== { TJvID3Persistent } ===================================================
+
+constructor TJvID3Persistent.Create(AController: TJvID3Controller);
+begin
+ inherited Create;
+ FController := AController;
+end;
+
+//=== { TJvID3Pictures } =====================================================
+
+constructor TJvID3Pictures.Create(AController: TJvID3Controller);
+var
+ Index: TJvID3PictureType;
+begin
+ inherited Create(AController);
+
+ for Index := Low(TJvID3PictureType) to High(TJvID3PictureType) do
+ begin
+ FPictures[Index] := TPicture.Create;
+ FPictures[Index].OnChange := @PictureChanged;
+ end;
+end;
+
+destructor TJvID3Pictures.Destroy;
+var
+ Index: TJvID3PictureType;
+begin
+ for Index := Low(TJvID3PictureType) to High(TJvID3PictureType) do
+ FPictures[Index].Free;
+ inherited Destroy;
+end;
+
+function TJvID3Pictures.GetPicture(const AType: TJvID3PictureType): TPicture;
+begin
+ Result := FPictures[TJvID3PictureType(AType)];
+end;
+
+procedure TJvID3Pictures.PictureChanged(Sender: TObject);
+var
+ Index: TJvID3PictureType;
+begin
+ if FUpdating then
+ Exit;
+
+ for Index := Low(TJvID3PictureType) to High(TJvID3PictureType) do
+ if FPictures[Index] = Sender then
+ begin
+ PictureToFrame(Index);
+ Exit;
+ end;
+end;
+
+procedure TJvID3Pictures.PictureToFrame(const AType: TJvID3PictureType);
+var
+ Frame: TJvID3PictureFrame;
+begin
+ if not FController.Active then
+ Exit;
+
+ Frame := TJvID3PictureFrame.FindOrCreate(FController, AType);
+ Frame.Assign(FPictures[AType]);
+
+ { Borland has made it hard for us to determine the type of picture; let's
+ just look at the Picture.Graphic classname :) This is no way a reliable
+ method thus I don't recommend using TJvID3v2 for pictures }
+
+ Frame.MIMEType := ExtractMIMETypeFromClassName(FPictures[AType].Graphic.ClassName);
+end;
+
+procedure TJvID3Pictures.RemovePictures;
+var
+ Index: TJvID3PictureType;
+begin
+ FUpdating := True;
+ try
+ for Index := Low(TJvID3PictureType) to High(TJvID3PictureType) do
+ FPictures[Index].Assign(nil);
+ finally
+ FUpdating := False;
+ end;
+end;
+
+procedure TJvID3Pictures.RetrievePictures;
+var
+ Frame: TJvID3PictureFrame;
+ Index: TJvID3PictureType;
+begin
+ FUpdating := True;
+ try
+ for Index := Low(TJvID3PictureType) to High(TJvID3PictureType) do
+ begin
+ Frame := TJvID3PictureFrame.Find(FController, Index);
+ FPictures[Index].Assign(Frame);
+ end;
+ finally
+ FUpdating := False;
+ end;
+end;
+
+procedure TJvID3Pictures.SetPicture(const AType: TJvID3PictureType;
+ const Value: TPicture);
+begin
+ FPictures[TJvID3PictureType(AType)].Assign(Value);
+ //ChangePicture(AType);
+end;
+
+//=== { TJvID3PicturesDesc } =================================================
+
+//function TJvID3PicturesDesc.GetText(const AType: Integer{TJvID3PictureType}): WideString;
+function TJvID3PicturesDesc.GetText(const AType: TJvID3PictureType): String;
+var
+ Frame: TJvID3PictureFrame;
+begin
+ Frame := TJvID3PictureFrame.Find(FController, TJvID3PictureType(AType));
+ if Assigned(Frame) then
+ Result := Frame.Description
+ else
+ Result := '';
+end;
+
+procedure TJvID3PicturesDesc.SetText(const AType: TJvID3PictureType;
+ const Value: String);
+ //const Value: WideString);
+begin
+ if FController.Active then
+ TJvID3PictureFrame.FindOrCreate(FController, TJvID3PictureType(AType)).Description := Value;
+end;
+
+
+//=== { TJvID3Popularimeter } ================================================
+
+function TJvID3Popularimeter.GetCounter: Cardinal;
+var
+ Frame: TJvID3PopularimeterFrame;
+begin
+ Frame := TJvID3PopularimeterFrame.Find(FController);
+ if Assigned(Frame) then
+ Result := Frame.Counter
+ else
+ Result := 0;
+end;
+
+function TJvID3Popularimeter.GetEMailAddress: AnsiString;
+var
+ Frame: TJvID3PopularimeterFrame;
+begin
+ Frame := TJvID3PopularimeterFrame.Find(FController);
+ if Assigned(Frame) then
+ Result := Frame.EMailAddress
+ else
+ Result := '';
+end;
+
+function TJvID3Popularimeter.GetRating: Byte;
+var
+ Frame: TJvID3PopularimeterFrame;
+begin
+ Frame := TJvID3PopularimeterFrame.Find(FController);
+ if Assigned(Frame) then
+ Result := Frame.Rating
+ else
+ Result := 0;
+end;
+
+procedure TJvID3Popularimeter.SetCounter(const Value: Cardinal);
+begin
+ if FController.Active then
+ TJvID3PopularimeterFrame.FindOrCreate(FController).Counter := Value;
+end;
+
+procedure TJvID3Popularimeter.SetEMailAddress(const Value: AnsiString);
+begin
+ if FController.Active then
+ TJvID3PopularimeterFrame.FindOrCreate(FController).EMailAddress := Value;
+end;
+
+procedure TJvID3Popularimeter.SetRating(const Value: Byte);
+begin
+ if FController.Active then
+ TJvID3PopularimeterFrame.FindOrCreate(FController).Rating := Value;
+end;
+
+//=== { TJvID3Text } =========================================================
+
+constructor TJvID3Text.Create(AController: TJvID3Controller);
+begin
+ inherited Create(AController);
+ FDummyList := TStringList.Create;
+ //FDummyList := {$IFDEF COMPILER12_UP}TStringList{$ELSE}TWideStringList{$ENDIF COMPILER12_UP}.Create;
+end;
+
+destructor TJvID3Text.Destroy;
+begin
+ FDummyList.Free;
+ inherited Destroy;
+end;
+
+function TJvID3Text.GetDateTime(const FrameID: TJvID3FrameID): TDateTime;
+var
+ Frame: TJvID3TimestampFrame;
+begin
+ Frame := TJvID3TimestampFrame.Find(FController, TJvID3FrameID(FrameID));
+ if Assigned(Frame) then
+ Result := Frame.Value
+ else
+ Result := 0;
+end;
+
+//function TJvID3Text.GetList(const FrameID: Integer{TJvID3FrameID}): {$IFDEF COMPILER12_UP}TStrings{$ELSE}TWideStrings{$ENDIF COMPILER12_UP};
+function TJvID3Text.GetList(const FrameID: TJvID3FrameID): TStrings;
+begin
+ if FController.Active then
+ Result := TJvID3SimpleListFrame.FindOrCreate(FController, TJvID3FrameID(FrameID)).List
+ else
+ begin
+ Result := FDummyList;
+ Result.Clear;
+ end;
+end;
+
+function TJvID3Text.GetNumber(const FrameID: TJvID3FrameID): Cardinal;
+var
+ Frame: TJvID3NumberFrame;
+begin
+ Frame := TJvID3NumberFrame.Find(FController, TJvID3FrameID(FrameID));
+ if Assigned(Frame) then
+ Result := Frame.Value
+ else
+ Result := 0;
+end;
+
+//function TJvID3Text.GetText(const FrameID: Integer{TJvID3FrameID}): WideString;
+function TJvID3Text.GetText(const FrameID: TJvID3FrameID): String;
+var
+ Frame: TJvID3TextFrame;
+begin
+ Frame := TJvID3TextFrame.Find(FController, TJvID3FrameID(FrameID));
+ if Assigned(Frame) then
+ Result := Frame.Text
+ else
+ Result := '';
+end;
+
+function TJvID3Text.GetBPM: Cardinal;
+var
+ res: Integer;
+begin
+ val(BPMStr, Result, res);
+ if res <> 0 then Result := 0;
+ //Result := Trunc(StrToFloatDef(StringReplace(BPMStr, '.', FormatSettings.DecimalSeparator, []), 0));
+end;
+
+procedure TJvID3Text.SetBPM(const Value: Cardinal);
+begin
+ BPMStr := IntToStr(Value);
+end;
+
+procedure TJvID3Text.SetDateTime(const FrameID: TJvID3FrameID;
+ const Value: TDateTime);
+begin
+ if FController.Active then
+ TJvID3TimestampFrame.FindOrCreate(FController, TJvID3FrameID(FrameID)).Value := Value;
+end;
+
+procedure TJvID3Text.SetList(const FrameID: TJvID3FrameID; const Value: TStrings);
+ //const Value: {$IFDEF COMPILER12_UP}TStrings{$ELSE}TWideStrings{$ENDIF COMPILER12_UP});
+begin
+ if FController.Active then
+ TJvID3SimpleListFrame.FindOrCreate(FController, TJvID3FrameID(FrameID)).List.Assign(Value);
+end;
+
+procedure TJvID3Text.SetNumber(const FrameID: TJvID3FrameID; const Value: Cardinal);
+begin
+ if FController.Active then
+ TJvID3NumberFrame.FindOrCreate(FController, TJvID3FrameID(FrameID)).Value := Value;
+end;
+
+//procedure TJvID3Text.SetText(const FrameID: Integer{TJvID3FrameID}; const Value: WideString);
+procedure TJvID3Text.SetText(const FrameID: TJvID3FrameID; const Value: String);
+begin
+ if FController.Active then
+ TJvID3TextFrame.FindOrCreate(FController, TJvID3FrameID(FrameID)).Text := Value;
+end;
+
+
+//=== { TJvID3UDText } =======================================================
+
+//procedure TJvID3UDText.Add(const ADescription, AValue: WideString);
+procedure TJvID3UDText.Add(const ADescription, AValue: String);
+begin
+ if not Assigned(FController) then
+ ID3Error(RsEID3NoController);
+
+ with TJvID3UserFrame(FController.AddFrame(fiUserText)) do
+ begin
+ Description := ADescription;
+ Value := AValue;
+ end;
+end;
+
+//function TJvID3UDText.GetDescription: WideString;
+function TJvID3UDText.GetDescription: String;
+var
+ Frame: TJvID3UserFrame;
+begin
+ if ItemIndex < 0 then
+ Result := ''
+ else
+ begin
+ Frame := TJvID3UserFrame.Find(FController, ItemIndex);
+ if Assigned(Frame) then
+ Result := Frame.Description
+ else
+ Result := '';
+ end;
+end;
+
+function TJvID3UDText.GetItemCount: Integer;
+begin
+ if not FController.Active then
+ Result := 0
+ else
+ Result := FController.GetFrameCountFor(fiUserText);
+end;
+
+function TJvID3UDText.GetItemIndex: Integer;
+begin
+ if not FController.Active then
+ FItemIndex := -1
+ else
+ FItemIndex := Min(FItemIndex, ItemCount - 1);
+ Result := FItemIndex;
+end;
+
+//function TJvID3UDText.GetValue: WideString;
+function TJvID3UDText.GetValue: String;
+var
+ Frame: TJvID3UserFrame;
+begin
+ if ItemIndex < 0 then
+ Result := ''
+ else
+ begin
+ Frame := TJvID3UserFrame.Find(FController, ItemIndex);
+ if Assigned(Frame) then
+ Result := Frame.Value
+ else
+ Result := '';
+ end;
+end;
+
+//procedure TJvID3UDText.SetDescription(const Value: WideString);
+procedure TJvID3UDText.SetDescription(const Value: String);
+begin
+ if FController.Active and (ItemIndex >= 0) and (ItemIndex < ItemCount) then
+ TJvID3UserFrame.Find(FController, ItemIndex).Description := Value;
+end;
+
+procedure TJvID3UDText.SetItemIndex(const Value: Integer);
+begin
+ if Value <> FItemIndex then
+ FItemIndex := Min(Value, ItemCount - 1);
+end;
+
+//procedure TJvID3UDText.SetValue(const Value: WideString);
+procedure TJvID3UDText.SetValue(const Value: String);
+begin
+ if FController.Active and (ItemIndex >= 0) and (ItemIndex < ItemCount) then
+ TJvID3UserFrame.Find(FController, ItemIndex).Value := Value;
+end;
+
+
+//=== { TJvID3UDUrl } ========================================================
+
+//procedure TJvID3UDUrl.Add(const ADescription: WideString; const AURL: AnsiString);
+procedure TJvID3UDUrl.Add(const ADescription: String; const AURL: AnsiString);
+begin
+ if not Assigned(FController) then
+ ID3Error(RsEID3NoController);
+
+ with TJvID3URLUserFrame(FController.AddFrame(fiWWWUser)) do
+ begin
+ Description := ADescription;
+ URL := AURL;
+ end;
+end;
+
+//function TJvID3UDUrl.GetDescription: WideString;
+function TJvID3UDUrl.GetDescription: String;
+var
+ Frame: TJvID3URLUserFrame;
+begin
+ if ItemIndex < 0 then
+ Result := ''
+ else
+ begin
+ Frame := TJvID3URLUserFrame.Find(FController, ItemIndex);
+ if Assigned(Frame) then
+ Result := Frame.Description
+ else
+ Result := '';
+ end;
+end;
+
+function TJvID3UDUrl.GetItemCount: Integer;
+begin
+ if not FController.Active then
+ Result := 0
+ else
+ Result := FController.GetFrameCountFor(fiWWWUser);
+end;
+
+function TJvID3UDUrl.GetItemIndex: Integer;
+begin
+ if not FController.Active then
+ FItemIndex := -1
+ else
+ FItemIndex := Min(FItemIndex, ItemCount - 1);
+ Result := FItemIndex;
+end;
+
+function TJvID3UDUrl.GetURL: AnsiString;
+var
+ Frame: TJvID3URLUserFrame;
+begin
+ if ItemIndex < 0 then
+ Result := ''
+ else
+ begin
+ Frame := TJvID3URLUserFrame.Find(FController, ItemIndex);
+ if Assigned(Frame) then
+ Result := Frame.URL
+ else
+ Result := '';
+ end;
+end;
+
+//procedure TJvID3UDUrl.SetDescription(const Value: WideString);
+procedure TJvID3UDUrl.SetDescription(const Value: String);
+begin
+ if FController.Active and (ItemIndex >= 0) then
+ TJvID3URLUserFrame.Find(FController, ItemIndex).Description := Value;
+end;
+
+procedure TJvID3UDUrl.SetItemIndex(const Value: Integer);
+begin
+ if Value <> FItemIndex then
+ FItemIndex := Min(Value, ItemCount - 1);
+end;
+
+procedure TJvID3UDUrl.SetURL(const Value: AnsiString);
+begin
+ if FController.Active and (ItemIndex >= 0) then
+ TJvID3URLUserFrame.Find(FController, ItemIndex).URL := Value;
+end;
+
+//=== { TJvID3v2 } ===========================================================
+
+constructor TJvID3v2.Create(AOwner: TComponent);
+begin
+ inherited Create(AOwner);
+ RegisterClient(Self, @ActiveChanged);
+
+ FProcessPictures := True;
+ FID3Text := TJvID3Text.Create(Self);
+ FWeb := TJvID3Web.Create(Self);
+ FUserDefinedText := TJvID3UDText.Create(Self);
+ FUserDefinedWeb := TJvID3UDUrl.Create(Self);
+ FInvolvedPeople := TJvID3Ipl.Create(Self);
+ FImages := TJvID3Images.Create(Self);
+ FOwner := TJvID3Owner.Create(Self);
+ FPopularimeter := TJvID3Popularimeter.Create(Self);
+
+ WriteEncodingAs := ifeAuto;
+ ReadEncodingAs := ifeAuto;
+
+ Options := [coAutoCorrect, coRemoveEmptyFrames];
+end;
+
+destructor TJvID3v2.Destroy;
+begin
+ UnRegisterClient(Self);
+
+ FID3Text.Free;
+ FWeb.Free;
+ FUserDefinedText.Free;
+ FUserDefinedWeb.Free;
+ FInvolvedPeople.Free;
+ FImages.Free;
+ FOwner.Free;
+ FPopularimeter.Free;
+ inherited Destroy;
+end;
+
+procedure TJvID3v2.ActiveChanged(Sender: TObject; Activated: Boolean);
+begin
+ if FProcessPictures then
+ begin
+ if Activated then
+ FImages.Pictures.RetrievePictures
+ else
+ FImages.Pictures.RemovePictures;
+ end;
+end;
+
+function TJvID3v2.GetPlayCounter: Cardinal;
+var
+ Frame: TJvID3PlayCounterFrame;
+begin
+ Frame := TJvID3PlayCounterFrame.Find(Self);
+ if Assigned(Frame) then
+ Result := Frame.Counter
+ else
+ Result := 0;
+end;
+
+procedure TJvID3v2.SetPlayCounter(const Value: Cardinal);
+begin
+ if Active then
+ TJvID3PlayCounterFrame.FindOrCreate(Self).Counter := Value;
+end;
+
+//=== { TJvID3Web } ==========================================================
+
+function TJvID3Web.GetText(const FrameID: TJvID3FrameID): AnsiString;
+var
+ Frame: TJvID3URLFrame;
+begin
+ Frame := TJvID3URLFrame.Find(FController, TJvID3FrameID(FrameID));
+ if Assigned(Frame) then
+ Result := Frame.URL
+ else
+ Result := '';
+end;
+
+procedure TJvID3Web.SetText(const FrameID: TJvID3FrameID; const Value: AnsiString);
+begin
+ if FController.Active then
+ TJvID3URLFrame.FindOrCreate(FController, TJvID3FrameID(FrameID)).URL := Value;
+end;
+
+
+end.
diff --git a/components/jvcllaz/run/JvMM/JvId3v2Types.pas b/components/jvcllaz/run/JvMM/JvId3v2Types.pas
new file mode 100644
index 000000000..3397a4da7
--- /dev/null
+++ b/components/jvcllaz/run/JvMM/JvId3v2Types.pas
@@ -0,0 +1,1589 @@
+{-----------------------------------------------------------------------------
+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: JvID3v2Types.PAS, released on 2001-02-28.
+
+The Initial Developer of the Original Code is Sébastien Buysse [sbuysse att buypin dott com]
+Portions created by Sébastien Buysse are Copyright (C) 2001 Sébastien Buysse.
+All Rights Reserved.
+
+Contributor(s):
+ Michael Beck [mbeck att bigfoot dott com].
+ Remko Bonte [remkobonte att myrealbox dott com].
+
+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:
+-----------------------------------------------------------------------------}
+// $Id$
+
+unit JvId3v2Types;
+
+
+interface
+
+uses
+ Classes;
+
+type
+ TJvID3TagSizeRestriction = (tsrMax128Frames_1MB, tsrMax64Frames_128KB, tsrMax32Frames_40KB,
+ tsrMax32Frames_4KB);
+ TJvID3TextEncodingRestriction = (terNoRestrictions, terOnlyISO_8859_1orUTF8);
+ TJvID3TextFieldsSizeRestriction = (tfszNoRestrictions, tfszMax1024Char, tfszMax128Char,
+ tfszMax30Char);
+ TJvID3ImageEncodingRestriction = (ierNoRestrictions, ierOnlyPNGorJPEG);
+ TJvID3ImageSizeRestriction = (isrNoRestrictions, isrMax256x256, isrMax64x64, isr64x64UnlessRequired);
+
+ TJvID3Restrictions = record
+ RTagSize: TJvID3TagSizeRestriction;
+ RTextEncoding: TJvID3TextEncodingRestriction;
+ RTextFieldsSize: TJvID3TextFieldsSizeRestriction;
+ RImageEncoding: TJvID3ImageEncodingRestriction;
+ RImageSize: TJvID3ImageSizeRestriction;
+ end;
+
+ TJvID3HeaderExtendedFlag = (hefTagIsAnUpdate, hefCRCDataPresent, hefTagRestrictions);
+ TJvID3HeaderExtendedFlags = set of TJvID3HeaderExtendedFlag;
+
+ TJvID3HeaderFlag = (hfUnsynchronisation, hfExtendedHeader, hfExperimentalIndicator, hfFooterPresent);
+ TJvID3HeaderFlags = set of TJvID3HeaderFlag;
+
+ TJvID3FrameHeaderFlag = (fhfOnTagAlterDiscardFrame, fhfOnFileAlterDiscardFrame,
+ fhfReadOnly, fhfIsCompressed, fhfIsEncrypted, fhfContainsGroupInformation, fhfUnsynchronisationApplied,
+ fhfDataLengthIndicator);
+ TJvID3FrameHeaderFlags = set of TJvID3FrameHeaderFlag;
+
+ { $00 ISO-8859-1 [ISO-8859-1]. Terminated with $00.
+ $01 UTF-16 [UTF-16] encoded Unicode [UNICODE] with BOM. All
+ strings in the same frame SHALL have the same byteorder.
+ Terminated with $00 00.
+ $02 UTF-16BE [UTF-16] encoded Unicode [UNICODE] without BOM.
+ Terminated with $00 00.
+ $03 UTF-8 [UTF-8] encoded Unicode [UNICODE]. Terminated with $00.
+ }
+
+ TJvID3ForceEncoding = (ifeDontCare, ifeISO_8859_1, ifeUTF_16, ifeUTF_16BE, ifeUTF_8, ifeAuto);
+ TJvID3Encoding = (ienISO_8859_1, ienUTF_16, ienUTF_16BE, ienUTF_8);
+ TJvID3Encodings = set of TJvID3Encoding;
+
+ TJvID3ForceVersion = (ifvDontCare, ifv2_2, ifv2_3, ifv2_4);
+ TJvID3Version = (iveLowerThan2_2, ive2_2, ive2_3, ive2_4, iveHigherThan2_4);
+
+const
+ CForceEncodingToEncoding: array [TJvID3ForceEncoding] of TJvID3Encoding =
+ (ienISO_8859_1, ienISO_8859_1, ienUTF_16, ienUTF_16BE, ienUTF_8, ienISO_8859_1);
+ CForceVersionToVersion: array [TJvID3ForceVersion] of TJvID3Version =
+ (ive2_3, ive2_2, ive2_3, ive2_4);
+
+type
+ TID3v2HeaderRec = packed record
+ Identifier: array [0..2] of AnsiChar;
+ MajorVersion: Byte;
+ RevisionNumber: Byte;
+ Flags: Byte;
+ Size: Cardinal;
+ end;
+
+ TID3v2FrameRec = packed record
+ ID: array [0..3] of AnsiChar;
+ // (rom) changed to Cardinal sizes are usually unsigned
+ Size: Cardinal;
+ Flag0: Byte;
+ Flag1: Byte;
+ end;
+
+ TJvID3PictureType = (
+ ptOther, { Other }
+ ptFileIcon, { 32x32 pixels 'file icon' (PNG only) }
+ ptOtherFileIcon, { Other file icon }
+ ptCoverFront, { Cover (front) }
+ ptCoverBack, { Cover (back) }
+ ptLeafletPage, { Leaflet page }
+ ptMedia, { Media (e.g. lable side of CD) }
+ ptLeadArtist, { Lead artist/lead performer/soloist }
+ ptArtist, { Artist/performer }
+ ptConductor, { Conductor }
+ ptBand, { Band/Orchestra }
+ ptComposer, { Composer }
+ ptLyricist, { Lyricist/text writer }
+ ptRecordingLocation, { Recording Location }
+ ptDuringRecording, { During recording }
+ ptDuringPerformance, { During performance }
+ ptMovieVideoScreenCapture, { Movie/video screen capture }
+ ptBrightColouredFish, { A bright coloured fish }
+ ptIllustration, { Illustration }
+ ptBandLogotype, { Band/artist logotype }
+ ptPublisherLogotype { Publisher/Studio logotype }
+ );
+
+ TJvID3FrameID =
+ (
+ { ---- } fiErrorFrame, { Erroneous frame, ie chars not in [a..z, A..Z, 0..9] }
+ { #0 } fiPaddingFrame, { Padding }
+ { ???? } fiUnknownFrame, { No known frame }
+ { AENC } fiAudioCrypto, { Audio encryption }
+ { APIC } fiPicture, { Attached picture }
+ { ASPI } fiAudioSeekPoint, { Audio seek point index }
+ { COMM } fiComment, { Comments }
+ { COMR } fiCommercial, { Commercial frame }
+ { ENCR } fiCryptoReg, { Encryption method registration }
+ { EQU2 } fiEqualization2, { Equalisation (2) }
+ { EQUA } fiEqualization, { Equalization }
+ { ETCO } fiEventTiming, { Event timing codes }
+ { GEOB } fiGeneralObject, { General encapsulated object }
+ { GRID } fiGroupingReg, { Group identification registration }
+ { IPLS } fiInvolvedPeople, { Involved people list }
+ { LINK } fiLinkedInfo, { Linked information }
+ { MCDI } fiCDID, { Music CD identifier }
+ { MLLT } fiMPEGLookup, { MPEG location lookup table }
+ { OWNE } fiOwnership, { Ownership frame }
+ { PRIV } fiPrivate, { Private frame }
+ { PCNT } fiPlayCounter, { Play counter }
+ { POPM } fiPopularimeter, { Popularimeter }
+ { POSS } fiPositionsync, { Position synchronisation frame }
+ { RBUF } fiBufferSize, { Recommended buffer size }
+ { RVA2 } fiVolumeAdj2, { Relative volume adjustment (2) }
+ { RVAD } fiVolumeAdj, { Relative volume adjustment }
+ { RVRB } fiReverb, { Reverb }
+ { SEEK } fiSeekFrame, { Seek frame }
+ { SIGN } fiSignature, { Signature frame }
+ { SYLT } fiSyncedLyrics, { Synchronized lyric/text }
+ { SYTC } fiSyncedTempo, { Synchronized tempo codes }
+ { TALB } fiAlbum, { Album/Movie/Show title }
+ { TBPM } fiBPM, { BPM (beats per minute) }
+ { TCOM } fiComposer, { Composer }
+ { TCON } fiContentType, { Content type }
+ { TCOP } fiCopyright, { Copyright message }
+ { TDAT } fiDate, { Date }
+ { TDEN } fiEncodingTime, { Encoding time }
+ { TDLY } fiPlaylistDelay, { Playlist delay }
+ { TDOR } fiOrigReleaseTime, { Original release time }
+ { TDRC } fiRecordingTime, { Recording time }
+ { TDRL } fiReleaseTime, { Release time }
+ { TDTG } fiTaggingTime, { Tagging time }
+ { TIPL } fiInvolvedPeople2, { Involved people list }
+ { TENC } fiEncodedBy, { Encoded by }
+ { TEXT } fiLyricist, { Lyricist/Text writer }
+ { TFLT } fiFileType, { File type }
+ { TIME } fiTime, { Time }
+ { TIT1 } fiContentGroup, { Content group description }
+ { TIT2 } fiTitle, { Title/songname/content description }
+ { TIT3 } fiSubTitle, { Subtitle/Description refinement }
+ { TKEY } fiInitialKey, { Initial key }
+ { TLAN } fiLanguage, { Language(s) }
+ { TLEN } fiSongLen, { Length }
+ { TMCL } fiMusicianCreditList, { Musician credits list }
+ { TMED } fiMediaType, { Media type }
+ { TMOO } fiMood, { Mood }
+ { TOAL } fiOrigAlbum, { Original album/movie/show title }
+ { TOFN } fiOrigFileName, { Original filename }
+ { TOLY } fiOrigLyricist, { Original lyricist(s)/text writer(s) }
+ { TOPE } fiOrigArtist, { Original artist(s)/performer(s) }
+ { TORY } fiOrigYear, { Original release year }
+ { TOWN } fiFileOwner, { File owner/licensee }
+ { TPE1 } fiLeadArtist, { Lead performer(s)/Soloist(s) }
+ { TPE2 } fiBand, { Band/orchestra/accompaniment }
+ { TPE3 } fiConductor, { Conductor/performer refinement }
+ { TPE4 } fiMixArtist, { Interpreted, remixed, or otherwise modified by }
+ { TPOS } fiPartInSet, { Part of a set }
+ { TPRO } fiProducedNotice, { Produced notice }
+ { TPUB } fiPublisher, { Publisher }
+ { TRCK } fiTrackNum, { Track number/Position in set }
+ { TRDA } fiRecordingDates, { Recording dates }
+ { TRSN } fiNetRadioStation, { Internet radio station name }
+ { TRSO } fiNetRadioOwner, { Internet radio station owner }
+ { TSIZ } fiSize, { Size }
+ { TSOA } fiAlbumSortOrder, { Album sort order }
+ { TSOP } fiPerformerSortOrder, { Performer sort order }
+ { TSOT } fiTitleSortOrder, { Title sort order }
+ { TSRC } fiISRC, { ISRC (international standard recording code) }
+ { TSSE } fiEncoderSettings, { Software/Hardware and settings used for encoding }
+ { TSST } fiSetSubTitle, { Set subtitle }
+ { TXXX } fiUserText, { User defined text information }
+ { TYER } fiYear, { Year }
+ { UFID } fiUniqueFileID, { Unique file identifier }
+ { USER } fiTermsOfUse, { Terms of use }
+ { USLT } fiUnsyncedLyrics, { Unsynchronized lyric/text transcription }
+ { WCOM } fiWWWCommercialInfo, { Commercial information }
+ { WCOP } fiWWWCopyright, { Copyright/Legal information }
+ { WOAF } fiWWWAudioFile, { Official audio file webpage }
+ { WOAR } fiWWWArtist, { Official artist/performer webpage }
+ { WOAS } fiWWWAudioSource, { Official audio source webpage }
+ { WORS } fiWWWRadioPage, { Official internet radio station homepage }
+ { WPAY } fiWWWPayment, { Payment }
+ { WPUB } fiWWWPublisher, { Official publisher webpage }
+ { WXXX } fiWWWUser, { User defined URL link }
+ { } fiMetaCrypto, { Encrypted meta frame (ID3v2.2.x) }
+ { } fiMetaCompression { Compressed meta frame (ID3v2.2.1) }
+ );
+ TJvID3FrameIDs = set of TJvID3FrameID;
+
+{ Frame ID procedures }
+function ID3_StringToFrameID(const S: AnsiString): TJvID3FrameID;
+function ID3_FrameIDToString(const ID: TJvID3FrameID; const Size: Integer = 4): AnsiString;
+
+{ Genre procedures }
+function ID3_GenreToID(const AGenre: string; const InclWinampGenres: Boolean = True): Integer;
+{ searches for a genre that is a prefix for AGenreLong }
+function ID3_LongGenreToID(const ALongGenre: string; const InclWinampGenres: Boolean = True): Integer;
+function ID3_IDToGenre(const ID: Integer; const InclWinampGenres: Boolean = True): string;
+procedure ID3_Genres(Strings: TStrings; const InclWinampGenres: Boolean = True);
+
+{ Language ISO 639-2 procedures }
+function ISO_639_2IsCode(const Code: AnsiString): Boolean;
+function ISO_639_2CodeToName(const Code: AnsiString): AnsiString;
+{ Known problem: some codes such as 'dut' and 'nld', have the same name value,
+ thus ISO_639_2NameToCode('Dutch') = 'dut' not 'nld' }
+function ISO_639_2NameToCode(const Name: string): AnsiString;
+procedure ISO_639_2Names(Strings: TStrings);
+
+
+implementation
+
+uses
+ Math, SysUtils,
+ (*
+ {$IFDEF SUPPORTS_INLINE}
+ Windows,
+ {$ENDIF SUPPORTS_INLINE}
+ {$IFDEF HAS_UNIT_ANSISTRINGS}
+ AnsiStrings,
+ {$ENDIF HAS_UNIT_ANSISTRINGS}
+ *)
+ JvJCLUtils,
+ JvConsts, JvResources, JvTypes;
+
+type
+ TJvListType =
+ (ltID3LongText, ltID3ShortText, ltISO_639_2Code, ltISO_639_2Name, ltID3Genres);
+
+ TJvID3FrameDef = packed record
+ ShortTextID: array [0..2] of AnsiChar;
+ LongTextID: array [0..3] of AnsiChar;
+ end;
+
+ { Note: When you change type of S or L to 'string' it will increase the exe size
+ with minimal 475x8 bytes }
+
+ TShortToLongName = record
+ S: array [0..2] of AnsiChar;
+ L: PAnsiChar;
+ end;
+
+ TJvID3TermFinder = class
+ private
+ FLists: array [TJvListType] of TStringList;
+ protected
+ procedure BuildList_ISO_639_2Name;
+ procedure BuildList_ISO_639_2Code;
+ procedure BuildList_ID3LongText;
+ procedure BuildList_ID3ShortText;
+ procedure BuildList_ID3Genres;
+ function IsFrameOk(const S: AnsiString): Boolean;
+ public
+ constructor Create; virtual;
+ destructor Destroy; override;
+
+ function ID3LongTextToFrameID(const S: AnsiString): TJvID3FrameID;
+ function ID3ShortTextToFrameID(const S: AnsiString): TJvID3FrameID;
+
+ function ID3GenreToID(const AGenre: string; const InclWinampGenres: Boolean): Integer;
+ procedure ID3Genres(AStrings: TStrings; const InclWinampGenres: Boolean);
+ function ID3LongGenreToID(const ALongGenre: string; const InclWinampGenres: Boolean): Integer;
+
+ function ISO_639_2CodeToIndex(const ACode: AnsiString): Integer;
+ function ISO_639_2NameToIndex(const AName: string): Integer;
+ procedure ISO_639_2Names(AStrings: TStrings);
+ end;
+
+const
+ CID3FrameDefs: array [TJvID3FrameID] of TJvID3FrameDef = ( { Ver. 2 3 4 }
+ (ShortTextID: ''; LongTextID: '' ), { fiErrorFrame - - - }
+ (ShortTextID: ''; LongTextID: '' ), { fiPaddingFrame - - - }
+ (ShortTextID: ''; LongTextID: '' ), { fiUnknownFrame - - - }
+ (ShortTextID: 'CRA'; LongTextID: 'AENC'), { fiAudioCrypto X X X }
+ (ShortTextID: 'PIC'; LongTextID: 'APIC'), { fiPicture X X X }
+ (ShortTextID: ''; LongTextID: 'ASPI'), { fiAudioSeekPoint - - X }
+ (ShortTextID: 'COM'; LongTextID: 'COMM'), { fiComment X X X }
+ (ShortTextID: ''; LongTextID: 'COMR'), { fiCommercial - X X }
+ (ShortTextID: ''; LongTextID: 'ENCR'), { fiCryptoReg - X X }
+ (ShortTextID: ''; LongTextID: 'EQU2'), { fiEqualization2 - - X }
+ (ShortTextID: 'EQU'; LongTextID: 'EQUA'), { fiEqualization X X d }
+ (ShortTextID: 'ETC'; LongTextID: 'ETCO'), { fiEventTiming X X X }
+ (ShortTextID: 'GEO'; LongTextID: 'GEOB'), { fiGeneralObject X X X }
+ (ShortTextID: ''; LongTextID: 'GRID'), { fiGroupingReg - X X }
+ (ShortTextID: 'IPL'; LongTextID: 'IPLS'), { fiInvolvedPeople X X d }
+ (ShortTextID: 'LNK'; LongTextID: 'LINK'), { fiLinkedInfo X X X }
+ (ShortTextID: 'MCI'; LongTextID: 'MCDI'), { fiCDID X X X }
+ (ShortTextID: 'MLL'; LongTextID: 'MLLT'), { fiMPEGLookup X X X }
+ (ShortTextID: ''; LongTextID: 'OWNE'), { fiOwnership - X X }
+ (ShortTextID: ''; LongTextID: 'PRIV'), { fiPrivate - X X }
+ (ShortTextID: 'CNT'; LongTextID: 'PCNT'), { fiPlayCounter X X X }
+ (ShortTextID: 'POP'; LongTextID: 'POPM'), { fiPopularimeter X X X }
+ (ShortTextID: ''; LongTextID: 'POSS'), { fiPositionsync - X X }
+ (ShortTextID: 'BUF'; LongTextID: 'RBUF'), { fiBufferSize X X X }
+ (ShortTextID: ''; LongTextID: 'RVA2'), { fiVolumeAdj2 - - X }
+ (ShortTextID: 'RVA'; LongTextID: 'RVAD'), { fiVolumeAdj X X d }
+ (ShortTextID: 'REV'; LongTextID: 'RVRB'), { fiReverb X X X }
+ (ShortTextID: ''; LongTextID: 'SEEK'), { fiSeekFrame - - X }
+ (ShortTextID: ''; LongTextID: 'SIGN'), { fiSignature - - X }
+ (ShortTextID: 'SLT'; LongTextID: 'SYLT'), { fiSyncedLyrics X X X }
+ (ShortTextID: 'STC'; LongTextID: 'SYTC'), { fiSyncedTempo X X X }
+ (ShortTextID: 'TAL'; LongTextID: 'TALB'), { fiAlbum X X X }
+ (ShortTextID: 'TBP'; LongTextID: 'TBPM'), { fiBPM X X X }
+ (ShortTextID: 'TCM'; LongTextID: 'TCOM'), { fiComposer X X X }
+ (ShortTextID: 'TCO'; LongTextID: 'TCON'), { fiContentType X X X }
+ (ShortTextID: 'TCR'; LongTextID: 'TCOP'), { fiCopyright X X X }
+ (ShortTextID: 'TDA'; LongTextID: 'TDAT'), { fiDate X X d }
+ (ShortTextID: ''; LongTextID: 'TDEN'), { fiEncodingTime - - X }
+ (ShortTextID: 'TDY'; LongTextID: 'TDLY'), { fiPlaylistDelay X X X }
+ (ShortTextID: ''; LongTextID: 'TDOR'), { fiOrigReleaseTime - - X }
+ (ShortTextID: ''; LongTextID: 'TDRC'), { fiRecordingTime - - X }
+ (ShortTextID: ''; LongTextID: 'TDRL'), { fiReleaseTime - - X }
+ (ShortTextID: ''; LongTextID: 'TDTG'), { fiTaggingTime - - X }
+ (ShortTextID: ''; LongTextID: 'TIPL'), { fiInvolvedPeople2 - - X }
+ (ShortTextID: 'TEN'; LongTextID: 'TENC'), { fiEncodedBy X X X }
+ (ShortTextID: 'TXT'; LongTextID: 'TEXT'), { fiLyricist X X X }
+ (ShortTextID: 'TFT'; LongTextID: 'TFLT'), { fiFileType X X X }
+ (ShortTextID: 'TIM'; LongTextID: 'TIME'), { fiTime X X d }
+ (ShortTextID: 'TT1'; LongTextID: 'TIT1'), { fiContentGroup X X X }
+ (ShortTextID: 'TT2'; LongTextID: 'TIT2'), { fiTitle X X X }
+ (ShortTextID: 'TT3'; LongTextID: 'TIT3'), { fiSubTitle X X X }
+ (ShortTextID: 'TKE'; LongTextID: 'TKEY'), { fiInitialKey X X X }
+ (ShortTextID: 'TLA'; LongTextID: 'TLAN'), { fiLanguage X X X }
+ (ShortTextID: 'TLE'; LongTextID: 'TLEN'), { fiSongLen X X X }
+ (ShortTextID: ''; LongTextID: 'TMCL'), { fiMusicianCreditList - - X }
+ (ShortTextID: 'TMT'; LongTextID: 'TMED'), { fiMediaType X X X }
+ (ShortTextID: ''; LongTextID: 'TMOO'), { fiMood - - X }
+ (ShortTextID: 'TOT'; LongTextID: 'TOAL'), { fiOrigAlbum X X X }
+ (ShortTextID: 'TOF'; LongTextID: 'TOFN'), { fiOrigFileName X X X }
+ (ShortTextID: 'TOL'; LongTextID: 'TOLY'), { fiOrigLyricist X X X }
+ (ShortTextID: 'TOA'; LongTextID: 'TOPE'), { fiOrigArtist X X X }
+ (ShortTextID: 'TOR'; LongTextID: 'TORY'), { fiOrigYear X X d }
+ (ShortTextID: ''; LongTextID: 'TOWN'), { fiFileOwner - X X }
+ (ShortTextID: 'TP1'; LongTextID: 'TPE1'), { fiLeadArtist X X X }
+ (ShortTextID: 'TP2'; LongTextID: 'TPE2'), { fiBand X X X }
+ (ShortTextID: 'TP3'; LongTextID: 'TPE3'), { fiConductor X X X }
+ (ShortTextID: 'TP4'; LongTextID: 'TPE4'), { fiMixArtist X X X }
+ (ShortTextID: 'TPA'; LongTextID: 'TPOS'), { fiPartInSet X X X }
+ (ShortTextID: ''; LongTextID: 'TPRO'), { fiProducedNotice - - X }
+ (ShortTextID: 'TPB'; LongTextID: 'TPUB'), { fiPublisher X X X }
+ (ShortTextID: 'TRK'; LongTextID: 'TRCK'), { fiTrackNum X X X }
+ (ShortTextID: 'TRD'; LongTextID: 'TRDA'), { fiRecordingDates X X d }
+ (ShortTextID: 'TRN'; LongTextID: 'TRSN'), { fiNetRadioStation X X X }
+ (ShortTextID: 'TRO'; LongTextID: 'TRSO'), { fiNetRadioOwner X X X }
+ (ShortTextID: 'TSI'; LongTextID: 'TSIZ'), { fiSize X X d }
+ (ShortTextID: ''; LongTextID: 'TSOA'), { fiAlbumSortOrder - - X }
+ (ShortTextID: ''; LongTextID: 'TSOP'), { fiPerformerSortOrder - - X }
+ (ShortTextID: ''; LongTextID: 'TSOT'), { fiTitleSortOrder - - X }
+ (ShortTextID: 'TRC'; LongTextID: 'TSRC'), { fiISRC X X X }
+ (ShortTextID: ''; LongTextID: 'TSSE'), { fiEncoderSettings - X X }
+ (ShortTextID: 'TSS'; LongTextID: 'TSST'), { fiSetSubTitle - - X }
+ (ShortTextID: 'TXX'; LongTextID: 'TXXX'), { fiUserText X X X }
+ (ShortTextID: 'TYE'; LongTextID: 'TYER'), { fiYear X X d }
+ (ShortTextID: 'UFI'; LongTextID: 'UFID'), { fiUniqueFileID X X X }
+ (ShortTextID: ''; LongTextID: 'USER'), { fiTermsOfUse - X X }
+ (ShortTextID: 'ULT'; LongTextID: 'USLT'), { fiUnsyncedLyrics X X X }
+ (ShortTextID: 'WCM'; LongTextID: 'WCOM'), { fiWWWCommercialInfo X X X }
+ (ShortTextID: 'WCP'; LongTextID: 'WCOP'), { fiWWWCopyright X X X }
+ (ShortTextID: 'WAF'; LongTextID: 'WOAF'), { fiWWWAudioFile X X X }
+ (ShortTextID: 'WAR'; LongTextID: 'WOAR'), { fiWWWArtist X X X }
+ (ShortTextID: 'WAS'; LongTextID: 'WOAS'), { fiWWWAudioSource X X X }
+ (ShortTextID: 'WRA'; LongTextID: 'WORS'), { fiWWWRadioPage X X X }
+ (ShortTextID: 'WPY'; LongTextID: 'WPAY'), { fiWWWPayment X X X }
+ (ShortTextID: 'WPB'; LongTextID: 'WPUB'), { fiWWWPublisher X X X }
+ (ShortTextID: 'WXX'; LongTextID: 'WXXX'), { fiWWWUser X X X }
+ (ShortTextID: 'CRM'; LongTextID: '' ), { fiMetaCrypto X - - }
+ (ShortTextID: 'CDM'; LongTextID: '' ) { fiMetaCompressio X - - }
+ );
+
+ { http://www.loc.gov/standards/iso639-2/englangn.html }
+
+ CISO_639_2Data: array [0..474] of TShortToLongName =
+ (
+ {0}(S: 'aar'; L: 'Afar'),
+ (S: 'abk'; L: 'Abkhazian'),
+ (S: 'ace'; L: 'Achinese'),
+ (S: 'ach'; L: 'Acoli'),
+ (S: 'ada'; L: 'Adangme'),
+ (S: 'afa'; L: 'Afro-Asiatic (Other)'),
+ (S: 'afh'; L: 'Afrihili'),
+ (S: 'afr'; L: 'Afrikaans'),
+ (S: 'aka'; L: 'Akan'),
+ (S: 'akk'; L: 'Akkadian'),
+ {10}(S: 'alb'; L: 'Albanian'), // Also 'sqi'
+ (S: 'ale'; L: 'Aleut'),
+ (S: 'alg'; L: 'Algonquian languages'),
+ (S: 'amh'; L: 'Amharic'),
+ (S: 'ang'; L: 'English, Old (ca.450-1100)'),
+ (S: 'apa'; L: 'Apache languages'),
+ (S: 'ara'; L: 'Arabic'),
+ (S: 'arc'; L: 'Aramaic'),
+ (S: 'arg'; L: 'Aragonese'),
+ (S: 'arm'; L: 'Armenian'), // Also 'hye'
+ {20}(S: 'arn'; L: 'Araucanian'),
+ (S: 'arp'; L: 'Arapaho'),
+ (S: 'art'; L: 'Artificial (Other)'),
+ (S: 'arw'; L: 'Arawak'),
+ (S: 'asm'; L: 'Assamese'),
+ (S: 'ast'; L: 'Asturian; Bable'),
+ (S: 'ath'; L: 'Athapascan languages'),
+ (S: 'aus'; L: 'Australian languages'),
+ (S: 'ava'; L: 'Avaric'),
+ (S: 'ave'; L: 'Avestan'),
+ {30}(S: 'awa'; L: 'Awadhi'),
+ (S: 'aym'; L: 'Aymara'),
+ (S: 'aze'; L: 'Azerbaijani'),
+ (S: 'bad'; L: 'Banda'),
+ (S: 'bai'; L: 'Bamileke languages'),
+ (S: 'bak'; L: 'Bashkir'),
+ (S: 'bal'; L: 'Baluchi'),
+ (S: 'bam'; L: 'Bambara'),
+ (S: 'ban'; L: 'Balinese'),
+ (S: 'baq'; L: 'Basque'), // Also 'eus'
+ {40}(S: 'bas'; L: 'Basa'),
+ (S: 'bat'; L: 'Baltic (Other)'),
+ (S: 'bej'; L: 'Beja'),
+ (S: 'bel'; L: 'Belarusian'),
+ (S: 'bem'; L: 'Bemba'),
+ (S: 'ben'; L: 'Bengali'),
+ (S: 'ber'; L: 'Berber (Other)'),
+ (S: 'bho'; L: 'Bhojpuri'),
+ (S: 'bih'; L: 'Bihari'),
+ (S: 'bik'; L: 'Bikol'),
+ {50}(S: 'bin'; L: 'Bini'),
+ (S: 'bis'; L: 'Bislama'),
+ (S: 'bla'; L: 'Siksika'),
+ (S: 'bnt'; L: 'Bantu (Other)'),
+ (S: 'bod'; L: 'Tibetan'), // Also 'tib'
+ (S: 'bos'; L: 'Bosnian'),
+ (S: 'bra'; L: 'Braj'),
+ (S: 'bre'; L: 'Breton'),
+ (S: 'btk'; L: 'Batak (Indonesia)'),
+ (S: 'bua'; L: 'Buriat'),
+ {60}(S: 'bug'; L: 'Buginese'),
+ (S: 'bul'; L: 'Bulgarian'),
+ (S: 'bur'; L: 'Burmese'), // Also 'mya'
+ (S: 'cad'; L: 'Caddo'),
+ (S: 'cai'; L: 'Central American Indian (Other)'),
+ (S: 'car'; L: 'Carib'),
+ (S: 'cat'; L: 'Catalan'),
+ (S: 'cau'; L: 'Caucasian (Other)'),
+ (S: 'ceb'; L: 'Cebuano'),
+ (S: 'cel'; L: 'Celtic (Other)'),
+ {70}(S: 'ces'; L: 'Czech'), // Also 'cze'
+ (S: 'cha'; L: 'Chamorro'),
+ (S: 'chb'; L: 'Chibcha'),
+ (S: 'che'; L: 'Chechen'),
+ (S: 'chg'; L: 'Chagatai'),
+ (S: 'chi'; L: 'Chinese'), // Also 'zho'
+ (S: 'chk'; L: 'Chuukese'),
+ (S: 'chm'; L: 'Mari'),
+ (S: 'chn'; L: 'Chinook jargon'),
+ (S: 'cho'; L: 'Choctaw'),
+ {80}(S: 'chp'; L: 'Chipewyan'),
+ (S: 'chr'; L: 'Cherokee'),
+ (S: 'chu'; L: 'Old Bulgarian'),
+ (S: 'chv'; L: 'Chuvash'),
+ (S: 'chy'; L: 'Cheyenne'),
+ (S: 'cmc'; L: 'Chamic languages'),
+ (S: 'cop'; L: 'Coptic'),
+ (S: 'cor'; L: 'Cornish'),
+ (S: 'cos'; L: 'Corsican'),
+ (S: 'cpe'; L: 'Creoles and pidgins, English-based (Other)'),
+ {90}(S: 'cpf'; L: 'Creoles and pidgins, French-based (Other)'),
+ (S: 'cpp'; L: 'Creoles and pidgins, Portuguese-based (Other)'),
+ (S: 'cre'; L: 'Cree'),
+ (S: 'crp'; L: 'Creoles and pidgins(Other)'),
+ (S: 'cus'; L: 'Cushitic (Other)'),
+ (S: 'cym'; L: 'Welsh'), // Also 'wel'
+ (S: 'cze'; L: 'Czech'), // Also 'ces'
+ (S: 'dak'; L: 'Dakota'),
+ (S: 'dan'; L: 'Danish'),
+ (S: 'dar'; L: 'Dargwa'),
+ {100}(S: 'day'; L: 'Dayak'),
+ (S: 'del'; L: 'Delaware'),
+ (S: 'den'; L: 'Slave (Athapascan)'),
+ (S: 'deu'; L: 'German'), // Also 'ger'
+ (S: 'dgr'; L: 'Dogrib'),
+ (S: 'din'; L: 'Dinka'),
+ (S: 'div'; L: 'Divehi'),
+ (S: 'doi'; L: 'Dogri'),
+ (S: 'dra'; L: 'Dravidian (Other)'),
+ (S: 'dua'; L: 'Duala'),
+ {110}(S: 'dum'; L: 'Dutch, Middle (ca. 1050-1350)'),
+ (S: 'dut'; L: 'Dutch'), // Also 'nld'
+ (S: 'dyu'; L: 'Dyula'),
+ (S: 'dzo'; L: 'Dzongkha'),
+ (S: 'efi'; L: 'Efik'),
+ (S: 'egy'; L: 'Egyptian (Ancient)'),
+ (S: 'eka'; L: 'Ekajuk'),
+ (S: 'ell'; L: 'Greek, Modern (1453-)'), // Also 'gre'
+ (S: 'elx'; L: 'Elamite'),
+ (S: 'eng'; L: 'English'),
+ {120}(S: 'enm'; L: 'English, Middle (1100-1500)'),
+ (S: 'epo'; L: 'Esperanto'),
+ (S: 'est'; L: 'Estonian'),
+ (S: 'eus'; L: 'Basque'), // Also 'baq'
+ (S: 'ewe'; L: 'Ewe'),
+ (S: 'ewo'; L: 'Ewondo'),
+ (S: 'fan'; L: 'Fang'),
+ (S: 'fao'; L: 'Faroese'),
+ (S: 'fas'; L: 'Persian'), // Also 'per'
+ (S: 'fat'; L: 'Fanti'),
+ {130}(S: 'fij'; L: 'Fijian'),
+ (S: 'fin'; L: 'Finnish'),
+ (S: 'fiu'; L: 'Finno-Ugrian (Other)'),
+ (S: 'fon'; L: 'Fon'),
+ (S: 'fra'; L: 'French'), // Also 'fre'
+ (S: 'fre'; L: 'French'), // Also 'fra'
+ (S: 'frm'; L: 'French, Middle (ca.1400-1600)'),
+ (S: 'fro'; L: 'French, Old (842-ca.1400)'),
+ (S: 'fry'; L: 'Frisian'),
+ (S: 'ful'; L: 'Fulah'),
+ {140}(S: 'fur'; L: 'Friulian'),
+ (S: 'gaa'; L: 'Ga'),
+ (S: 'gay'; L: 'Gayo'),
+ (S: 'gba'; L: 'Gbaya'),
+ (S: 'gem'; L: 'Germanic (Other)'),
+ (S: 'geo'; L: 'Georgian'), // Also 'kat'
+ (S: 'ger'; L: 'German'), // Also 'deu'
+ (S: 'gez'; L: 'Geez'),
+ (S: 'gil'; L: 'Gilbertese'),
+ (S: 'gla'; L: 'Gaelic; Scottish Gaelic'),
+ {150}(S: 'gle'; L: 'Irish'),
+ (S: 'glg'; L: 'Gallegan'),
+ (S: 'glv'; L: 'Manx'),
+ (S: 'gmh'; L: 'German, Middle High (ca.1050-1500)'),
+ (S: 'goh'; L: 'German, Old High (ca.750-1050)'),
+ (S: 'gon'; L: 'Gondi'),
+ (S: 'gor'; L: 'Gorontalo'),
+ (S: 'got'; L: 'Gothic'),
+ (S: 'grb'; L: 'Grebo'),
+ (S: 'grc'; L: 'Greek, Ancient (to 1453)'),
+ {160}(S: 'gre'; L: 'Greek, Modern (1453-)'), // Also 'ell'
+ (S: 'grn'; L: 'Guarani'),
+ (S: 'guj'; L: 'Gujarati'),
+ (S: 'gwi'; L: 'Gwich´in'),
+ (S: 'hai'; L: 'Haida'),
+ (S: 'hau'; L: 'Hausa'),
+ (S: 'haw'; L: 'Hawaiian'),
+ (S: 'heb'; L: 'Hebrew'),
+ (S: 'her'; L: 'Herero'),
+ (S: 'hil'; L: 'Hiligaynon'),
+ {170}(S: 'him'; L: 'Himachali'),
+ (S: 'hin'; L: 'Hindi'),
+ (S: 'hit'; L: 'Hittite'),
+ (S: 'hmn'; L: 'Hmong'),
+ (S: 'hmo'; L: 'Hiri Motu'),
+ (S: 'hrv'; L: 'Croatian'), // Also 'scr'
+ (S: 'hun'; L: 'Hungarian'),
+ (S: 'hup'; L: 'Hupa'),
+ (S: 'hye'; L: 'Armenian'), // Also 'arm'
+ (S: 'iba'; L: 'Iban'),
+ {180}(S: 'ibo'; L: 'Igbo'),
+ (S: 'ice'; L: 'Icelandic'), // Also 'isl'
+ (S: 'ido'; L: 'Ido'),
+ (S: 'iii'; L: 'Sichuan Yi'),
+ (S: 'ijo'; L: 'Ijo'),
+ (S: 'iku'; L: 'Inuktitut'),
+ (S: 'ile'; L: 'Interlingue'),
+ (S: 'ilo'; L: 'Iloko'),
+ (S: 'ina'; L: 'Interlingua (International Auxiliary Language Association)'),
+ (S: 'inc'; L: 'Indic (Other)'),
+ {190}(S: 'ind'; L: 'Indonesian'),
+ (S: 'ine'; L: 'Indo-European (Other)'),
+ (S: 'inh'; L: 'Ingush'),
+ (S: 'ipk'; L: 'Inupiaq'),
+ (S: 'ira'; L: 'Iranian (Other)'),
+ (S: 'iro'; L: 'Iroquoian languages'),
+ (S: 'isl'; L: 'Icelandic'), // Also 'ice'
+ (S: 'ita'; L: 'Italian'),
+ (S: 'jav'; L: 'Javanese'),
+ (S: 'jpn'; L: 'Japanese'),
+ {200}(S: 'jpr'; L: 'Judeo-Persian'),
+ (S: 'jrb'; L: 'Judeo-Arabic'),
+ (S: 'kaa'; L: 'Kara-Kalpak'),
+ (S: 'kab'; L: 'Kabyle'),
+ (S: 'kac'; L: 'Kachin'),
+ (S: 'kal'; L: 'Kalaallisut'),
+ (S: 'kam'; L: 'Kamba'),
+ (S: 'kan'; L: 'Kannada'),
+ (S: 'kar'; L: 'Karen'),
+ (S: 'kas'; L: 'Kashmiri'),
+ {210}(S: 'kat'; L: 'Georgian'), // Also 'geo'
+ (S: 'kau'; L: 'Kanuri'),
+ (S: 'kaw'; L: 'Kawi'),
+ (S: 'kaz'; L: 'Kazakh'),
+ (S: 'kbd'; L: 'Kabardian'),
+ (S: 'kha'; L: 'Khasi'),
+ (S: 'khi'; L: 'Khoisan (Other)'),
+ (S: 'khm'; L: 'Khmer'),
+ (S: 'kho'; L: 'Khotanese'),
+ (S: 'kik'; L: 'Kikuyu; Gikuyu'),
+ {220}(S: 'kin'; L: 'Kinyarwanda'),
+ (S: 'kir'; L: 'Kirghiz'),
+ (S: 'kmb'; L: 'Kimbundu'),
+ (S: 'kok'; L: 'Konkani'),
+ (S: 'kom'; L: 'Komi'),
+ (S: 'kon'; L: 'Kongo'),
+ (S: 'kor'; L: 'Korean'),
+ (S: 'kos'; L: 'Kosraean'),
+ (S: 'kpe'; L: 'Kpelle'),
+ (S: 'kro'; L: 'Kru'),
+ {230}(S: 'kru'; L: 'Kurukh'),
+ (S: 'kua'; L: 'Kuanyama; Kwanyama'),
+ (S: 'kum'; L: 'Kumyk'),
+ (S: 'kur'; L: 'Kurdish'),
+ (S: 'kut'; L: 'Kutenai'),
+ (S: 'lad'; L: 'Ladino'),
+ (S: 'lah'; L: 'Lahnda'),
+ (S: 'lam'; L: 'Lamba'),
+ (S: 'lao'; L: 'Lao'),
+ (S: 'lat'; L: 'Latin'),
+ {240}(S: 'lav'; L: 'Latvian'),
+ (S: 'lez'; L: 'Lezghian'),
+ (S: 'lim'; L: 'Limburgan'),
+ (S: 'lin'; L: 'Lingala'),
+ (S: 'lit'; L: 'Lithuanian'),
+ (S: 'lol'; L: 'Mongo'),
+ (S: 'loz'; L: 'Lozi'),
+ (S: 'ltz'; L: 'Luxembourgish'),
+ (S: 'lua'; L: 'Luba-Lulua'),
+ (S: 'lub'; L: 'Luba-Katanga'),
+ {250}(S: 'lug'; L: 'Ganda'),
+ (S: 'lui'; L: 'Luiseno'),
+ (S: 'lun'; L: 'Lunda'),
+ (S: 'luo'; L: 'Luo (Kenya and Tanzania)'),
+ (S: 'lus'; L: 'Lushai'),
+ (S: 'mac'; L: 'Macedonian'), // Also 'mkd'
+ (S: 'mad'; L: 'Madurese'),
+ (S: 'mag'; L: 'Magahi'),
+ (S: 'mah'; L: 'Marshallese'),
+ (S: 'mai'; L: 'Maithili'),
+ {260}(S: 'mak'; L: 'Makasar'),
+ (S: 'mal'; L: 'Malayalam'),
+ (S: 'man'; L: 'Mandingo'),
+ (S: 'mao'; L: 'Maori'), // Also 'mri'
+ (S: 'map'; L: 'Austronesian (Other)'),
+ (S: 'mar'; L: 'Marathi'),
+ (S: 'mas'; L: 'Masai'),
+ (S: 'may'; L: 'Malay'), // Also 'msa'
+ (S: 'mdr'; L: 'Mandar'),
+ (S: 'men'; L: 'Mende'),
+ {270}(S: 'mga'; L: 'Irish, Middle (900-1200)'),
+ (S: 'mic'; L: 'Micmac'),
+ (S: 'min'; L: 'Minangkabau'),
+ (S: 'mis'; L: 'Miscellaneous languages'),
+ (S: 'mkd'; L: 'Macedonian'), // Also 'mac'
+ (S: 'mkh'; L: 'Mon-Khmer (Other)'),
+ (S: 'mlg'; L: 'Malagasy'),
+ (S: 'mlt'; L: 'Maltese'),
+ (S: 'mnc'; L: 'Manchu'),
+ (S: 'mni'; L: 'Manipuri'),
+ {280}(S: 'mno'; L: 'Manobo languages'),
+ (S: 'moh'; L: 'Mohawk'),
+ (S: 'mol'; L: 'Moldavian'),
+ (S: 'mon'; L: 'Mongolian'),
+ (S: 'mos'; L: 'Mossi'),
+ (S: 'mri'; L: 'Maori'), // Also 'mao'
+ (S: 'msa'; L: 'Malay'), // Also 'may'
+ (S: 'mul'; L: 'Multiple languages'),
+ (S: 'mun'; L: 'Munda languages'),
+ (S: 'mus'; L: 'Creek'),
+ {290}(S: 'mwr'; L: 'Marwari'),
+ (S: 'mya'; L: 'Burmese'), // Also 'bur'
+ (S: 'myn'; L: 'Mayan languages'),
+ (S: 'nah'; L: 'Nahuatl'),
+ (S: 'nai'; L: 'North American Indian (Other)'),
+ (S: 'nap'; L: 'Neapolitan'),
+ (S: 'nau'; L: 'Nauru'),
+ (S: 'nav'; L: 'Navajo; Navaho'),
+ (S: 'nbl'; L: 'Ndebele, South'),
+ (S: 'nde'; L: 'Ndebele, North'),
+ {300}(S: 'ndo'; L: 'Ndonga'),
+ (S: 'nds'; L: 'German, Low'),
+ (S: 'nep'; L: 'Nepali'),
+ (S: 'new'; L: 'Newari'),
+ (S: 'nia'; L: 'Nias'),
+ (S: 'nic'; L: 'Niger-Kordofanian (Other)'),
+ (S: 'niu'; L: 'Niuean'),
+ (S: 'nld'; L: 'Dutch'), // Also 'dut'
+ (S: 'nno'; L: 'Nynorsk, Norwegian'),
+ (S: 'nob'; L: 'Bokmål, Norwegian'),
+ {310}(S: 'non'; L: 'Norse, Old'),
+ (S: 'nor'; L: 'Norwegian'),
+ (S: 'nso'; L: 'Sotho, Northern'),
+ (S: 'nub'; L: 'Nubian languages'),
+ (S: 'nya'; L: 'Chichewa'),
+ (S: 'nym'; L: 'Nyamwezi'),
+ (S: 'nyn'; L: 'Nyankole'),
+ (S: 'nyo'; L: 'Nyoro'),
+ (S: 'nzi'; L: 'Nzima'),
+ (S: 'oci'; L: 'Occitan (post 1500); Provençal'),
+ {320}(S: 'oji'; L: 'Ojibwa'),
+ (S: 'ori'; L: 'Oriya'),
+ (S: 'orm'; L: 'Oromo'),
+ (S: 'osa'; L: 'Osage'),
+ (S: 'oss'; L: 'Ossetian'),
+ (S: 'ota'; L: 'Turkish, Ottoman (1500-1928)'),
+ (S: 'oto'; L: 'Otomian languages'),
+ (S: 'paa'; L: 'Papuan (Other)'),
+ (S: 'pag'; L: 'Pangasinan'),
+ (S: 'pal'; L: 'Pahlavi'),
+ {330}(S: 'pam'; L: 'Pampanga'),
+ (S: 'pan'; L: 'Panjabi'),
+ (S: 'pap'; L: 'Papiamento'),
+ (S: 'pau'; L: 'Palauan'),
+ (S: 'peo'; L: 'Persian, Old (ca.600-400)'),
+ (S: 'per'; L: 'Persian'), // Also 'fas'
+ (S: 'phi'; L: 'Philippine (Other)'),
+ (S: 'phn'; L: 'Phoenician'),
+ (S: 'pli'; L: 'Pali'),
+ (S: 'pol'; L: 'Polish'),
+ {340}(S: 'pon'; L: 'Pohnpeian'),
+ (S: 'por'; L: 'Portuguese'),
+ (S: 'pra'; L: 'Prakrit languages'),
+ (S: 'pro'; L: 'Provençal, Old (to 1500)'),
+ (S: 'pus'; L: 'Pushto'),
+ (S: 'qtz'; L: 'Reserved for local user; qaa'),
+ (S: 'que'; L: 'Quechua'),
+ (S: 'raj'; L: 'Rajasthani'),
+ (S: 'rap'; L: 'Rapanui'),
+ (S: 'rar'; L: 'Rarotongan'),
+ {350}(S: 'roa'; L: 'Romance (Other)'),
+ (S: 'roh'; L: 'Raeto-Romance'),
+ (S: 'rom'; L: 'Romany'),
+ (S: 'ron'; L: 'Romanian'), // Also 'rum'
+ (S: 'rum'; L: 'Romanian'), // Also 'ron'
+ (S: 'run'; L: 'Rundi'),
+ (S: 'rus'; L: 'Russian'),
+ (S: 'sad'; L: 'Sandawe'),
+ (S: 'sag'; L: 'Sango'),
+ (S: 'sah'; L: 'Yakut'),
+ {360}(S: 'sai'; L: 'South American Indian (Other)'),
+ (S: 'sal'; L: 'Salishan languages'),
+ (S: 'sam'; L: 'Samaritan Aramaic'),
+ (S: 'san'; L: 'Sanskrit'),
+ (S: 'sas'; L: 'Sasak'),
+ (S: 'sat'; L: 'Santali'),
+ (S: 'scc'; L: 'Serbian'), // Also 'srp'
+ (S: 'sco'; L: 'Scots'),
+ (S: 'scr'; L: 'Croatian'), // Also 'hrv'
+ (S: 'sel'; L: 'Selkup'),
+ {370}(S: 'sem'; L: 'Semitic (Other)'),
+ (S: 'sga'; L: 'Irish, Old (to 900)'),
+ (S: 'sgn'; L: 'Sign languages'),
+ (S: 'shn'; L: 'Shan'),
+ (S: 'sid'; L: 'Sidamo'),
+ (S: 'sin'; L: 'Sinhalese'),
+ (S: 'sio'; L: 'Siouan languages'),
+ (S: 'sit'; L: 'Sino-Tibetan (Other)'),
+ (S: 'sla'; L: 'Slavic (Other)'),
+ (S: 'slk'; L: 'Slovak'), // Also 'slo'
+ {380}(S: 'slo'; L: 'Slovak'), // Also 'slk'
+ (S: 'slv'; L: 'Slovenian'),
+ (S: 'sma'; L: 'Southern Sami'),
+ (S: 'sme'; L: 'Northern Sami'),
+ (S: 'smi'; L: 'Sami languages (Other)'),
+ (S: 'smj'; L: 'Lule Sami'),
+ (S: 'smn'; L: 'Inari Sami'),
+ (S: 'smo'; L: 'Samoan'),
+ (S: 'sms'; L: 'Skolt Sami'),
+ (S: 'sna'; L: 'Shona'),
+ {390}(S: 'snd'; L: 'Sindhi'),
+ (S: 'snk'; L: 'Soninke'),
+ (S: 'sog'; L: 'Sogdian'),
+ (S: 'som'; L: 'Somali'),
+ (S: 'son'; L: 'Songhai'),
+ (S: 'sot'; L: 'Sotho, Southern'),
+ (S: 'spa'; L: 'Spanish; Castilian'),
+ (S: 'sqi'; L: 'Albanian'), // Also 'alb'
+ (S: 'srd'; L: 'Sardinian'),
+ (S: 'srp'; L: 'Serbian'), // Also 'scc'
+ {400}(S: 'srr'; L: 'Serer'),
+ (S: 'ssa'; L: 'Nilo-Saharan (Other)'),
+ (S: 'ssw'; L: 'Swati'),
+ (S: 'suk'; L: 'Sukuma'),
+ (S: 'sun'; L: 'Sundanese'),
+ (S: 'sus'; L: 'Susu'),
+ (S: 'sux'; L: 'Sumerian'),
+ (S: 'swa'; L: 'Swahili'),
+ (S: 'swe'; L: 'Swedish'),
+ (S: 'syr'; L: 'Syriac'),
+ {410}(S: 'tah'; L: 'Tahitian'),
+ (S: 'tai'; L: 'Tai (Other)'),
+ (S: 'tam'; L: 'Tamil'),
+ (S: 'tat'; L: 'Tatar'),
+ (S: 'tel'; L: 'Telugu'),
+ (S: 'tem'; L: 'Timne'),
+ (S: 'ter'; L: 'Tereno'),
+ (S: 'tet'; L: 'Tetum'),
+ (S: 'tgk'; L: 'Tajik'),
+ (S: 'tgl'; L: 'Tagalog'),
+ {420}(S: 'tha'; L: 'Thai'),
+ (S: 'tib'; L: 'Tibetan'), // Also 'bod'
+ (S: 'tig'; L: 'Tigre'),
+ (S: 'tir'; L: 'Tigrinya'),
+ (S: 'tiv'; L: 'Tiv'),
+ (S: 'tkl'; L: 'Tokelau'),
+ (S: 'tli'; L: 'Tlingit'),
+ (S: 'tmh'; L: 'Tamashek'),
+ (S: 'tog'; L: 'Tonga (Nyasa)'),
+ (S: 'ton'; L: 'Tonga (Tonga Islands)'),
+ {430}(S: 'tpi'; L: 'Tok Pisin'),
+ (S: 'tsi'; L: 'Tsimshian'),
+ (S: 'tsn'; L: 'Tswana'),
+ (S: 'tso'; L: 'Tsonga'),
+ (S: 'tuk'; L: 'Turkmen'),
+ (S: 'tum'; L: 'Tumbuka'),
+ (S: 'tup'; L: 'Tupi languages'),
+ (S: 'tur'; L: 'Turkish'),
+ (S: 'tut'; L: 'Altaic (Other)'),
+ (S: 'tvl'; L: 'Tuvalu'),
+ {440}(S: 'twi'; L: 'Twi'),
+ (S: 'tyv'; L: 'Tuvinian'),
+ (S: 'uga'; L: 'Ugaritic'),
+ (S: 'uig'; L: 'Uighur'),
+ (S: 'ukr'; L: 'Ukrainian'),
+ (S: 'umb'; L: 'Umbundu'),
+ (S: 'und'; L: 'Undetermined'),
+ (S: 'urd'; L: 'Urdu'),
+ (S: 'uzb'; L: 'Uzbek'),
+ (S: 'vai'; L: 'Vai'),
+ {450}(S: 'ven'; L: 'Venda'),
+ (S: 'vie'; L: 'Vietnamese'),
+ (S: 'vol'; L: 'Volapük'),
+ (S: 'vot'; L: 'Votic'),
+ (S: 'wak'; L: 'Wakashan languages'),
+ (S: 'wal'; L: 'Walamo'),
+ (S: 'war'; L: 'Waray'),
+ (S: 'was'; L: 'Washo'),
+ (S: 'wel'; L: 'Welsh'), // Also 'cym'
+ (S: 'wen'; L: 'Sorbian languages'),
+ {460}(S: 'wln'; L: 'Walloon'),
+ (S: 'wol'; L: 'Wolof'),
+ (S: 'xho'; L: 'Xhosa'),
+ (S: 'yao'; L: 'Yao'),
+ (S: 'yap'; L: 'Yapese'),
+ (S: 'yid'; L: 'Yiddish'),
+ (S: 'yor'; L: 'Yoruba'),
+ (S: 'ypk'; L: 'Yupik languages'),
+ (S: 'zap'; L: 'Zapotec'),
+ (S: 'zen'; L: 'Zenaga'),
+ {470}(S: 'zha'; L: 'Zhuang; Chuang'),
+ (S: 'zho'; L: 'Chinese'), // Also 'chi'
+ (S: 'znd'; L: 'Zande'),
+ (S: 'zul'; L: 'Zulu'),
+ (S: 'zun'; L: 'Zuni')
+ );
+
+ CID3Genres: array[0..147] of PAnsiChar = (
+
+ { The following genres are defined in ID3v1 }
+
+ {0}'Blues',
+ 'Classic Rock',
+ 'Country',
+ 'Dance',
+ 'Disco',
+ 'Funk',
+ 'Grunge',
+ 'Hip-Hop',
+ 'Jazz',
+ 'Metal',
+ {10}'New Age',
+ 'Oldies',
+ 'Other', { <= Default }
+ 'Pop',
+ 'R&B',
+ 'Rap',
+ 'Reggae',
+ 'Rock',
+ 'Techno',
+ 'Industrial',
+ {20}'Alternative',
+ 'Ska',
+ 'Death Metal',
+ 'Pranks',
+ 'Soundtrack',
+ 'Euro-Techno',
+ 'Ambient',
+ 'Trip-Hop',
+ 'Vocal',
+ 'Jazz+Funk',
+ {30}'Fusion',
+ 'Trance',
+ 'Classical',
+ 'Instrumental',
+ 'Acid',
+ 'House',
+ 'Game',
+ 'Sound Clip',
+ 'Gospel',
+ 'Noise',
+ {40}'AlternRock',
+ 'Bass',
+ 'Soul',
+ 'Punk',
+ 'Space',
+ 'Meditative',
+ 'Instrumental Pop',
+ 'Instrumental Rock',
+ 'Ethnic',
+ 'Gothic',
+ {50}'Darkwave',
+ 'Techno-Industrial',
+ 'Electronic',
+ 'Pop-Folk',
+ 'Eurodance',
+ 'Dream',
+ 'Southern Rock',
+ 'Comedy',
+ 'Cult',
+ 'Gangsta',
+ {60}'Top 40',
+ 'Christian Rap',
+ 'Pop/Funk',
+ 'Jungle',
+ 'Native American',
+ 'Cabaret',
+ 'New Wave',
+ 'Psychedelic', // = 'Psychadelic' in ID3 docs, 'Psychedelic' in winamp.
+ 'Rave',
+ 'Showtunes',
+ {70}'Trailer',
+ 'Lo-Fi',
+ 'Tribal',
+ 'Acid Punk',
+ 'Acid Jazz',
+ 'Polka',
+ 'Retro',
+ 'Musical',
+ 'Rock & Roll',
+ 'Hard Rock',
+
+ { The following genres are Winamp extensions }
+
+ {80}'Folk',
+ 'Folk-Rock',
+ 'National Folk',
+ 'Swing',
+ 'Fast Fusion',
+ 'Bebob',
+ 'Latin',
+ 'Revival',
+ 'Celtic',
+ 'Bluegrass',
+ {90}'Avantgarde',
+ 'Gothic Rock',
+ 'Progressive Rock',
+ 'Psychedelic Rock',
+ 'Symphonic Rock',
+ 'Slow Rock',
+ 'Big Band',
+ 'Chorus',
+ 'Easy Listening',
+ 'Acoustic',
+ {100}'Humour',
+ 'Speech',
+ 'Chanson',
+ 'Opera',
+ 'Chamber Music',
+ 'Sonata',
+ 'Symphony',
+ 'Booty Bass',
+ 'Primus',
+ 'Porn Groove',
+ {110}'Satire',
+ 'Slow Jam',
+ 'Club',
+ 'Tango',
+ 'Samba',
+ 'Folklore',
+ 'Ballad',
+ 'Power Ballad',
+ 'Rhythmic Soul',
+ 'Freestyle',
+ {120}'Duet',
+ 'Punk Rock',
+ 'Drum Solo',
+ 'A capella', // A Capella
+ 'Euro-House',
+ 'Dance Hall',
+
+ { winamp ?? genres }
+
+ 'Goa',
+ 'Drum & Bass',
+ 'Club-House',
+ 'Hardcore',
+ {130}'Terror',
+ 'Indie',
+ 'BritPop',
+ 'Negerpunk',
+ 'Polsk Punk',
+ 'Beat',
+ 'Christian Gangsta Rap',
+ 'Heavy Metal',
+ 'Black Metal',
+ 'Crossover',
+ {140}'Contemporary Christian',
+ 'Christian Rock',
+
+ { winamp 1.91 genres }
+
+ 'Merengue',
+ 'Salsa',
+ 'Trash Metal',
+
+ { winamp 1.92 genres }
+
+ 'Anime',
+ 'JPop',
+ 'SynthPop'
+ );
+
+ CGenre_HighV1 = 79;
+ CGenre_DefaultID = 12;
+
+//=== Local procedures =======================================================
+
+function IndexOfLongString(Strings: TStrings; const ALongText: string): Integer;
+{ Searches for a string in Strings that is a prefix of ALongText, this is used
+ by the ID3 genres; problem is that some strings may have the same prefix, ie
+
+ Pop
+ Pop/Funk
+ Pop-Folk
+
+ If we search for a prefix for 'Pop/Funk or something' the binary search
+ may return 'Pop', thus we use FindFrom to search some more
+
+ Note:
+
+ 'Rock' => Result = 17
+ 'Rocks' => Result = 255 (nothing found)
+ 'Rock Rock' => Result = 17
+}
+
+ function IsPrefix(const S: string): Boolean;
+ begin
+ Result := (AnsiStrLIComp(PChar(S), PChar(ALongText), Length(S)) = 0);
+ end;
+
+ function HasSpaceAfterPrefix(const Prefix: string): Boolean;
+ { PRE: IsPrefix(Prefix) = True }
+ var
+ C: Integer;
+ begin
+ C := Length(Prefix) - Length(ALongText);
+ Result := (C = 0) or ((C < 0) and (ALongText[Length(Prefix) + 1] = ' '));
+ end;
+
+ function FindFrom(const Index: Integer): Integer;
+ begin
+ { Try to find I with IsPrefix(Strings[I]) and HasSpaceAfterPrefix(Strings[i]) }
+
+ if Length(Strings[Index]) < Length(ALongText) then
+ begin
+ { Now is valid: IsPrefix(Strings[Result]) }
+
+ Result := Index + 1;
+
+ { Strings is sorted thus it's only usefull to look at higher indexes than
+ Index ie only at higher indexes are possibly longer prefixes of ALongText }
+ while (Result < Strings.Count) and IsPrefix(Strings[Result]) do
+ Inc(Result);
+
+ { Strings[Result] is not ok, Strings[Result-1] is ok }
+ Dec(Result);
+
+ { Now is valid: IsPrefix(Strings[Result]) }
+ end
+ else
+ if Length(ALongText) < Length(Strings[Index]) then
+ begin
+ Result := Index - 1;
+
+ while (Result >= 0) and (Length(Strings[Result]) > Length(ALongText)) do
+ if AnsiStrLIComp(PChar(Strings[Result]), PChar(ALongText), Length(ALongText)) = 0 then
+ Dec(Result)
+ else
+ begin
+ { Not found }
+ Result := -1;
+ Exit;
+ end;
+
+ if (Result < 0) or not IsPrefix(Strings[Result]) then
+ begin
+ { Not found }
+ Result := -1;
+ Exit;
+ end;
+
+ { Now is valid: IsPrefix(Strings[Result]) }
+ end
+ else
+ begin
+ { Found }
+ Result := Index;
+ Exit;
+ end;
+
+ { Now is valid: IsPrefix(Strings[Result]) }
+
+ if HasSpaceAfterPrefix(Strings[Result]) then
+ { Found }
+ Exit;
+
+ Dec(Result);
+
+ { Now go down until we find a string X with X + some separator is a prefix
+ of ALongText }
+ while Result >= 0 do
+ if IsPrefix(Strings[Result]) then
+ begin
+ if HasSpaceAfterPrefix(Strings[Result]) then
+ { Found }
+ Exit
+ else
+ Dec(Result);
+ end
+ else
+ begin
+ { Not found }
+ Result := -1;
+ Exit;
+ end;
+ end;
+var
+ Top, Mid, C: Integer;
+begin
+ Result := 0;
+ Top := Strings.Count - 1;
+ while Result <= Top do
+ begin
+ Mid := (Result + Top) shr 1;
+ C := AnsiStrLIComp(PChar(Strings[Mid]), PChar(ALongText),
+ Min(Length(Strings[Mid]), Length(ALongText)));
+ if C < 0 then
+ Result := Mid + 1
+ else
+ if C = 0 then
+ begin
+ Result := FindFrom(Mid);
+ Exit;
+ end
+ else { C > 0 }
+ Top := Mid - 1;
+ end;
+
+ { Nothing found }
+ Result := -1;
+end;
+
+//=== Global procedures ======================================================
+
+function ID3_FrameIDToString(const ID: TJvID3FrameID; const Size: Integer): AnsiString;
+begin
+ case Size of
+ 3:
+ Result := CID3FrameDefs[ID].ShortTextID;
+ 4:
+ Result := CID3FrameDefs[ID].LongTextID;
+ else
+ raise EJVCLException.CreateRes(@RsEFrameIDSizeCanOnlyBe34);
+ end;
+end;
+
+var
+ GID3TermFinder: TJvID3TermFinder = nil;
+
+function ID3TermFinder: TJvID3TermFinder;
+begin
+ if GID3TermFinder = nil then
+ GID3TermFinder := TJvID3TermFinder.Create;
+ Result := GID3TermFinder;
+end;
+
+procedure ID3_Genres(Strings: TStrings; const InclWinampGenres: Boolean);
+begin
+ ID3TermFinder.ID3Genres(Strings, InclWinampGenres);
+end;
+
+function ID3_GenreToID(const AGenre: string; const InclWinampGenres: Boolean): Integer;
+begin
+ Result := ID3TermFinder.ID3GenreToID(AGenre, True);
+end;
+
+function ID3_IDToGenre(const ID: Integer; const InclWinampGenres: Boolean): string;
+const
+ HighValue: array [Boolean] of Byte = (CGenre_HighV1, High(CID3Genres));
+begin
+ { Note : In Winamp, ID = 255 then Genre = '' }
+ if (ID >= Low(CID3Genres)) and (ID <= HighValue[InclWinampGenres]) then
+ Result := string(CID3Genres[ID])
+ else
+ Result := '';
+end;
+
+function ID3_LongGenreToID(const ALongGenre: string; const InclWinampGenres: Boolean = True): Integer;
+begin
+ Result := ID3TermFinder.ID3LongGenreToID(ALongGenre, InclWinampGenres);
+end;
+
+function ID3_StringToFrameID(const S: AnsiString): TJvID3FrameID;
+var
+ L: Integer;
+begin
+ L := Length(S);
+ case L of
+ 0:
+ Result := fiPaddingFrame;
+ 3:
+ if S = #0#0#0 then
+ Result := fiPaddingFrame
+ else
+ Result := ID3TermFinder.ID3ShortTextToFrameID(S);
+ 4:
+ if S = #0#0#0#0 then
+ Result := fiPaddingFrame
+ else
+ Result := ID3TermFinder.ID3LongTextToFrameID(S);
+ else
+ Result := fiErrorFrame
+ end;
+end;
+
+function ISO_639_2CodeToName(const Code: AnsiString): AnsiString;
+var
+ Index: Integer;
+begin
+ if Length(Code) <> 3 then
+ begin
+ Result := '';
+ Exit;
+ end;
+
+ Index := ID3TermFinder.ISO_639_2CodeToIndex(Code);
+ if Index >= Low(CISO_639_2Data) then
+ Result := CISO_639_2Data[Index].L
+ else
+ Result := '';
+end;
+
+function ISO_639_2IsCode(const Code: AnsiString): Boolean;
+begin
+ Result := (Length(Code) = 3) and
+ (ID3TermFinder.ISO_639_2CodeToIndex(Code) >= Low(CISO_639_2Data));
+end;
+
+procedure ISO_639_2Names(Strings: TStrings);
+begin
+ ID3TermFinder.ISO_639_2Names(Strings);
+end;
+
+function ISO_639_2NameToCode(const Name: string): AnsiString;
+var
+ Index: Integer;
+begin
+ Index := ID3TermFinder.ISO_639_2NameToIndex(Name);
+ if (Index < Low(CISO_639_2Data)) or (Index > High(CISO_639_2Data)) then
+ Result := ''
+ else
+ Result := CISO_639_2Data[Index].S;
+end;
+
+//=== { TJvID3TermFinder } ===================================================
+
+constructor TJvID3TermFinder.Create;
+var
+ ListType: TJvListType;
+begin
+ inherited Create;
+ for ListType := Low(TJvListType) to High(TJvListType) do
+ FLists[ListType] := nil;
+end;
+
+destructor TJvID3TermFinder.Destroy;
+var
+ ListType: TJvListType;
+begin
+ for ListType := Low(TJvListType) to High(TJvListType) do
+ FLists[ListType].Free;
+ inherited Destroy;
+end;
+
+procedure TJvID3TermFinder.BuildList_ID3Genres;
+var
+ I: Integer;
+begin
+ if Assigned(FLists[ltID3Genres]) then
+ Exit;
+
+ FLists[ltID3Genres] := TStringList.Create;
+ with FLists[ltID3Genres] do
+ begin
+ { There are no duplicates in the list }
+ Duplicates := dupError;
+ Sorted := True;
+
+ for I := Low(CID3Genres) to High(CID3Genres) do
+ AddObject(string(CID3Genres[I]), TObject(I));
+ end;
+end;
+
+procedure TJvID3TermFinder.BuildList_ID3LongText;
+var
+ FrameID: TJvID3FrameID;
+begin
+ if Assigned(FLists[ltID3LongText]) then
+ Exit;
+
+ FLists[ltID3LongText] := TStringList.Create;
+ with FLists[ltID3LongText] do
+ begin
+ Duplicates := dupError;
+ Sorted := True;
+
+ for FrameID := Low(TJvID3FrameID) to High(TJvID3FrameID) do
+ with CID3FrameDefs[FrameID] do
+ if LongTextID[0] <> #0 then
+ AddObject(string(LongTextID), TObject(FrameID));
+ end;
+end;
+
+procedure TJvID3TermFinder.BuildList_ID3ShortText;
+var
+ FrameID: TJvID3FrameID;
+begin
+ if Assigned(FLists[ltID3ShortText]) then
+ Exit;
+
+ FLists[ltID3ShortText] := TStringList.Create;
+ with FLists[ltID3ShortText] do
+ begin
+ Duplicates := dupError;
+ Sorted := True;
+
+ for FrameID := Low(TJvID3FrameID) to High(TJvID3FrameID) do
+ with CID3FrameDefs[FrameID] do
+ if ShortTextID[0] <> #0 then
+ AddObject(string(ShortTextID), TObject(FrameID));
+ end;
+end;
+
+procedure TJvID3TermFinder.BuildList_ISO_639_2Code;
+var
+ I: Integer;
+begin
+ if Assigned(FLists[ltISO_639_2Code]) then
+ Exit;
+
+ FLists[ltISO_639_2Code] := TStringList.Create;
+ with FLists[ltISO_639_2Code] do
+ begin
+ { There are no duplicates in the list }
+ Duplicates := dupError;
+ Sorted := True;
+
+ for I := Low(CISO_639_2Data) to High(CISO_639_2Data) do
+ AddObject(string(CISO_639_2Data[I].S), TObject(I));
+ end;
+end;
+
+procedure TJvID3TermFinder.BuildList_ISO_639_2Name;
+var
+ I: Integer;
+begin
+ if Assigned(FLists[ltISO_639_2Name]) then
+ Exit;
+
+ FLists[ltISO_639_2Name] := TStringList.Create;
+ with FLists[ltISO_639_2Name] do
+ begin
+ { There are duplicates in the list }
+ Duplicates := dupIgnore;
+ Sorted := True;
+
+ for I := Low(CISO_639_2Data) to High(CISO_639_2Data) do
+ AddObject(string(CISO_639_2Data[I].L), TObject(I));
+ end;
+end;
+
+procedure TJvID3TermFinder.ID3Genres(AStrings: TStrings;
+ const InclWinampGenres: Boolean);
+var
+ I: Integer;
+begin
+ BuildList_ID3Genres;
+
+ AStrings.BeginUpdate;
+ try
+ AStrings.Clear;
+
+ { In Winamp, ID = 255 then Genre = '' }
+ if InclWinampGenres then
+ AStrings.AddObject('', TObject(255));
+
+ for I := 0 to FLists[ltID3Genres].Count - 1 do
+ if InclWinampGenres or (Integer(FLists[ltID3Genres].Objects[I]) <= CGenre_HighV1) then
+ AStrings.AddObject(FLists[ltID3Genres][I], FLists[ltID3Genres].Objects[I]);
+ finally
+ AStrings.EndUpdate;
+ end;
+end;
+
+function TJvID3TermFinder.ID3GenreToID(const AGenre: string; const InclWinampGenres: Boolean): Integer;
+const
+ { In Winamp, ID = 255 then Genre = '' }
+ CDefaultGenre: array [Boolean] of Byte = (CGenre_DefaultID, 255);
+begin
+ BuildList_ID3Genres;
+
+ if AGenre = '' then
+ Result := CDefaultGenre[InclWinampGenres]
+ else
+ begin
+ Result := FLists[ltID3Genres].IndexOf(AGenre);
+
+ { Special case: 'Psychadelic' }
+ if (Result < 0) and (CompareText(AGenre, 'psychadelic') = 0) then
+ Result := FLists[ltID3Genres].IndexOf('Psychedelic');
+
+ if not InclWinampGenres and (Result > CGenre_HighV1) then
+ Result := -1;
+
+ if Result >= 0 then
+ Result := Integer(FLists[ltID3Genres].Objects[Result])
+ else
+ Result := cDefaultGenre[InclWinampGenres];
+ end;
+end;
+
+function TJvID3TermFinder.ID3LongGenreToID(const ALongGenre: string;
+ const InclWinampGenres: Boolean): Integer;
+const
+ { In Winamp, ID = 255 then Genre = '' }
+ CDefaultGenre: array [Boolean] of Byte = (CGenre_DefaultID, 255);
+begin
+ BuildList_ID3Genres;
+
+ if ALongGenre = '' then
+ begin
+ Result := CDefaultGenre[InclWinampGenres];
+ Exit;
+ end;
+
+ Result := IndexOfLongString(FLists[ltID3Genres], string(ALongGenre));
+
+ { Special case: 'Psychadelic' }
+ if (Result < 0) and (AnsiStrLIComp(PChar(string(ALongGenre)), 'psychadelic', Length(ALongGenre)) = 0) then
+ Result := FLists[ltID3Genres].IndexOf('Psychedelic');
+
+ if not InclWinampGenres and (Result > CGenre_HighV1) then
+ Result := -1;
+
+ if Result >= 0 then
+ Result := Integer(FLists[ltID3Genres].Objects[Result])
+ else
+ Result := CDefaultGenre[InclWinampGenres];
+end;
+
+function TJvID3TermFinder.ID3LongTextToFrameID(
+ const S: AnsiString): TJvID3FrameID;
+var
+ I: Integer;
+begin
+ if not IsFrameOk(S) then
+ begin
+ Result := fiErrorFrame;
+ Exit;
+ end;
+
+ BuildList_ID3LongText;
+
+ I := FLists[ltID3LongText].IndexOf(string(S));
+ if I < 0 then
+ Result := fiUnknownFrame
+ else
+ Result := TJvID3FrameID(FLists[ltID3LongText].Objects[I]);
+end;
+
+function TJvID3TermFinder.ID3ShortTextToFrameID(
+ const S: AnsiString): TJvID3FrameID;
+var
+ I: Integer;
+begin
+ if not IsFrameOk(S) then
+ begin
+ Result := fiErrorFrame;
+ Exit;
+ end;
+
+ BuildList_ID3ShortText;
+
+ I := FLists[ltID3ShortText].IndexOf(string(S));
+ if I < 0 then
+ Result := fiUnknownFrame
+ else
+ Result := TJvID3FrameID(FLists[ltID3ShortText].Objects[I]);
+end;
+
+function TJvID3TermFinder.IsFrameOk(const S: AnsiString): Boolean;
+var
+ I: Integer;
+begin
+ { The frame ID must be made out of the characters capital A-Z and 0-9. }
+ Result := False;
+
+ for I := 1 to Length(S) do
+ if not CharInSet(S[I], (['A'..'Z'] + DigitChars)) then
+ Exit;
+
+ Result := True;
+end;
+
+function TJvID3TermFinder.ISO_639_2CodeToIndex(
+ const ACode: AnsiString): Integer;
+begin
+ BuildList_ISO_639_2Code;
+
+ Result := FLists[ltISO_639_2Code].IndexOf(string(AnsiLowerCase(ACode)));
+ if Result >= 0 then
+ Result := Integer(FLists[ltISO_639_2Code].Objects[Result]);
+end;
+
+procedure TJvID3TermFinder.ISO_639_2Names(AStrings: TStrings);
+begin
+ BuildList_ISO_639_2Name;
+
+ AStrings.Assign(FLists[ltISO_639_2Name]);
+end;
+
+function TJvID3TermFinder.ISO_639_2NameToIndex(
+ const AName: string): Integer;
+begin
+ BuildList_ISO_639_2Name;
+
+ Result := FLists[ltISO_639_2Name].IndexOf(AName);
+ if Result >= 0 then
+ Result := Integer(FLists[ltISO_639_2Name].Objects[Result]);
+end;
+
+initialization
+
+finalization
+ FreeAndNil(GID3TermFinder);
+
+end.