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 @@ + + + + + + + + + + <Scaled Value="True"/> + <ResourceType Value="res"/> + <UseXPManifest Value="True"/> + <XPManifest> + <DpiAware Value="True"/> + </XPManifest> + <Icon Value="0"/> + </General> + <BuildModes Count="1"> + <Item1 Name="Default" Default="True"/> + </BuildModes> + <PublishOptions> + <Version Value="2"/> + </PublishOptions> + <RunParams> + <FormatVersion Value="2"/> + <Modes Count="0"/> + </RunParams> + <RequiredPackages Count="2"> + <Item1> + <PackageName Value="JvMMLazR"/> + </Item1> + <Item2> + <PackageName Value="LCL"/> + </Item2> + </RequiredPackages> + <Units Count="2"> + <Unit0> + <Filename Value="JvID3v1Demo.lpr"/> + <IsPartOfProject Value="True"/> + </Unit0> + <Unit1> + <Filename Value="JvID3v1MainFormU.pas"/> + <IsPartOfProject Value="True"/> + <ComponentName Value="JvID3v1MainForm"/> + <HasResources Value="True"/> + <ResourceBaseClass Value="Form"/> + </Unit1> + </Units> + </ProjectOptions> + <CompilerOptions> + <Version Value="11"/> + <PathDelim Value="\"/> + <Target> + <Filename Value="JvID3v1Demo"/> + </Target> + <SearchPaths> + <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/> + </SearchPaths> + <Linking> + <Options> + <Win32> + <GraphicApplication Value="True"/> + </Win32> + </Options> + </Linking> + </CompilerOptions> + <Debugging> + <Exceptions Count="3"> + <Item1> + <Name Value="EAbort"/> + </Item1> + <Item2> + <Name Value="ECodetoolError"/> + </Item2> + <Item3> + <Name Value="EFOpenError"/> + </Item3> + </Exceptions> + </Debugging> +</CONFIG> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<CONFIG> + <ProjectOptions> + <Version Value="11"/> + <PathDelim Value="\"/> + <General> + <Flags> + <UseDefaultCompilerOptions Value="True"/> + </Flags> + <SessionStorage Value="InProjectDir"/> + <MainUnit Value="0"/> + <Title Value="JvID3v2Demo"/> + <Scaled Value="True"/> + <ResourceType Value="res"/> + <UseXPManifest Value="True"/> + <XPManifest> + <DpiAware Value="True"/> + </XPManifest> + <Icon Value="0"/> + </General> + <BuildModes Count="1"> + <Item1 Name="Default" Default="True"/> + </BuildModes> + <PublishOptions> + <Version Value="2"/> + </PublishOptions> + <RunParams> + <FormatVersion Value="2"/> + <Modes Count="0"/> + </RunParams> + <RequiredPackages Count="2"> + <Item1> + <PackageName Value="JvMMLazR"/> + </Item1> + <Item2> + <PackageName Value="LCL"/> + </Item2> + </RequiredPackages> + <Units Count="3"> + <Unit0> + <Filename Value="JvID3v2Demo.lpr"/> + <IsPartOfProject Value="True"/> + </Unit0> + <Unit1> + <Filename Value="JvID3v2EditFormU.pas"/> + <IsPartOfProject Value="True"/> + <ComponentName Value="JvID3v2EditForm"/> + <HasResources Value="True"/> + <ResourceBaseClass Value="Form"/> + </Unit1> + <Unit2> + <Filename Value="JvID3v2MainFormU.pas"/> + <IsPartOfProject Value="True"/> + <ComponentName Value="JvID3v2MainForm"/> + <HasResources Value="True"/> + <ResourceBaseClass Value="Form"/> + </Unit2> + </Units> + </ProjectOptions> + <CompilerOptions> + <Version Value="11"/> + <PathDelim Value="\"/> + <Target> + <Filename Value="JvID3v2Demo"/> + </Target> + <SearchPaths> + <IncludeFiles Value="$(ProjOutDir)"/> + <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/> + </SearchPaths> + <Linking> + <Options> + <Win32> + <GraphicApplication Value="True"/> + </Win32> + </Options> + </Linking> + </CompilerOptions> + <Debugging> + <Exceptions Count="3"> + <Item1> + <Name Value="EAbort"/> + </Item1> + <Item2> + <Name Value="ECodetoolError"/> + </Item2> + <Item3> + <Name Value="EFOpenError"/> + </Item3> + </Exceptions> + </Debugging> +</CONFIG> 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 @@ <Description Value="JVCL Multimedia Components (Runtime)."/> <License Value="The JVCL is released in accordance with the MPL 1.1 license. To get your own copy or read it, go to http://www.mozilla.org/MPL/MPL-1.1.html. "/> <Version Major="1" Release="4"/> - <Files Count="4"> + <Files Count="7"> <Item1> <Filename Value="..\run\JvMM\JvSpecialProgress.pas"/> <UnitName Value="JvSpecialProgress"/> @@ -32,6 +32,18 @@ <Filename Value="..\run\JvMM\JvId3v1.pas"/> <UnitName Value="JvId3v1"/> </Item4> + <Item5> + <Filename Value="..\run\JvMM\JvId3v2Types.pas"/> + <UnitName Value="JvId3v2Types"/> + </Item5> + <Item6> + <Filename Value="..\run\JvMM\JvID3v2Base.pas"/> + <UnitName Value="JvID3v2Base"/> + </Item6> + <Item7> + <Filename Value="..\run\JvMM\JvId3v2.pas"/> + <UnitName Value="JvId3v2"/> + </Item7> </Files> <RequiredPkgs Count="2"> <Item1> 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 <text string> $00 + Preview start $xx xx + Preview length $xx xx + Encryption info <binary data> } +function TJvID3AudioEncryptionFrame.GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal; +begin + Result := Cardinal(Length(FOwnerID)) + 1 + 2 + 2 + DataSize; +end; + +function TJvID3AudioEncryptionFrame.GetIsEmpty: Boolean; +begin + Result := inherited GetIsEmpty and (Length(FOwnerID) = 0) and + (FPreviewStart = 0) and (FPreviewLength = 0); +end; + +{ Owner identifier <text string> $00 + Preview start $xx xx + Preview length $xx xx + Encryption info <binary data> } +procedure TJvID3AudioEncryptionFrame.ReadFrame; +begin + with Stream do + begin + ReadStringA(FOwnerID); + + if not CanRead(4) then + Exit; + + Read(FPreviewStart, 2); + FPreviewStart := ReverseBytes(FPreviewStart); + + Read(FPreviewLength, 2); + FPreviewLength := ReverseBytes(FPreviewLength); + end; + ReadData(Stream.BytesTillEndOfFrame); +end; + +function TJvID3AudioEncryptionFrame.SameUniqueIDAs( + const Frame: TJvID3Frame): Boolean; +begin + { There may be more than one "AENC" frames in a tag, but only one with the + same 'Owner identifier' } + Result := (Frame is TJvID3AudioEncryptionFrame) and + (Frame.FrameID = FrameID) and (FrameID = fiAudioCrypto); + + if Result then + Result := AnsiSameStr(TJvID3AudioEncryptionFrame(Frame).OwnerID, OwnerID) + else + Result := inherited SameUniqueIDAs(Frame); +end; + +procedure TJvID3AudioEncryptionFrame.SetOwnerID(const Value: AnsiString); +begin + if FOwnerID <> Value then + begin + FOwnerID := Value; + Changed; + end; +end; + +procedure TJvID3AudioEncryptionFrame.SetPreviewLength(const Value: Word); +begin + if FPreviewLength <> Value then + begin + FPreviewLength := Value; + Changed; + end; +end; + +procedure TJvID3AudioEncryptionFrame.SetPreviewStart(const Value: Word); +begin + if FPreviewStart <> Value then + begin + FPreviewStart := Value; + Changed; + end; +end; + +procedure TJvID3AudioEncryptionFrame.WriteFrame; +var + TempWord: Word; +begin + { Owner identifier <text string> $00 + Preview start $xx xx + Preview length $xx xx + Encryption info <binary data> + } + with Stream do + begin + WriteStringA(OwnerID); + WriteTerminatorA; + + TempWord := ReverseBytes(PreviewStart); + Write(TempWord, 2); + + TempWord := ReverseBytes(PreviewLength); + Write(TempWord, 2); + end; + WriteData; +end; + + +//=== { TJvID3Base } ========================================================= + +constructor TJvID3Base.Create(AController: TJvID3Controller); +begin + inherited Create; + FController := AController; +end; + +procedure TJvID3Base.AfterConstruction; +begin + inherited AfterConstruction; + Reset; +end; + +procedure TJvID3Base.Assign(Source: TPersistent); +begin + if not Assigned(Source) then + Reset + else + inherited Assign(Source); +end; + +function TJvID3Base.GetStream: TJvID3Stream; +begin + if not Assigned(FController) then + ID3Error(RsEID3NoController); + + if icsUsingTempStream in FController.FState then + Result := FController.FTempStream + else + Result := FController.FStream; +end; + + +//=== { TJvID3BinaryFrame } ================================================== + +procedure TJvID3BinaryFrame.AfterConstruction; +begin + inherited AfterConstruction; + FData := nil; + FDataSize := 0; +end; + +procedure TJvID3BinaryFrame.Assign(Source: TPersistent); +begin + if Source is TJvID3BinaryFrame then + SetData(TJvID3BinaryFrame(Source).FData, TJvID3BinaryFrame(Source).DataSize); + + inherited Assign(Source); +end; + +procedure TJvID3BinaryFrame.BeforeDestruction; +begin + inherited BeforeDestruction; + FreeMem(FData); +end; + +class function TJvID3BinaryFrame.CanAddFrame(AController: TJvID3Controller; + AFrameID: TJvID3FrameID): Boolean; +begin + { There may only be one 'MCDI' frame in each tag. } + Result := ((AFrameID = fiCDID) and not AController.HasFrame(fiCDID)) or + (AFrameID <> fiCDID) or inherited CanAddFrame(AController, AFrameID); +end; + +function TJvID3BinaryFrame.CheckFrame(const HandleError: TJvID3HandleError): Boolean; +begin + Result := True; +end; + +procedure TJvID3BinaryFrame.Clear; +begin + SetData(nil, 0); + inherited Clear; +end; + +class function TJvID3BinaryFrame.Find(AController: TJvID3Controller; + const AFrameID: TJvID3FrameID): TJvID3BinaryFrame; +var + Frame: TJvID3Frame; +begin + Result := nil; + if not Assigned(AController) or not AController.Active then + Exit; + + Frame := AController.Frames.FindFrame(AFrameID); + if Frame is TJvID3BinaryFrame then + Result := TJvID3BinaryFrame(Frame); +end; + +class function TJvID3BinaryFrame.FindOrCreate(AController: TJvID3Controller; + const AFrameID: TJvID3FrameID): TJvID3BinaryFrame; +begin + if not Assigned(AController) then + ID3Error(RsEID3NoController); + + Result := Find(AController, AFrameID); + if not Assigned(Result) then + begin + AController.CheckFrameClass(TJvID3BinaryFrame, AFrameID); + Result := TJvID3BinaryFrame(AController.AddFrame(AFrameID)); + end; +end; + +function TJvID3BinaryFrame.GetData(P: Pointer; const Size: Cardinal): Boolean; +var + CopySize: Cardinal; +begin + Result := Assigned(P); + if not Result then + Exit; + + CopySize := Min(Size, DataSize); + if (CopySize > 0) and Assigned(FData) then + Move(FData^, P^, CopySize); +end; + +function TJvID3BinaryFrame.GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal; +begin + Result := FDataSize; +end; + +function TJvID3BinaryFrame.GetIsEmpty: Boolean; +begin + Result := DataSize = 0; +end; + +procedure TJvID3BinaryFrame.LoadFromFile(const AFileName: string); +var + lStream: TStream; +begin + lStream := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyWrite); + try + LoadFromStream(lStream); + finally + lStream.Free; + end; +end; + +procedure TJvID3BinaryFrame.LoadFromStream(AStream: TStream); +begin + AStream.Position := 0; + FDataSize := AStream.Size; + ReallocMem(FData, FDataSize); + if Assigned(FData) then + AStream.Read(FData^, FDataSize); + Changed; +end; + +procedure TJvID3BinaryFrame.ReadData(ASize: Cardinal); +begin + {if ASize < 0 then + ASize := 0;} + + FDataSize := ASize; + ReallocMem(FData, FDataSize); + + if Assigned(FData) and (FDataSize > 0) then + with Stream do + Read(FData^, FDataSize); +end; + +procedure TJvID3BinaryFrame.ReadFrame; +begin + ReadData(FFrameSize); +end; + +function TJvID3BinaryFrame.SameUniqueIDAs(const Frame: TJvID3Frame): Boolean; +begin + { There may only be one 'MCDI' frame in each tag. } + Result := (Assigned(Frame) and (Frame.FrameID = FrameID) and + (FrameID = fiCDID)) or inherited SameUniqueIDAs(Frame); +end; + +procedure TJvID3BinaryFrame.SaveToFile(const AFileName: string); +var + lStream: TStream; +begin + lStream := TFileStream.Create(AFileName, fmCreate); + try + SaveToStream(lStream); + finally + lStream.Free; + end; +end; + +procedure TJvID3BinaryFrame.SaveToStream(AStream: TStream); +begin + if (DataSize > 0) and Assigned(FData) then + AStream.Write(FData^, DataSize) +end; + +function TJvID3BinaryFrame.SetData(P: Pointer; const Size: Cardinal): Boolean; +begin + Result := Assigned(P) or (Size = 0); + if not Result then + Exit; + + ReallocMem(FData, Size); + FDataSize := Size; + if Assigned(FData) and Assigned(P) then + Move(P^, FData^, FDataSize); + Changed; +end; + +procedure TJvID3BinaryFrame.WriteData; +begin + if Assigned(FData) then + with Stream do + Write(FData^, DataSize); +end; + +procedure TJvID3BinaryFrame.WriteFrame; +begin + WriteData; +end; + + +//=== { TJvID3ContentFrame } ================================================= + +procedure TJvID3ContentFrame.Assign(Source: TPersistent); +var + Src: TJvID3ContentFrame; +begin + if Source is TJvID3ContentFrame then + begin + Src := TJvID3ContentFrame(Source); + + FLanguage := Src.Language; + FText := Src.Text; + FDescription := Src.Description; + end; + + inherited Assign(Source); +end; + +class function TJvID3ContentFrame.CanAddFrame(AController: TJvID3Controller; + AFrameID: TJvID3FrameID): Boolean; +begin + { There may be more than one comment frame in each tag, but only one with + the same language and content descriptor. + There may be more than one 'Unsynchronised lyrics/text transcription' frame + in each tag, but only one with the same language and content descriptor. + } + Result := (AFrameID in [fiComment, fiUnsyncedLyrics]) or + inherited CanAddFrame(AController, AFrameID); +end; + +function TJvID3ContentFrame.CheckFrame(const HandleError: TJvID3HandleError): Boolean; +begin + Result := CheckIsLanguageA(Self, FLanguage, HandleError); + + { If something has changed update the framesize } + if not Result and (HandleError = heAutoCorrect) then + begin + UpdateFrameSize; + Result := True; + end; +end; + +procedure TJvID3ContentFrame.Clear; +begin + FLanguage := ''; + FText := ''; + FDescription := ''; + + inherited Clear; +end; + +class function TJvID3ContentFrame.Find(AController: TJvID3Controller; + const AFrameID: TJvID3FrameID): TJvID3ContentFrame; +var + Frame: TJvID3Frame; +begin + Result := nil; + if not Assigned(AController) or not AController.Active then + Exit; + + Frame := AController.Frames.FindFrame(AFrameID); + if Frame is TJvID3ContentFrame then + Result := TJvID3ContentFrame(Frame); +end; + +class function TJvID3ContentFrame.FindOrCreate(AController: TJvID3Controller; + const AFrameID: TJvID3FrameID): TJvID3ContentFrame; +begin + if not Assigned(AController) then + ID3Error(RsEID3NoController); + + Result := Find(AController, AFrameID); + if not Assigned(Result) then + begin + AController.CheckFrameClass(TJvID3ContentFrame, AFrameID); + Result := TJvID3ContentFrame(AController.AddFrame(AFrameID)); + end; +end; + +function TJvID3ContentFrame.GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal; +begin + { Text encoding $xx + Language $xx xx xx + Short content descrip. <text string according to encoding> $00 (00) + The actual text <full text string according to encoding> + } + Result := 1 + 3 + + LengthEnc(Description, ToEncoding) + + LengthTerminatorEnc(ToEncoding) + + LengthEnc(Text, ToEncoding); +end; + +function TJvID3ContentFrame.GetIsEmpty: Boolean; +begin + Result := ((Length(FLanguage) = 0) or (FLanguage = cUnknownLanguage)) and + (Text = '') and (Description = ''); +end; + +function HasNonISO_8859_1Chars(const S: WideString): Boolean; +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. <text string according to encoding> $00 (00) + The actual text <full text string according to encoding> + } + + with Stream do + begin + ReadEncoding; + ReadLanguage(FLanguage); + ReadStringEnc(FDescription); + ReadStringEnc(FText); + end; +end; + +function TJvID3ContentFrame.SameUniqueIDAs(const Frame: TJvID3Frame): Boolean; +begin + { There may be more than one comment frame in each tag, but only one with + the same language and content descriptor. + There may be more than one 'Unsynchronised lyrics/text transcription' frame + in each tag, but only one with the same language and content descriptor. + } + Result := (Frame is TJvID3ContentFrame) and + (Frame.FrameID = FrameID) and (FrameID in [fiComment, fiUnsyncedLyrics]); + + if Result then + Result := + AnsiSameStr(TJvID3ContentFrame(Frame).Language, Self.Language) and + 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. <text string according to encoding> $00 (00) + The actual text <full text string according to encoding> + } + + with Stream do + begin + WriteEncoding; + WriteLanguage(Language); + WriteStringEnc(Description); + WriteTerminatorEnc; + WriteStringEnc(Text); + end; +end; + + +//=== { TJvID3Controller } =================================================== + +constructor TJvID3Controller.Create(AOwner: TComponent); +begin + inherited Create(AOwner); + + FFrames := TJvID3Frames.Create(Self); + FHeader := TJvID3Header.Create(Self); + FExtendedHeader := TJvID3ExtendedHeader.Create(Self); + FFileInfo := TJvID3FileInfo.Create; + + FActivateEvents := TList.Create; + FClients := TList.Create; + FState := []; + + { Defaults } + FReadEncodingAs := ifeAuto; + FWriteEncodingAs := ifeAuto; + FReadVersionAs := ifvDontCare; + FWriteVersionAs := ifvDontCare; + FOptions := [coAutoCorrect, coRemoveEmptyFrames]; +end; + +destructor TJvID3Controller.Destroy; +begin + SetActive(False); + + inherited Destroy; + + FreeAndNil(FActivateEvents); + FreeAndNil(FClients); + + FDesigner.Free; + FDesigner := nil; + + FreeAndNil(FFrames); + FHeader.Free; + FExtendedHeader.Free; + FFileInfo.Free; + FStream.Free; +end; + +function TJvID3Controller.AddFrame(const AFrameID: TJvID3FrameID): TJvID3Frame; +var + FrameClass: TJvID3FrameClass; +begin + if not Active and not (icsReading in FState) then + ID3Error(RsEID3ControllerNotActive, Self); + + FrameClass := GetFrameClass(AFrameID); + + Result := FrameClass.Create(Self, AFrameID); + try + Result.Name := CreateUniqueName(Self, Result.FrameName, FrameClass, Result); + Result.Controller := Self; + except + Result.Free; + { Suppress errors while reading } + if not (icsReading in FState) then + raise; + end; +end; + +procedure TJvID3Controller.ApplyUnsynchronisationSchemeOnCurrentStream; +var + TmpStream: TMemoryStream; + LTempStreamSize: Cardinal; +begin + TmpStream := TMemoryStream.Create; + try + if icsUsingTempStream in FState then + begin + if not Assigned(FTempStream) then + ID3Error(RsENoTempStream, Self); + + LTempStreamSize := GetTempStreamSize; + FTempStream.Seek(0, soBeginning); + ApplyUnsynchronisationScheme(FTempStream, TmpStream, LTempStreamSize); + TmpStream.Seek(0, soBeginning); + FTempStream.Seek(0, soBeginning); + FTempStream.CopyFrom(TmpStream, TmpStream.Size); + end + else + begin + { Exclude header (size=10) from the unsynchronisation } + FStream.Seek(10, soBeginning); + ApplyUnsynchronisationScheme(FStream, TmpStream, FStream.Size - 10); + TmpStream.Seek(0, soBeginning); + FStream.Seek(10, soBeginning); + FStream.CopyFrom(TmpStream, TmpStream.Size); + end; + finally + TmpStream.Free; + end; +end; + +procedure TJvID3Controller.BeginReading; +begin + if FState <> [] then + ID3Error(RsEAlreadyReadingWriting, Self); + + Include(FState, icsReading); + FStream := TJvID3Stream.Create; + + BeginUpdate; +end; + +procedure TJvID3Controller.BeginUpdate; +begin + Inc(FUpdateCount); +end; + +procedure TJvID3Controller.BeginUseTempStream; +begin + if icsUsingTempStream in FState then + ID3Error(RsEAlreadyUsingTempStream, Self); + + Include(FState, icsUsingTempStream); + if not Assigned(FTempStream) then + FTempStream := TJvID3Stream.Create; + + FTempStream.Seek(0, soBeginning); + + { Init FTempStream as FStream } + FTempStream.FSourceEncoding := FStream.FSourceEncoding; + FTempStream.FDestEncoding := FStream.FDestEncoding; + FTempStream.FAllowedEncodings := FStream.FAllowedEncodings; +end; + +procedure TJvID3Controller.BeginWriting; +begin + if FState <> [] then + ID3Error(RsEAlreadyReadingWriting, Self); + + Include(FState, icsWriting); + FStream := TJvID3Stream.Create; + + BeginUpdate; +end; + +function TJvID3Controller.CanAddFrame(const AFrameID: TJvID3FrameID): Boolean; +var + FrameClass: TJvID3FrameClass; +begin + { While reading we can always add all kinds of frames, ie we accept that the + stream may contain errors } + if icsReading in FState then + begin + Result := True; + Exit; + end; + + FrameClass := GetFrameClass(AFrameID); + if Assigned(FrameClass) then + Result := FrameClass.CanAddFrame(Self, AFrameID) + else + Result := False; +end; + +procedure TJvID3Controller.ChangeToVersion(const ANewVersion: TJvID3Version); +begin + Frames.ChangeToVersion(ANewVersion); + Header.ChangeToVersion(ANewVersion); + ExtendedHeader.ChangeToVersion(ANewVersion); +end; + +procedure TJvID3Controller.CheckFrameClass(FrameClass: TJvID3FrameClass; + const AFrameID: TJvID3FrameID); +var + LFrameClass: string; +begin + if FrameClass <> GetFrameClass(AFrameID) then + begin + if Assigned(FrameClass) then + LFrameClass := FrameClass.ClassName + else + LFrameClass := ''; + ID3ErrorFmt(RsEID3InvalidFrameClass, [LFrameClass, ID3_FrameIDToString(AFrameID)], Self); + end; +end; + +procedure TJvID3Controller.Close; +begin + SetActive(False); +end; + +procedure TJvID3Controller.Commit; +const + CHandleError: array [Boolean] of TJvID3HandleError = (heRaise, heAutoCorrect); +begin + if not Active then + ID3Error(RsEID3ControllerNotActive); + + try + if coRemoveEmptyFrames in Options then + FFrames.RemoveEmptyFrames; + FFrames.CheckFrames(CHandleError[coAutoCorrect in Options]); + + SaveToFile(FFileName); + SetModified(False); + except + if csDesigning in ComponentState then + if Assigned(Classes.ApplicationHandleException) then + Classes.ApplicationHandleException(ExceptObject) + else + ShowException(ExceptObject, ExceptAddr) + else + raise; + end; +end; + +function TJvID3Controller.CopyFromID3v1(const DoOverwrite: Boolean): Boolean; +var + ID3v1Ctrl: TJvID3v1; +begin + if not Active then + ID3Error(RsEID3ControllerNotActive, Self); + + ID3v1Ctrl := TJvID3v1.Create(nil); + try + ID3v1Ctrl.FileName := FileName; + ID3v1Ctrl.Open; + Result := ID3v1Ctrl.HasTag; + if Result then + CopyFromID3v1Ctrl(ID3v1Ctrl, DoOverwrite); + finally + ID3v1Ctrl.Free; + end; +end; + +procedure TJvID3Controller.CopyFromID3v1Ctrl(AID3v1: TJvID3v1; + const DoOverwrite: Boolean); +var + Frame: TJvID3Frame; + Year: Word; + + function GetFrame(AFrameID: TJvID3FrameID): TJvID3Frame; + begin + Result := FFrames.FindFrame(AFrameID); + if Assigned(Result) and not DoOverwrite then + { If the frame already exists, and we don't want to overwrite, return nil } + Result := nil + else + if not Assigned(Result) then + { If the frame does not exists, create one } + Result := AddFrame(AFrameID); + end; + +begin + { There is a lot of extra code, because it may be possible that some frame + is not encoded in ISO-8859-1 } + + if not Assigned(AID3v1) then + Exit; + + // Songname + Frame := GetFrame(fiTitle); + if Assigned(Frame) then + TJvID3TextFrame(Frame).Text := UTF8ToISO_8859_1(AID3v1.SongName); + + // Artist + Frame := GetFrame(fiLeadArtist); + if Assigned(Frame) then + begin + TJvID3CustomTextFrame(Frame).Text := UTF8ToISO_8859_1(AID3v1.Artist); + end; + + // Album + Frame := GetFrame(fiAlbum); + if Assigned(Frame) then + TJvID3TextFrame(Frame).Text := UTF8ToISO_8859_1(AID3v1.Album); + + // Year + Year := StrToIntDef(string(AID3v1.Year), 0); + if Year > 0 then + begin + if Version = ive2_4 then + begin + Frame := GetFrame(fiRecordingTime); + if Assigned(Frame) then + TJvID3TimeStampFrame(Frame).FValue := EncodeDate(Year, 1, 1); + end + else + begin + Frame := GetFrame(fiYear); + if Assigned(Frame) then + TJvID3NumberFrame(Frame).FValue := Year; + end; + end; + + // Comment + Frame := GetFrame(fiComment); + if Assigned(Frame) then + TJvID3ContentFrame(Frame).Text := UTF8ToISO_8859_1(AID3v1.Comment); + + // Genre + Frame := GetFrame(fiContentType); + if Assigned(Frame) then + begin + if AID3v1.Genre = 255 then + TJvID3TextFrame(Frame).Text := '' + else + TJvID3TextFrame(Frame).Text := Format('(%d)', [AID3v1.Genre]); + end; + + // AlbumTrack + if AID3v1.AlbumTrack > 0 then + begin + Frame := GetFrame(fiTrackNum); + if Assigned(Frame) then + TJvID3TextFrame(Frame).Text := IntToStr(AID3v1.AlbumTrack); + end; +end; + +function TJvID3Controller.CopyToID3v1(const DoOverwrite: Boolean): Boolean; +var + ID3v1Ctrl: TJvID3v1; +begin + if not Active then + ID3Error(RsEID3ControllerNotActive, Self); + + ID3v1Ctrl := TJvID3v1.Create(nil); + try + ID3v1Ctrl.FileName := FileName; + ID3v1Ctrl.Open; + CopyToID3v1Ctrl(ID3v1Ctrl, DoOverwrite); + Result := ID3v1Ctrl.Commit; + finally + ID3v1Ctrl.Free; + end; +end; + +procedure TJvID3Controller.CopyToID3v1Ctrl(AID3v1: TJvID3v1; + const DoOverwrite: Boolean); +var + S: string; + Frame: TJvID3Frame; + Track, P: Integer; + I: Integer; + YearSet, CommentSet: Boolean; +begin + { There is a lot of extra code, because it may be possible that some frame + is not encoded in ISO-8859-1 } + + if not Assigned(AID3v1) then + Exit; + + YearSet := False; + CommentSet := False; + + for I := 0 to FrameCount - 1 do + begin + Frame := FFrames[I]; + + with AID3v1 do + case Frame.FrameID of + fiTitle: + if DoOverwrite or (SongName = '') then + SongName := ISO_8859_1ToUTF8(Copy(TJvID3TextFrame(Frame).Text, 1, 30)); + fiLeadArtist: + if DoOverwrite or (Artist = '') then + begin + { Note: fiLeadArtist has multiple lines } + Artist := ISO_8859_1ToUTF8(Copy(TJvID3CustomTextFrame(Frame).Text, 1, 30)); + end; + fiAlbum: + if DoOverwrite or (Album = '') then + Album := ISO_8859_1ToUTF8(Copy(TJvID3TextFrame(Frame).Text, 1, 30)); + fiYear: + if not YearSet and (DoOverwrite or (Year = '')) then + begin + Year := Format('%.4d', [TJvID3NumberFrame(Frame).Value]); + YearSet := True; + end; + fiRecordingTime: + if not YearSet and (DoOverwrite or (Year = '')) then + begin + Year := Format('%.4d', [YearOf(TJvID3TimestampFrame(Frame).Value)]); + YearSet := True; + end; + fiComment: + { Note : there may be more than 1 fiComment frame in the tag, just + pick the first we encounter } + if not CommentSet and (DoOverwrite or (SongName = '')) then + begin + Comment := ISO_8859_1ToUTF8(Copy(TJvID3ContentFrame(Frame).Text, 1, 30)); + CommentSet := True; + end; + fiContentType: + if DoOverwrite or (Genre = 255) then + Genre := ID3_LongGenreToID(TJvID3TextFrame(Frame).Text); + fiTrackNum: + if DoOverwrite or (AlbumTrack = 0) then + begin + S := TJvID3TextFrame(Frame).Text; + P := Pos('/', S); + if P > 0 then + Track := StrToIntDef(Copy(S, 1, P - 1), 0) + else + Track := StrToIntDef(S, 0); + if (Track < 0) or (Track > 255) then + Track := 0; + AlbumTrack := Byte(Track); + end; + end; + end; +end; + +procedure TJvID3Controller.DoClose; +begin + { Note: this will set Modified to True... } + Frames.Clear; + FFileInfo.Reset; + FActive := False; + + { ... thus we set it now back to false } + SetModified(False); +end; + +procedure TJvID3Controller.DoOpen; +var + FileStream: TFileStream; +begin + FileStream := TFileStream.Create(FFileName, fmOpenRead or fmShareDenyWrite); + try + LoadFromStream(FileStream); + FActive := True; + + if ReadVersionAs <> ifvDontCare then + FFrames.ChangeToVersion(CForceVersionToVersion[ReadVersionAs]); + finally + FileStream.Free; + end; +end; + +procedure TJvID3Controller.EndReading; +begin + if not (icsReading in FState) then + ID3Error(RsENotReading, Self); + + Exclude(FState, icsReading); + FreeAndNil(FStream); + FreeAndNil(FTempStream); + + EndUpdate; +end; + +procedure TJvID3Controller.EndUpdate; +begin + Dec(FUpdateCount); + if FUpdateCount = 0 then + ID3Event(ideID3Change, 0); +end; + +procedure TJvID3Controller.EndUseTempStream; +begin + if not (icsUsingTempStream in FState) then + ID3Error(RsENotUsingTempStream, Self); + + Exclude(FState, icsUsingTempStream); + { Do not free the temp stream } +end; + +procedure TJvID3Controller.EndWriting; +begin + if not (icsWriting in FState) then + ID3Error(RsENotWriting, Self); + + Exclude(FState, icsWriting); + FreeAndNil(FStream); + FreeAndNil(FTempStream); + + EndUpdate; +end; + +procedure TJvID3Controller.EnsureExists(const FrameIDs: TJvID3FrameIDs); +var + FrameID: TJvID3FrameID; + IDs: TJvID3FrameIDs; +begin + if not Active then + ID3Error(RsEID3ControllerNotActive, Self); + + IDs := FrameIDs - FFrames.GetFrameIDs; + { IDs represents a set of frames we have to construct } + + if IDs <> [] then + for FrameID := Low(TJvID3FrameID) to High(TJvID3FrameID) do + if (FrameID in IDs) and not (GetFrameClass(FrameID) = TJvID3SkipFrame) then + AddFrame(FrameID); +end; + +procedure TJvID3Controller.Erase; +var + SavedActive: Boolean; +begin + SavedActive := Active; + Close; + ChangeTagSize(FileName, 0); + + if SavedActive then + begin + Open; + { Force Modified to be True } + SetModified(True); + end; +end; + +function TJvID3Controller.FindFirstFrame(const AFrameID: TJvID3FrameID; + var Frame: TJvID3Frame): Boolean; +begin + Frame := nil; + Result := FindNextFrame(AFrameID, Frame); +end; + +function TJvID3Controller.FindNextFrame(const AFrameID: TJvID3FrameID; + var From: TJvID3Frame): Boolean; +var + I: Integer; +begin + if From = nil then + begin + From := Frames.FindFrame(AFrameID); + Result := Assigned(From); + end + else + begin + Result := True; + I := From.Index + 1; + while I < FrameCount do + begin + From := Frames[I]; + if From.FrameID = AFrameID then + Exit; + Inc(I); + end; + Result := False; + From := nil; + end; +end; + +class function TJvID3Controller.GetFrameClass(const FrameID: TJvID3FrameID): TJvID3FrameClass; +begin + Result := DefaultFrameClasses[FrameID]; + if not Assigned(Result) then + { TJvID3SkipFrame is the default frame for non-implemented frames } + Result := TJvID3SkipFrame; +end; + +function TJvID3Controller.GetFrameCount: Integer; +begin + Result := Frames.Count; +end; + +function TJvID3Controller.GetFrameCountFor(const AFrameID: TJvID3FrameID): Cardinal; +var + I: Integer; +begin + Result := 0; + for I := 0 to FrameCount - 1 do + if Frames[I].FrameID = AFrameID then + Inc(Result); +end; + +function TJvID3Controller.GetReadVersion: TJvID3Version; +begin + { Returns the end-version (2.3 or 2.4) of a tag when reading. For example + a tag can have version 2.3 (on disk) but when ReadVersionAs is set to ifv2_4 + it will be translated to a v2.4 tag, and ReadVersion will return ive2_4 in + this case } + + case ReadVersionAs of + ifvDontCare: + begin + Result := Version; + if Result < ive2_2 then + Result := ive2_2 + else + if Result > ive2_4 then + Result := ive2_4; + end; + ifv2_2: + Result := ive2_2; + ifv2_3: + Result := ive2_3; + ifv2_4: + Result := ive2_4; + else + Result := ive2_3; + ID3Error(RsEID3UnknownVersion, Self); + end; +end; + +function TJvID3Controller.GetTagSize: Cardinal; +begin + if not Active then + Result := 0 + else + Result := Header.Size; +end; + +function TJvID3Controller.GetTempStreamSize: Cardinal; +begin + if not Assigned(FTempStream) then + ID3Error(RsENoTempStream, Self); + + Result := FTempStream.Position; +end; + +function TJvID3Controller.GetVersion: TJvID3Version; +begin + Result := MajorVersionToVersion(FHeader.MajorVersion); +end; + +function TJvID3Controller.GetWriteVersion: TJvID3Version; +begin + { Returns the end-version (2.3 or 2.4) of a tag when writing. For example + a tag can have version 2.3 but when WriteVersionAs is set to ifv2_4 it will + be translated to a v2.4 tag, and WriteVersion will return ive2_4 in this + case } + case WriteVersionAs of + ifvDontCare: + begin + Result := Version; + { Default to v2.4; latest version } + if (Result < ive2_2) or (Result > ive2_4) then + Result := ive2_4; + end; + ifv2_2: + Result := ive2_2; + ifv2_3: + Result := ive2_3; + ifv2_4: + Result := ive2_4; + else + Result := ive2_3; + ID3Error(RsEID3UnknownVersion, Self); + end; +end; + +function TJvID3Controller.HasFrame(const AFrameID: TJvID3FrameID): Boolean; +begin + Result := Assigned(Frames.FindFrame(AFrameID)); +end; + +procedure TJvID3Controller.ID3Event(Event: TJvID3Event; Info: Integer); +begin + if (Event in [ideFrameChange, ideFrameListChange]) and + (FState * [icsReading, icsWriting] = []) then + SetModified(True); + + if (FUpdateCount = 0) and Assigned(FDesigner) then + FDesigner.ID3Event(Event, Info); +end; + +procedure TJvID3Controller.Loaded; +begin + inherited Loaded; + try + if FStreamedActive then + SetActive(True); + except + if csDesigning in ComponentState then + if Assigned(Classes.ApplicationHandleException) then + Classes.ApplicationHandleException(ExceptObject) + else + ShowException(ExceptObject, ExceptAddr) + else + raise; + end; +end; + +procedure TJvID3Controller.LoadFromStream(AStream: TStream); +begin + BeginReading; + try + { Clear } + FHeader.Reset; + FExtendedHeader.Reset; + FFrames.Reset; + + { Read the header } + if AStream.Size >= 10 then + FStream.ReadFromStream(AStream, 10) + else + Exit; + + { Parse the header } + FHeader.Read; + + if FHeader.HasTag and (Version in CSupportedVersions) then + begin + + { Init encoding after the version is read } + FStream.InitAllowedEncodings(ReadVersion, ReadEncodingAs); + + { Note that we will overwrite the header in FStream (first 10 bytes in FStream) } + FStream.Position := 0; + + if hfUnsynchronisation in FHeader.Flags then + { Unsynchronisation scheme is applied to the tag, we have to remove it, + ie replace $FF $00 with $FF } + RemoveUnsynchronisationScheme(AStream, FStream, FHeader.Size) + else + { If not, we just copy the stream to the memory stream } + FStream.ReadFromStream(AStream, FHeader.Size); + + FStream.Position := 0; + + if hfExtendedHeader in FHeader.Flags then + { Read extended header, note that it's read after the unsynchronisation + scheme is removed } + FExtendedHeader.Read; + + FFrames.Read; + end; + + if Header.HasTag then + FFileInfo.Read(AStream, 10 + Header.Size) + 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 <text string> $00 + FileName <text string according to encoding> $00 (00) + Content description <text string according to encoding> $00 (00) + Encapsulated object <binary data> + } + Result := 1 + Cardinal(Length(MIMEType)) + 1 + + LengthEnc(FileName, ToEncoding) + + LengthTerminatorEnc(ToEncoding) + + LengthEnc(ContentDescription, ToEncoding) + + LengthTerminatorEnc(ToEncoding) + + DataSize; +end; + +function TJvID3GeneralObjFrame.GetIsEmpty: Boolean; +begin + Result := inherited GetIsEmpty and (Length(FMIMEType) = 0) and + (ContentDescription = '') and + (FileName = ''); +end; + +function TJvID3GeneralObjFrame.MustWriteAsUTF: Boolean; +begin + Result := HasNonISO_8859_1Chars(FileName) or HasNonISO_8859_1Chars(ContentDescription); +end; + +procedure TJvID3GeneralObjFrame.ReadFrame; +begin + { Text encoding $xx + MIME type <text string> $00 + FileName <text string according to encoding> $00 (00) + Content description <text string according to encoding> $00 (00) + Encapsulated object <binary data> + } + with Stream do + begin + ReadEncoding; + ReadStringA(FMIMEType); + ReadStringEnc(FFileName); + ReadStringEnc(FContentDescription); + end; + ReadData(Stream.BytesTillEndOfFrame); +end; + +function TJvID3GeneralObjFrame.SameUniqueIDAs(const Frame: TJvID3Frame): Boolean; +begin + { There may be more than one "GEOB" frame in each tag, but only one with the + same content descriptor. } + Result := (Frame is TJvID3GeneralObjFrame) and + (Frame.FrameID = FrameID) and (FrameID = fiGeneralObject); + + if Result then + Result := 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 <text string> $00 + FileName <text string according to encoding> $00 (00) + Content description <text string according to encoding> $00 (00) + Encapsulated object <binary data> + } + with Stream do + begin + WriteEncoding; + WriteStringA(MIMEType); + WriteTerminatorA; + WriteStringEnc(FileName); + WriteTerminatorEnc; + WriteStringEnc(ContentDescription); + WriteTerminatorEnc; + end; + WriteData; +end; + + +//=== { TJvID3Header } ======================================================= + +procedure TJvID3Header.Assign(Source: TPersistent); +begin + if Source is TJvID3Header then + begin + FHasTag := TJvID3Header(Source).HasTag; + FRevisionNumber := TJvID3Header(Source).RevisionNumber; + FMajorVersion := TJvID3Header(Source).MajorVersion; + FSize := TJvID3Header(Source).Size; + FFlags := TJvID3Header(Source).Flags; + end + else + inherited Assign(Source); +end; + +procedure TJvID3Header.ChangeToVersion(const ANewVersion: TJvID3Version); +begin + case ANewVersion of + ive2_2: + begin + FRevisionNumber := 0; + FMajorVersion := 2; + { Only flag 'hfUnsynchronisation' is allowed } + FFlags := FFlags * [hfUnsynchronisation]; + end; + ive2_3: + begin + FRevisionNumber := 0; + FMajorVersion := 3; + Exclude(FFlags, hfFooterPresent); + end; + ive2_4: + begin + FRevisionNumber := 0; + FMajorVersion := 4; + end; + else + ID3Error(RsEID3VersionNotSupported); + end; +end; + +procedure TJvID3Header.Read; +var + Header: TID3v2HeaderRec; +begin + Reset; + + with Stream do + begin + BeginReadFrame(10); + try + if Read(Header, 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 <text string> $00 + Date of purch. <text string> + Seller <text string according to encoding> } +function TJvID3OwnershipFrame.GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal; +begin + Result := 1 + Cardinal(Length(FPricePayed)) + 1 + 8 + + LengthEnc(Seller, ToEncoding); +end; + +function TJvID3OwnershipFrame.GetIsEmpty: Boolean; +begin + Result := (Length(FPricePayed) = 0) and (Seller = '') and + (FDateOfPurch = 0); +end; + +function TJvID3OwnershipFrame.MustWriteAsUTF: Boolean; +begin + Result := HasNonISO_8859_1Chars(Seller); +end; + +{ Text encoding $xx + Price payed <text string> $00 + Date of purch. <text string> + Seller <text string according to encoding> } +procedure TJvID3OwnershipFrame.ReadFrame; +begin + with Stream do + begin + ReadEncoding; + ReadStringA(FPricePayed); + ReadDate(FDateOfPurch); + ReadStringEnc(FSeller); + end; +end; + +function TJvID3OwnershipFrame.SameUniqueIDAs(const Frame: TJvID3Frame): Boolean; +begin + { There may only be one 'OWNE' frame in a tag } + Result := (Assigned(Frame) and (Frame.FrameID = FrameID) and (FrameID = fiOwnership)) or + inherited SameUniqueIDAs(Frame); +end; + +procedure TJvID3OwnershipFrame.SetDateOfPurch(const Value: TDateTime); +begin + if FDateOfPurch <> Value then + begin + FDateOfPurch := Value; + Changed; + end; +end; + +procedure TJvID3OwnershipFrame.SetPricePayed(const Value: AnsiString); +begin + if FPricePayed <> Value then + begin + FPricePayed := Value; + Changed; + end; +end; + +//procedure TJvID3OwnershipFrame.SetSeller(const Value: WideString); +procedure TJvID3OwnershipFrame.SetSeller(const Value: String); +begin + if Value <> FSeller then + begin + FSeller := Value; + Changed; + end; +end; + +function TJvID3OwnershipFrame.SupportsVersion(const AVersion: TJvID3Version): Boolean; +begin + case FrameID of + { ** Not supported in 2.2 ** } + + fiOwnership: + Result := AVersion in [ive2_3, ive2_4]; + else + Result := True; + end; +end; + +procedure TJvID3OwnershipFrame.WriteFrame; +begin + { Text encoding $xx + Price payed <text string> $00 + Date of purch. <text string> + Seller <text string according to encoding> + } + with Stream do + begin + WriteEncoding; + WriteStringA(PricePayed); + WriteTerminatorA; + WriteDate(DateOfPurch); + WriteStringEnc(Seller); + end; +end; + + +//=== { TJvID3PictureFrame } ================================================= + +procedure TJvID3PictureFrame.Assign(Source: TPersistent); +var + lStream: TMemoryStream; +begin + if Source is TPicture then + Assign(TPicture(Source).Graphic) + else + if Source is TGraphic then + begin + lStream := TMemoryStream.Create; + try + TGraphic(Source).SaveToStream(lStream); + lStream.Seek(0, soBeginning); + LoadFromStream(lStream); + finally + lStream.Free; + end; + end + else + if Source is TJvID3PictureFrame then + begin + FMIMEType := TJvID3PictureFrame(Source).MIMEType; + FPictureType := TJvID3PictureFrame(Source).PictureType; + FDescription := TJvID3PictureFrame(Source).Description; + FURL := TJvID3PictureFrame(Source).URL; + end + else + inherited Assign(Source); +end; + +procedure TJvID3PictureFrame.AssignTo(Dest: TPersistent); +var + TmpFileName: string; +begin + if (Dest is TPicture) or (Dest is TGraphic) then + begin + if (DataSize > 0) and (MIMEType <> cURLArrow) then + begin + { !! We can't use FileGetTempName; it /creates/ a file with extension TMP but + we need to have a specific extension } +// TmpFileName := SysUtils.IncludeTrailingPathDelimiter(PathGetTempPath) + cPictureFrameFileNameTemplate + '_' + IntToStr(Random(500)) + '_' + FormatDateTime('zzz', Now); + TmpFileName := AppendPathDelim(GetTempDir) + cPictureFrameFileNameTemplate + '_' + IntToStr(Random(500)) + '_' + FormatDateTime('zzz', Now); + TmpFileName := FindUnusedFileName(TmpFileName, MIMETypeToExt(string(MIMEType)), ''); + try + SaveToFile(TmpFileName); + try + try + if Dest is TPicture then + TPicture(Dest).LoadFromFile(TmpFileName) + else + if Dest is TGraphic then + TGraphic(Dest).LoadFromFile(TmpFileName); + except + on EInvalidGraphic do + ; { Do nothing } + end + finally + SysUtils.DeleteFile(TmpFileName); + end; + except + { Something went wrong while saving picture to file } + end; + end + else + Dest.Assign(nil); + end + else + inherited AssignTo(Dest); +end; + +class function TJvID3PictureFrame.CanAddFrame(AController: TJvID3Controller; + AFrameID: TJvID3FrameID): Boolean; +begin + { There may be several pictures attached to one file, each in their + individual "APIC" frame, but only one with the same content descriptor. + There may only be one picture with the picture type declared as picture + type $01 and $02 respectively. + } + Result := (AFrameID = fiPicture) or inherited CanAddFrame(AController, AFrameID); +end; + +function TJvID3PictureFrame.CheckFrame(const HandleError: TJvID3HandleError): Boolean; +begin + { The description has a maximum length of 64 characters, but may be empty. } + + Result := CheckMaxCharCount(Self, FDescription, 64, HandleError); + if not Result and (HandleError = heAutoCorrect) then + begin + UpdateFrameSize; + Result := True; + end; +end; + +procedure TJvID3PictureFrame.Clear; +begin + FMIMEType := ''; + FPictureType := ptOther; + FDescription := ''; + FURL := ''; + + inherited Clear; +end; + +class function TJvID3PictureFrame.Find(AController: TJvID3Controller; + const AType: TJvID3PictureType): TJvID3PictureFrame; +var + Frame: TJvID3Frame; +begin + Result := nil; + if not Assigned(AController) or not AController.Active then + Exit; + + if not AController.FindFirstFrame(fiPicture, Frame) then + Exit; + + while (Frame is TJvID3PictureFrame) and + (TJvID3PictureFrame(Frame).PictureType <> AType) do + AController.FindNextFrame(fiPicture, Frame); + + if Frame is TJvID3PictureFrame then + Result := TJvID3PictureFrame(Frame) +end; + +class function TJvID3PictureFrame.FindOrCreate(AController: TJvID3Controller; + const AType: TJvID3PictureType): TJvID3PictureFrame; +begin + if not Assigned(AController) then + ID3Error(RsEID3NoController); + + Result := Find(AController, AType); + if not Assigned(Result) then + begin + Result := TJvID3PictureFrame(AController.AddFrame(fiPicture)); + Result.PictureType := AType; + end; +end; + +function TJvID3PictureFrame.GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal; +begin + { Text encoding: $xx + MIME type: <text string> $00 + Picture type: $xx + Description: <text string according to encoding> $00 (00) + Picture data: <binary data> + } + if HasOnlyURL then + Result := 1 + Length(cURLArrow) + 1 + 1 + + LengthEnc(Description, ToEncoding) + + LengthTerminatorEnc(ToEncoding) + Cardinal(Length(URL)) + else + Result := 1 + Cardinal(Length(MIMEType)) + 1 + 1 + + LengthEnc(Description, ToEncoding) + + LengthTerminatorEnc(ToEncoding) + DataSize; +end; + +function TJvID3PictureFrame.GetHasOnlyURL: Boolean; +begin + Result := (DataSize = 0) and (URL > ''); +end; + +function TJvID3PictureFrame.GetIsEmpty: Boolean; +begin + { Don't care about FPictureType } + Result := inherited GetIsEmpty and + ((Length(MIMEType) = 0) or (MIMEType = cURLArrow)) and + (Length(URL) = 0) and (Description = ''); +end; + +function TJvID3PictureFrame.MustWriteAsUTF: Boolean; +begin + Result := HasNonISO_8859_1Chars(Description); +end; + +procedure TJvID3PictureFrame.ReadFrame; +var + LPictureType: Byte; +begin + { Text encoding $xx + MIME type <text string> $00 + Picture type $xx + Description <text string according to encoding> $00 (00) + Picture data <binary data> + } + with Stream do + begin + ReadEncoding; + ReadStringA(FMIMEType); + if BytesTillEndOfFrame < 1 then + Exit; + + Read(LPictureType, 1); + if LPictureType <= Integer(High(TJvID3PictureType)) then + FPictureType := TJvID3PictureType(LPictureType) + else + FPictureType := ptOther; + + ReadStringEnc(FDescription); + + if MIMEType = cURLArrow then + { There is the possibility to put only a link to the image file by using + the 'MIME type' "-->" and having a complete URL instead of picture data. + } + ReadStringA(FURL) + else + Self.ReadData(BytesTillEndOfFrame); + end; +end; + +function TJvID3PictureFrame.SameUniqueIDAs(const Frame: TJvID3Frame): Boolean; +begin + { There may be several pictures attached to one file, each in their + individual "APIC" frame, but only one with the same content descriptor. + There may only be one picture with the picture type declared as picture + type $01 and $02 respectively. + } + Result := (Frame is TJvID3PictureFrame) and + (Frame.FrameID = FrameID) and (FrameID = fiPicture); + + if Result then + Result := + (TJvID3PictureFrame(Frame).PictureType = PictureType) and + ((PictureType in [ptFileIcon, ptOtherFileIcon]) or + 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 <text string> $00 + Picture type $xx + Description <text string according to encoding> $00 (00) + Picture data <binary data> + + There is the possibility to put only a link to the image file by using + the 'MIME type' "-->" and having a complete URL instead of picture data. } + + with Stream do + begin + WriteEncoding; + if HasOnlyURL then + WriteStringA(cURLArrow) + else + WriteStringA(MIMEType); + WriteTerminatorA; + + Write(PictureType, 1); + + WriteStringEnc(Description); + WriteTerminatorEnc; + if HasOnlyURL then + WriteStringA(URL) + else + Self.WriteData; + end; +end; + + +//=== { TJvID3PlayCounterFrame } ============================================= + +procedure TJvID3PlayCounterFrame.Assign(Source: TPersistent); +begin + if Source is TJvID3PlayCounterFrame then + FCounter := TJvID3PlayCounterFrame(Source).Counter; + + inherited Assign(Source); +end; + +class function TJvID3PlayCounterFrame.CanAddFrame(AController: TJvID3Controller; + AFrameID: TJvID3FrameID): Boolean; +begin + { There may only be one "PCNT" frame in each tag. } + Result := not AController.HasFrame(AFrameID) or + inherited CanAddFrame(AController, AFrameID); +end; + +function TJvID3PlayCounterFrame.CheckFrame(const HandleError: TJvID3HandleError): Boolean; +begin + Result := True; +end; + +procedure TJvID3PlayCounterFrame.Clear; +begin + FCounter := 0; + inherited Clear; +end; + +class function TJvID3PlayCounterFrame.Find(AController: TJvID3Controller): TJvID3PlayCounterFrame; +var + Frame: TJvID3Frame; +begin + Result := nil; + if not Assigned(AController) or not AController.Active then + Exit; + + Frame := AController.Frames.FindFrame(fiPlayCounter); + if Frame is TJvID3PlayCounterFrame then + Result := TJvID3PlayCounterFrame(Frame) +end; + +class function TJvID3PlayCounterFrame.FindOrCreate(AController: TJvID3Controller): TJvID3PlayCounterFrame; +begin + if not Assigned(AController) then + ID3Error(RsEID3NoController); + + Result := Find(AController); + if not Assigned(Result) then + Result := TJvID3PlayCounterFrame(AController.AddFrame(fiPlayCounter)); +end; + +function TJvID3PlayCounterFrame.GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal; +begin + Result := 4; +end; + +function TJvID3PlayCounterFrame.GetIsEmpty: Boolean; +begin + Result := False; +end; + +procedure TJvID3PlayCounterFrame.ReadFrame; +begin + Stream.ReadNumber(FCounter); +end; + +function TJvID3PlayCounterFrame.SameUniqueIDAs(const Frame: TJvID3Frame): Boolean; +begin + { There may only be one "PCNT" frame in each tag. } + Result := ((Frame.FrameID = FrameID) and (FrameID = fiPlayCounter)) or + inherited SameUniqueIDAs(Frame); +end; + +procedure TJvID3PlayCounterFrame.SetCounter(const Value: Cardinal); +begin + if FCounter <> Value then + begin + FCounter := Value; + Changed; + end; +end; + +procedure TJvID3PlayCounterFrame.WriteFrame; +begin + Stream.WriteNumber(FCounter); +end; + + +//=== { TJvID3PopularimeterFrame } =========================================== + +procedure TJvID3PopularimeterFrame.Assign(Source: TPersistent); +begin + if Source is TJvID3PopularimeterFrame then + begin + FRating := TJvID3PopularimeterFrame(Source).Rating; + FCounter := TJvID3PopularimeterFrame(Source).Counter; + FEMailAddress := TJvID3PopularimeterFrame(Source).EMailAddress; + end; + inherited Assign(Source); +end; + +class function TJvID3PopularimeterFrame.CanAddFrame(AController: TJvID3Controller; + AFrameID: TJvID3FrameID): Boolean; +begin + { There may be more than one "POPM" frame in each tag, but only one with the + same email address. } + Result := (AFrameID = fiPopularimeter) or inherited CanAddFrame(AController, AFrameID); +end; + +function TJvID3PopularimeterFrame.CheckFrame(const HandleError: TJvID3HandleError): Boolean; +begin + Result := True; +end; + +procedure TJvID3PopularimeterFrame.Clear; +begin + FRating := 0; + FCounter := 0; + FEMailAddress := ''; + + inherited Clear; +end; + +class function TJvID3PopularimeterFrame.Find(AController: TJvID3Controller): TJvID3PopularimeterFrame; +var + Frame: TJvID3Frame; +begin + Result := nil; + if not Assigned(AController) or not AController.Active then + Exit; + + Frame := AController.Frames.FindFrame(fiPopularimeter); + if Frame is TJvID3PopularimeterFrame then + Result := TJvID3PopularimeterFrame(Frame); +end; + +class function TJvID3PopularimeterFrame.Find(AController: TJvID3Controller; + const AEmailAddress: AnsiString): TJvID3PopularimeterFrame; +var + Frame: TJvID3Frame; +begin + Result := nil; + if not Assigned(AController) or not AController.Active then + Exit; + + if not AController.FindFirstFrame(fiPopularimeter, Frame) then + Exit; + + while (Frame is TJvID3PopularimeterFrame) and + not AnsiSameStr(AEmailAddress, TJvID3PopularimeterFrame(Frame).EMailAddress) do + AController.FindNextFrame(fiPopularimeter, Frame); + + if Frame is TJvID3PopularimeterFrame then + Result := TJvID3PopularimeterFrame(Frame); +end; + +class function TJvID3PopularimeterFrame.FindOrCreate(AController: TJvID3Controller): TJvID3PopularimeterFrame; +begin + if not Assigned(AController) then + ID3Error(RsEID3NoController); + + Result := Find(AController); + if not Assigned(Result) then + Result := TJvID3PopularimeterFrame(AController.AddFrame(fiPopularimeter)); +end; + +class function TJvID3PopularimeterFrame.FindOrCreate(AController: TJvID3Controller; + const AEmailAddress: AnsiString): TJvID3PopularimeterFrame; +begin + if not Assigned(AController) then + ID3Error(RsEID3NoController); + + Result := Find(AController, AEmailAddress); + if not Assigned(Result) then + begin + Result := TJvID3PopularimeterFrame(AController.AddFrame(fiPopularimeter)); + Result.EMailAddress := AEmailAddress; + end; +end; + +function TJvID3PopularimeterFrame.GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal; +begin + { Email to user <text string> $00 + Rating $xx + Counter $xx xx xx xx (xx ...) } + Result := Length(FEMailAddress) + 1 + 1 + 4; +end; + +function TJvID3PopularimeterFrame.GetIsEmpty: Boolean; +begin + Result := (FRating = 0) and (FCounter = 0) and (Length(FEMailAddress) = 0); +end; + +procedure TJvID3PopularimeterFrame.ReadFrame; +begin + { Email to user <text string> $00 + Rating $xx + Counter $xx xx xx xx (xx ...) } + with Stream do + begin + ReadStringA(FEMailAddress); + Read(FRating, 1); + ReadNumber(FCounter); + end; +end; + +function TJvID3PopularimeterFrame.SameUniqueIDAs(const Frame: TJvID3Frame): Boolean; +begin + { There may be more than one "POPM" frame in each tag, but only one with the + same email address. } + Result := (Frame is TJvID3PopularimeterFrame) and + (Frame.FrameID = FrameID) and (FrameID = fiPopularimeter); + + if Result then + Result := AnsiSameStr(TJvID3PopularimeterFrame(Frame).EMailAddress, EMailAddress) + else + Result := inherited SameUniqueIDAs(Frame); +end; + +procedure TJvID3PopularimeterFrame.SetCounter(const Value: Cardinal); +begin + if FCounter <> Value then + begin + FCounter := Value; + Changed; + end; +end; + +procedure TJvID3PopularimeterFrame.SetEMailAddress(const Value: AnsiString); +begin + if FEMailAddress <> Value then + begin + FEMailAddress := Value; + Changed; + end; +end; + +procedure TJvID3PopularimeterFrame.SetRating(const Value: Byte); +begin + if FRating <> Value then + begin + FRating := Value; + Changed; + end; +end; + +procedure TJvID3PopularimeterFrame.WriteFrame; +begin + { Email to user <text string> $00 + Rating $xx + Counter $xx xx xx xx (xx ...) } + with Stream do + begin + WriteStringA(EMailAddress); + WriteTerminatorA; + Write(Rating, 1); + WriteNumber(Counter); + end; +end; + + +//=== { TJvID3SimpleListFrame } ============================================== + +procedure TJvID3SimpleListFrame.AfterConstruction; +begin + inherited AfterConstruction; + + FList := TJvID3stringList.Create; + TStringList(FList).OnChange := @ListChanged; + (* + {$IFDEF COMPILER12_UP} + FList := TJvID3StringList.Create; + TStringList(FList).OnChange := ListChanged; + {$ELSE} + FList := JclUnicode.TWideStringList.Create; + JclUnicode.TWideStringList(FList).OnChange := ListChanged; + {$ENDIF COMPILER12_UP} + *) +end; + +procedure TJvID3SimpleListFrame.BeforeDestruction; +begin + inherited BeforeDestruction; + FList.Free; +end; + +function TJvID3SimpleListFrame.CheckFrame(const HandleError: TJvID3HandleError): Boolean; +begin + Result := False; + case FrameID of + fiLanguage: + case Encoding of + ienISO_8859_1: + Result := CheckIsLanguageList(Self, List, HandleError); + ienUTF_16, ienUTF_16BE, ienUTF_8: + Result := CheckIsLanguageList(Self, List, HandleError); + else + Error(RsEID3UnknownEncoding); + end; + else + case Encoding of + ienISO_8859_1: + Result := CheckList(Self, List, Separator, HandleError); + ienUTF_16, ienUTF_16BE, ienUTF_8: + Result := CheckList(Self, List, Separator, HandleError); + else + Error(RsEID3UnknownEncoding); + end; + end; + + if not Result and (HandleError = heAutoCorrect) then + begin + UpdateFrameSize; + Result := True; + end; +end; + +class function TJvID3SimpleListFrame.Find(AController: TJvID3Controller; + const AFrameID: TJvID3FrameID): TJvID3SimpleListFrame; +var + Frame: TJvID3Frame; +begin + Result := nil; + if not Assigned(AController) or not AController.Active then + Exit; + + Frame := AController.Frames.FindFrame(AFrameID); + if Frame is TJvID3SimpleListFrame then + Result := TJvID3SimpleListFrame(Frame); +end; + +class function TJvID3SimpleListFrame.FindOrCreate(AController: TJvID3Controller; + const AFrameID: TJvID3FrameID): TJvID3SimpleListFrame; +begin + if not Assigned(AController) then + ID3Error(RsEID3NoController); + + Result := Find(AController, AFrameID); + if not Assigned(Result) then + begin + AController.CheckFrameClass(TJvID3SimpleListFrame, AFrameID); + Result := TJvID3SimpleListFrame(AController.AddFrame(AFrameID)); + end; +end; + +function TJvID3SimpleListFrame.GetFixedStringLength: Integer; +begin + case FrameID of + fiLanguage: + Result := 3 + else + Result := -1; + end; +end; + +function TJvID3SimpleListFrame.GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal; +var + I: Integer; + CharLength: Integer; +begin + if ToEncoding = ienUTF_8 then + begin + //Result := 1 + Length(WideStringToUTF8(Text)); + Result := 1 + Length(Text); + Exit; + end; + + { Encoding byte = 1 } + Result := 1; + CharLength := 0; + + if FixedStringLength > 0 then + Inc(CharLength, List.Count * FixedStringLength) + else + begin + for I := 0 to List.Count - 1 do + begin + Inc(CharLength, Length(List[I])); + Inc(CharLength); // separator + end; + + { Set one separator less, the last line does not have a trailing + separator } + if not IsNullSeparator then + Dec(CharLength); + end; + + case ToEncoding of + ienISO_8859_1: + Inc(Result, CharLength); + ienUTF_16: + { Add the BOM's } + Inc(Result, List.Count * 2 + CharLength * 2); + ienUTF_16BE: + Inc(Result, CharLength * 2); + else + Error(RsEID3UnknownEncoding); + end; +end; + +function TJvID3SimpleListFrame.GetIsNullSeparator: Boolean; +begin +// Result := (FixedStringLength < 0) and (Separator = WideNull); + Result := (FixedStringLength < 0) and (Separator = #0); +end; + +//function TJvID3SimpleListFrame.GetSeparator: WideChar; +function TJvID3SimpleListFrame.GetSeparator: Char; +begin + case FrameID of + fiLyricist, fiComposer, fiOrigLyricist, fiOrigArtist, fiLeadArtist: +// Result := WideChar('/'); + Result := '/'; + fiLanguage, fiContentType: + Result := #0; +// Result := WideNull; + else + { ?? Unknown } + //Result := WideChar('/'); + Result := '/'; + end; +end; + +//function TJvID3SimpleListFrame.GetText: WideString; +function TJvID3SimpleListFrame.GetText: String; +begin + if Separator <> #0 then + Result := (FList as TJvID3StringList).GetSeparatedText(Separator) + else + Result := (FList as TJvID3StringList).GetSeparatedText(''); + (* + if Separator <> WideNull then + {$IFDEF COMPILER12_UP} + Result := (FList as TJvID3StringList).GetSeparatedText(Separator) + {$ELSE} + Result := (FList as JclUnicode.TWideStringList).GetSeparatedText(Separator) + {$ENDIF COMPILER12_UP} + else + {$IFDEF COMPILER12_UP} + Result := (FList as TJvID3StringList).GetSeparatedText(''); + {$ELSE} + Result := (FList as JclUnicode.TWideStringList).GetSeparatedText(''); + {$ENDIF COMPILER12_UP} + *) +end; + +procedure TJvID3SimpleListFrame.ListChanged(Sender: TObject); +begin + if not (icsReading in Controller.FState) then + Changed; +end; + +//procedure TJvID3SimpleListFrame.SetText(const ANewText: WideString); +procedure TJvID3SimpleListFrame.SetText(const ANewText: String); +begin + if FixedStringLength >= 0 then + ExtractFixedStrings(ANewText, FixedStringLength, List) + else + ExtractStrings(Separator, ANewText, List); +end; + +procedure TJvID3SimpleListFrame.ReadFrame; +const + cMinBytes: array [TJvID3Encoding] of Byte = (2, 4, 4, 2); +var + //S: WideString; + S: String; +begin + if IsNullSeparator then + begin + with Stream do + begin + ReadEncoding; + while BytesTillEndOfFrame > cMinBytes[Encoding] do + begin + ReadStringEnc(S); + List.Add(S); + end; + end; + end + else + inherited ReadFrame; +end; + +//procedure TJvID3SimpleListFrame.SetList(Value: {$IFDEF COMPILER12_UP}TStrings{$ELSE}JclUnicode.TWideStrings{$ENDIF COMPILER12_UP}); +procedure TJvID3SimpleListFrame.SetList(Value: TStrings); +begin + FList.Assign(Value); +end; + +procedure TJvID3SimpleListFrame.WriteFrame; +var + I: Integer; +begin + if IsNullSeparator then + begin + with Stream do + begin + WriteEncoding; + for I := 0 to List.Count - 1 do + begin + WriteStringEnc(List[I]); + WriteTerminatorEnc; + end; + end; + end + else + inherited WriteFrame; +end; + + +//=== { TJvID3SkipFrame } ==================================================== + +procedure TJvID3SkipFrame.ChangeToVersion(const ANewVersion: TJvID3Version); +var + LFrameID: TJvID3FrameID; +begin + case ANewVersion of + ive2_2: + if Length(FFrameIDStr) = 4 then + begin + LFrameID := ID3_StringToFrameID(FFrameIDStr); + if LFrameID in [fiErrorFrame, fiPaddingFrame] then + FFrameIDStr := '' + else + FFrameIDStr := ID3_FrameIDToString(LFrameID, 3); + end; + ive2_3, ive2_4: + if Length(FFrameIDStr) = 3 then + begin + LFrameID := ID3_StringToFrameID(FFrameIDStr); + if LFrameID in [fiErrorFrame, fiPaddingFrame] then + FFrameIDStr := '' + else + FFrameIDStr := ID3_FrameIDToString(LFrameID, 3); + end; + end; +end; + + +//=== { TJvID3Stream } ======================================================= + +procedure TJvID3Stream.BeginReadFrame(const AFrameSize: Integer); +begin + if FReadingFrame or FWritingFrame then + ID3Error(RsEAlreadyReadingWritingFrame); + + FStartPosition := Position; + FCurrentFrameSize := AFrameSize; + FReadingFrame := True; +end; + +procedure TJvID3Stream.BeginWriteFrame(const AFrameSize: Integer); +begin + if FReadingFrame or FWritingFrame then + ID3Error(RsEAlreadyReadingWritingFrame); + + //if not Assigned(Memory) then + // { $0A = 10, the size of the header } + // Capacity := $0A; + + FStartPosition := Position; + FCurrentFrameSize := AFrameSize; + FWritingFrame := True; +end; + +function TJvID3Stream.CanRead(const ACount: Cardinal): Boolean; +var + LBytesToRead: Longint; +begin + Assert(not FWritingFrame, RsECannotCallCanRead); + + if FReadingFrame then + LBytesToRead := BytesTillEndOfFrame + else + LBytesToRead := BytesTillEndOfTag; + + Result := (LBytesToRead >= 0) and (ACount <= Cardinal(LBytesToRead)); +end; + +procedure TJvID3Stream.EndReadFrame; +begin + if not FReadingFrame then + ID3Error(RsENotReadingFrame); + MoveToNextFrame; + FReadingFrame := False; +end; + +procedure TJvID3Stream.EndWriteFrame; +begin + if not FWritingFrame then + ID3Error(RsENotWritingFrame); + MoveToNextFrame; + FWritingFrame := False; +end; + +function TJvID3Stream.GetBytesTillEndOfFrame: Longint; +begin + Result := 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 <text string according to encoding> + } + Result := 1 + 3 + LengthEnc(Text, ToEncoding); +end; + +function TJvID3TermsOfUseFrame.GetIsEmpty: Boolean; +begin + Result := (Text = '') and (Length(FLanguage) = 0); +end; + +function TJvID3TermsOfUseFrame.MustWriteAsUTF: Boolean; +begin + Result := HasNonISO_8859_1Chars(Text); +end; + +procedure TJvID3TermsOfUseFrame.ReadFrame; +begin + { Text encoding $xx + Language $xx xx xx + The actual text <text string according to encoding> + } + with Stream do + begin + ReadEncoding; + ReadLanguage(FLanguage); + ReadStringEnc(FText); + end; +end; + +function TJvID3TermsOfUseFrame.SameUniqueIDAs(const Frame: TJvID3Frame): Boolean; +begin + { There may only be one 'USER' frame in a tag} + Result := (Assigned(Frame) and (Frame.FrameID = FrameID) and (FrameID = fiTermsOfUse)) or + inherited SameUniqueIDAs(Frame); +end; + +procedure TJvID3TermsOfUseFrame.SetLanguage(const Value: AnsiString); +begin + if FLanguage <> Value then + begin + FLanguage := Value; + Changed; + end; +end; + +procedure TJvID3TermsOfUseFrame.SetText(const Value: String); +//procedure TJvID3TermsOfUseFrame.SetText(const Value: WideString); +begin + if Value <> FText then + begin + FText := Value; + Changed; + end; +end; + +function TJvID3TermsOfUseFrame.SupportsVersion(const AVersion: TJvID3Version): Boolean; +begin + case FrameID of + { ** Not supported in 2.2 ** } + + fiTermsOfUse: + Result := AVersion in [ive2_3, ive2_4]; + else + Result := True; + end; +end; + +procedure TJvID3TermsOfUseFrame.WriteFrame; +begin + { Text encoding $xx + Language $xx xx xx + The actual text <text string according to encoding> + } + with Stream do + begin + WriteEncoding; + WriteLanguage(Language); + WriteStringEnc(Text); + end; +end; + + +//=== { TJvID3TextFrame } ==================================================== + +procedure TJvID3TextFrame.ChangeToVersion(const ANewVersion: TJvID3Version); +var + Year: Word; + LDate: TDateTime; + Frame: TJvID3Frame; +begin + if ANewVersion <> ive2_4 then + Exit; + + { Change + + fiYear, fiDate, fiTime, fiRecordingDates frames into 1 fiRecordingTime frame } + + if FrameID in [fiDate, fiTime] then + begin + if Assigned(FFrames.FindFrame(fiRecordingTime)) then + Exit; + + { 1. Determine the year from a fiYear frame} + Frame := TJvID3NumberFrame.Find(FController, fiYear); + if Assigned(Frame) then + Year := TJvID3NumberFrame(Frame).Value + else + { hm, no year frame , just assume it's current year } + Year := YearOf(Date); + + { 2. Determine month + day from a fiDate frame } + Frame := TJvID3TextFrame.Find(FController, fiDate); + if Assigned(Frame) then + with TJvID3TextFrame(Frame) do + LDate := GetID3Date(Text, Encoding, Year) + else + try + { hm, no date frame , just assume it's 1 jan } + LDate := EncodeDate(Year, 1, 1); + except + on EConvertError do + LDate := 0; + end; + + { 3. Determine hour + min from a fiTime frame} + Frame := TJvID3TextFrame.Find(FController, fiTime); + if Assigned(Frame) then + with TJvID3TextFrame(Frame) do + LDate := LDate + GetID3Time(Text, Encoding); + + { 4. Copy constructed date to a fiRecordingTime frame } + TJvID3TimestampFrame.FindOrCreate(FController, fiRecordingTime).Value := LDate; + end; +end; + +function TJvID3TextFrame.CheckFrame(const HandleError: TJvID3HandleError): Boolean; +begin + case FrameID of + fiTime: + Result := CheckIsID3Time(Self, FText, HandleError); + fiDate: + Result := CheckIsID3Date(Self, FText, HandleError); + fiPartInSet: + Result := CheckIsID3PartInSet(Self, FText, HandleError); + fiTrackNum: + Result := CheckIsID3PartInSet(Self, FText, HandleError); + else + Result := True; + end; + + { If something has changed update the framesize } + if not Result and (HandleError = heAutoCorrect) then + begin + UpdateFrameSize; + Result := True; + end; +end; + +class function TJvID3TextFrame.Find(AController: TJvID3Controller; + const AFrameID: TJvID3FrameID): TJvID3TextFrame; +var + Frame: TJvID3Frame; +begin + Result := nil; + if not Assigned(AController) or not AController.Active then + Exit; + + Frame := AController.Frames.FindFrame(AFrameID); + if Frame is TJvID3TextFrame then + Result := TJvID3TextFrame(Frame); +end; + +class function TJvID3TextFrame.FindOrCreate(AController: TJvID3Controller; + const AFrameID: TJvID3FrameID): TJvID3TextFrame; +begin + if not Assigned(AController) then + ID3Error(RsEID3NoController); + + Result := Find(AController, AFrameID); + if not Assigned(Result) then + begin + AController.CheckFrameClass(TJvID3TextFrame, AFrameID); + Result := TJvID3TextFrame(AController.AddFrame(AFrameID)); + end; +end; + +function TJvID3TextFrame.Gettext: String; +//function TJvID3TextFrame.GetText: WideString; +begin + Result := FText; +end; + +//procedure TJvID3TextFrame.SetText(const ANewText: WideString); +procedure TJvID3TextFrame.SetText(const ANewText: String); +begin + if ANewText <> FText then + begin + FText := ANewText; + Changed; + end; +end; + + +//=== { TJvID3TimestampFrame } =============================================== + +procedure TJvID3TimestampFrame.ChangeToVersion(const ANewVersion: TJvID3Version); +var + Year, Month, Day: Word; + Hour, Min: Word; + Dummy1, Dummy2: Word; +begin + { Change + + * fiRecordingTime into fiYear, fiDate, fiTime, fiRecordingDates + * fiOrigReleaseTime into fiOrigYear } + + if IsEmpty or not (ANewVersion in [ive2_2, ive2_3]) then + Exit; + + if FrameID = fiRecordingTime then + begin + { Check if frames don't exists already } + if [fiYear, fiDate, fiTime] * FFrames.GetFrameIDs = [] then + begin + { 1. Determine the Year, Month, Day, Hour and Min from this frame } + DecodeTime(Value, Hour, Min, Dummy1, Dummy2); + DecodeDate(Value, Year, Month, Day); + + { 2. Create a new fiYear frame for the Year } + TJvID3NumberFrame.FindOrCreate(FController, fiYear).Value := Year; + + { 3. Create a new fiDate frame [format = 'DDMM'] for the Day and Month } + TJvID3TextFrame.FindOrCreate(FController, fiDate).Text := + Format('%.2d%.2d', [Day, Month]); + + { 4. Create a new fiTime frame [format = 'HHMM'] for the Hour and Min } + TJvID3TextFrame.FindOrCreate(FController, fiTime).Text := + Format('%.2d%.2d', [Hour, Min]); + end; + end + else + if FrameID = fiOrigReleaseTime then + begin + { Check if frames don't exists already } + if not (fiOrigYear in FFrames.GetFrameIDs) then + begin + DecodeDate(Value, Year, Dummy1, Dummy2); + + { We can only store the year in a fiOrigYear frame, ie no other frames + are supported in v2.3 } + TJvID3NumberFrame.FindOrCreate(FController, fiOrigYear).Value := Year; + end; + end; +end; + +function TJvID3TimestampFrame.CheckFrame(const HandleError: TJvID3HandleError): Boolean; +begin + Result := True; +end; + +class function TJvID3TimestampFrame.Find(AController: TJvID3Controller; + const AFrameID: TJvID3FrameID): TJvID3TimestampFrame; +var + Frame: TJvID3Frame; +begin + Result := nil; + if not Assigned(AController) or not AController.Active then + Exit; + + Frame := AController.Frames.FindFrame(AFrameID); + if Frame is TJvID3TimestampFrame then + Result := TJvID3TimestampFrame(Frame); +end; + +class function TJvID3TimestampFrame.FindOrCreate(AController: TJvID3Controller; + const AFrameID: TJvID3FrameID): TJvID3TimestampFrame; +begin + if not Assigned(AController) then + ID3Error(RsEID3NoController); + + Result := Find(AController, AFrameID); + if not Assigned(Result) then + begin + AController.CheckFrameClass(TJvID3TimestampFrame, AFrameID); + Result := TJvID3TimestampFrame(AController.AddFrame(AFrameID)); + end; +end; + +{ The timestamp fields are based on a subset of ISO 8601. When being as + precise as possible the format of a time string is + yyyy-MM-ddTHH:mm:ss (year, "-", month, "-", day, "T", hour (out of + 24), ":", minutes, ":", seconds), but the precision may be reduced by + removing as many time indicators as wanted. Hence valid timestamps + are + yyyy, yyyy-MM, yyyy-MM-dd, yyyy-MM-ddTHH, yyyy-MM-ddTHH:mm and + yyyy-MM-ddTHH:mm:ss. All time stamps are UTC. For durations, use + the slash character as described in 8601, and for multiple non- + contiguous dates, use multiple strings, if allowed by the frame + definition. } + +//function TJvID3TimestampFrame.GetText: WideString; +function TJvID3TimeStampFrame.GetText: String; +var + Year, Month, Day, Hour, Min, Sec, Dummy: Word; +begin + DecodeDate(Value, Year, Month, Day); + DecodeTime(Value, Hour, Min, Sec, Dummy); + if Year > 9999 then + Year := 9999; + if (Hour = 0) and (Min = 0) and (Sec = 0) then + Result := Format('%.4d-%.2d-%.2d', [Year, Month, Day]) + else + Result := Format('%.4d-%.2d-%.2dT%.2d:%.2d:%.2d', [Year, Month, Day, Hour, Min, Sec]); +end; + +//procedure TJvID3TimestampFrame.SetText(const ANewText: WideString); +procedure TJvID3TimeStampFrame.SetText(const ANewText: String); +type + TimeKind = (tkYear, tkMonth, tkDay, tkHour, tkMin, tkSec); +const + { 1234567890123456789 + Format = yyyy-MM-ddTHH:mm:ss } + SepPos: array [TimeKind] of Byte = (5, 8, 11, 14, 17, 20); +var + //S: AnsiString; + S: String; + TimeArray: array [TimeKind] of Word; + BusyWith: TimeKind; + I: Byte; +begin + { Max. 19 chars } +// S := UTF16ToAnsiString(Copy(ANewText, 1, 19)); + S := Copy(ANewText, 1, 19); + + FillChar(TimeArray, 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 <text string according to encoding> $00 (00) + Value <text string according to encoding> + } + Result := 1 + + LengthEnc(Description, ToEncoding) + + LengthTerminatorEnc(ToEncoding) + + Cardinal(Length(FURL)); +end; + +function TJvID3URLUserFrame.GetIsEmpty: Boolean; +begin + Result := (FURL = '') and (Description = ''); +end; + +function TJvID3URLUserFrame.MustWriteAsUTF: Boolean; +begin + Result := HasNonISO_8859_1Chars(Description); +end; + +procedure TJvID3URLUserFrame.ReadFrame; +begin + with Stream do + begin + ReadEncoding; + ReadStringEnc(FDescription); + ReadStringA(FURL); + end; +end; + +//procedure TJvID3URLUserFrame.SetDescription(const Value: WideString); +procedure TJvID3URLUserFrame.SetDescription(const Value: String); +begin + if Value <> FDescription then + begin + FDescription := Value; + Changed; + end; +end; + +procedure TJvID3URLUserFrame.SetURL(const Value: AnsiString); +begin + if FURL <> Value then + begin + FURL := Value; + Changed; + end; +end; + +procedure TJvID3URLUserFrame.WriteFrame; +begin + with Stream do + begin + WriteEncoding; + WriteStringEnc(Description); + WriteTerminatorEnc; + WriteStringA(URL); + end; +end; + + +//=== { TJvID3UserFrame } ==================================================== + +procedure TJvID3UserFrame.Assign(Source: TPersistent); +begin + if Source is TJvID3CustomTextFrame then + begin + FValue := TJvID3UserFrame(Source).Value; + FDescription := TJvID3UserFrame(Source).Description; + end; + inherited Assign(Source); +end; + +class function TJvID3UserFrame.CanAddFrame(AController: TJvID3Controller; + AFrameID: TJvID3FrameID): Boolean; +begin + { There may be more than one "TXXX" frame in each tag, but only one + with the same description. } + Result := (AFrameID = fiUserText) or + inherited CanAddFrame(AController, AFrameID); +end; + +function TJvID3UserFrame.CheckFrame(const HandleError: TJvID3HandleError): Boolean; +begin + Result := True; +end; + +procedure TJvID3UserFrame.Clear; +begin + FValue := ''; + FDescription := ''; + inherited Clear; +end; + +class function TJvID3UserFrame.Find(AController: TJvID3Controller; + const AIndex: Integer): TJvID3UserFrame; +var + FoundIndex: Integer; + Frame: TJvID3Frame; +begin + Result := nil; + + if not Assigned(AController) or not AController.Active then + Exit; + + if not AController.FindFirstFrame(fiUserText, Frame) then + Exit; + + FoundIndex := 0; + + while Assigned(Frame) and (FoundIndex < AIndex) do + begin + AController.FindNextFrame(fiUserText, Frame); + Inc(FoundIndex); + end; + + if Frame is TJvID3UserFrame then + Result := TJvID3UserFrame(Frame); +end; + +class function TJvID3UserFrame.FindOrCreate(AController: TJvID3Controller; + const AIndex: Integer): TJvID3UserFrame; +begin + if not Assigned(AController) then + ID3Error(RsEID3NoController); + + Result := Find(AController, AIndex); + if not Assigned(Result) then + Result := TJvID3UserFrame(AController.AddFrame(fiUserText)); +end; + +function TJvID3UserFrame.GetFrameSize(const ToEncoding: TJvID3Encoding): Cardinal; +begin + { Text encoding $xx + Description <text string according to encoding> $00 (00) + Value <text string according to encoding> } + Result := 1 + + LengthEnc(Description, ToEncoding) + + LengthTerminatorEnc(ToEncoding) + + LengthEnc(Value, ToEncoding); +end; + +function TJvID3UserFrame.GetIsEmpty: Boolean; +begin + Result := (Value = '') and (Description = ''); +end; + +function TJvID3UserFrame.MustWriteAsUTF: Boolean; +begin + Result := HasNonISO_8859_1Chars(Value) or HasNonISO_8859_1Chars(Description) +end; + +procedure TJvID3UserFrame.ReadFrame; +begin + with Stream do + begin + ReadEncoding; + ReadUserString(FDescription, FValue); + end; +end; + +//procedure TJvID3UserFrame.SetDescription(const AValue: WideString); +procedure TJvID3UserFrame.SetDescription(const AValue: String); +begin + if AValue <> FDescription then + begin + FDescription := AValue; + Changed; + end; +end; + +//procedure TJvID3UserFrame.SetValue(const AValue: WideString); +procedure TJvID3UserFrame.SetValue(const AValue: String); +begin + if AValue <> FValue then + begin + FValue := AValue; + Changed; + end; +end; + +procedure TJvID3UserFrame.WriteFrame; +begin + with Stream do + begin + WriteEncoding; + WriteUserString(Description, Value); + end; +end; + + +end. 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.