In this project, we are opening MP3 files using the component 3delite.hu =- ID3v2 Library, and adding the MP3 files metadata to a ListView.

What I would like to accomplish.
When selecting multiple items in the listview, the edit fields will show <multi>, meaning that multiple items have been selected. If you try and edit anything and save it, this will result in the files getting the wrong data sent to them.
What I would like to ask is this.
How can you select multiple items, edit certain areas, and have it update each file individually?
Below is the code for this project.

LoadIt.pas

unit LoadIt;

interface


uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, ComCtrls, StdCtrls, ID3v1Library, ID3v2Library, UITypes, StrUtils;

type
  TForm1 = class(TForm)
    SidePanel: TPanel;
    MainPanel: TPanel;
    ListView1: TListView;
    OpenDialog1: TOpenDialog;
    strTitle: TEdit;
    strArtist: TEdit;
    Label1: TLabel;
    Label2: TLabel;
    strAlbum: TEdit;
    Label3: TLabel;
    strTrack: TEdit;
    Label5: TLabel;
    TopPanel: TPanel;
    Button8: TButton;
    Edit1: TEdit;
    Label4: TLabel;
    strPath: TEdit;
    saveButton: TButton;
    Memo1: TMemo;
    SelectedLabel: TLabel;
    Button1: TButton;
    Button2: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button8Click(Sender: TObject);
    procedure ListView1SelectItem(Sender: TObject; Item: TListItem;
      Selected: Boolean);
    procedure saveButtonClick(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  ID3v1Tag: TID3v1Tag = nil;
  ID3v2Tag: TID3v2Tag = nil;
  CurrentAPICIndex: Integer = - 1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
var
  Column: TListColumn;
begin
  TopPanel.Align := alTop;
  SidePanel.Align := alLeft;
  MainPanel.Align := alClient;
  ListView1.Align := alClient;
  ID3v1Tag := TID3v1Tag.Create;
  ID3v2Tag := TID3v2Tag.Create;

  Column := ListView1.Columns.Add;
  Column.Caption := 'Track ID';
  Column.Alignment := taLeftJustify;
  Column.Width := 60;

    Column := ListView1.Columns.Add;
  Column.Caption := 'Artist';
  Column.Alignment := taLeftJustify;
  Column.Width := 200;

    Column := ListView1.Columns.Add;
  Column.Caption := 'Album';
  Column.Alignment := taLeftJustify;
  Column.Width := 200;

  Column := ListView1.Columns.Add;
  Column.Caption := 'Title';
  Column.Alignment := taLeftJustify;
  Column.Width := 200;

   Column := ListView1.Columns.Add;
  Column.Caption := 'File ALocation';
  Column.Alignment := taLeftJustify;
  Column.Width := 0;


end;

procedure AddFileToTree(ListView: TListView; FileName: String);
var
  SR: TSearchRec;
  ListItem: TListItem;
begin
  // FileSize := 0;
  if (FindFirst(FileName, faAnyFile, SR) = 0) then
  begin
    // FileSize := SR.Size;
    // FileDate := FileDateToDateTime(SR.Time);
    ListItem := ListView.Items.Add;
    // get the MP3 File name
 // Track Number
ListItem.Caption := ID3v2Tag.GetUnicodeText('TRCK');  //0

   // get the Artist
ListItem.SubItems.Add(ID3v2Tag.GetUnicodeText('TPE1'));   // 1

   // get the Album
ListItem.SubItems.Add(ID3v2Tag.GetUnicodeText('TALB'));   // 2

   // get the Song title
ListItem.SubItems.Add(ID3v2Tag.GetUnicodeText('TIT2'));   // 3

   // get the path to the MP3 file
ListItem.SubItems.Add(FileName);    // 4

//  First we get the MP3 File name
ListItem.SubItems.Add(extractfilename(FileName));  //5
  end;
end;

// This section was coded by: Remy Lebeau
// https://stackoverflow.com/a/78172902/2031172
procedure TForm1.ListView1SelectItem(Sender: TObject; Item: TListItem;
  Selected: Boolean);
var
  ls: TListItem;
  sTrack, sArtist, sSong, sAlbum, sPath: string;

  procedure CheckForMulti(var selectedStr: string; const value: string);
  begin
    if selectedStr = '' then
      selectedStr := value
    else if selectedStr <> value then
      selectedStr := '<multi>';
  end;

begin
  ls := ListView1.Selected;
  while Assigned(ls) do
  begin
    CheckForMulti(sTrack,  ls.Caption);
    CheckForMulti(sArtist, ls.SubItems[0]);
    CheckForMulti(sSong,   ls.SubItems[1]);
    CheckForMulti(sAlbum,  ls.SubItems[2]);
    CheckForMulti(sPath,  ls.SubItems[3]);
    ls := ListView1.GetNextItem(ls, sdAll, [isSelected]);
  end;
  strTrack.Text  := sTrack;
  strArtist.Text := sArtist;
  strTitle.Text   := sSong;
  strAlbum.Text  := sAlbum;
  strPath.Text  := sPath;
end;

procedure TForm1.saveButtonClick(Sender: TObject);
var
    ErrorCode, i: Integer;
    LanguageID: TLanguageID;
    FileName : string;
    ListItem: TListItem;
    getString : string;
    LoadIt : integer;
begin

    if ID3v2Tag.MajorVersion = 2 then begin
        if MessageDlg('The tag in the file is an ID3v2.2 tag, if you click ''Yes'' it will be saved as an ID3v2.3 or ID3v2.4 tag. Do you want to save the tag?', mtConfirmation, [mbYes, mbCancel], 0) <> mrYes then begin
            Exit;
        end;
    end;


       ID3v2Tag.SetUnicodeText('TPE1', strArtist.Text);

       ID3v2Tag.SetUnicodeText('TALB', strAlbum.Text);

       ID3v2Tag.SetUnicodeText('TIT2', strTitle.Text);

       ID3v2Tag.SetUnicodeText('TRCK', strTrack.Text);

      ErrorCode := ID3v2Tag.SaveToFile(strPath.text);
   //  ErrorCode := ID3v2Tag.SaveToFile(strPath.Text{$IFDEF DEBUG}, False{$ENDIF});

    if ErrorCode <> ID3V2LIBRARY_SUCCESS then begin
        MessageDlg('Error saving ID3v2 tag, error code: ' + IntToStr(ErrorCode) + #13#10 + ID3v2TagErrorCode2String(ErrorCode), mtError, [mbOk], 0);
    end;
 end;



procedure TForm1.Button1Click(Sender: TObject);
var
  Item: TListItem;
  test: System.String;
begin
  memo1.Lines.Clear;
  Item := ListView1.Selected;
  while Item <> nil do
  begin
  // From Remy Lebeau - https://stackoverflow.com/a/14189378/2031172
  Memo1.SelStart := Memo1.GetTextLen;
  Memo1.SelLength := 0;
    //memo1.lines.Add(Item.SubItems[3]);
    // Added in this to created a comma delemited list.
    //  This should make it easier to iterate through.
    Memo1.SelText :=  Item.SubItems[3] + ',';
    Item := ListView1.GetNextItem(Item, sdAll, [isSelected]);
  end;
  // Remove the last character (comma) in the string.
  // Mike Jablonski - https://stackoverflow.com/a/29102436/2031172
  test := Memo1.Text;
  test := Copy(test,1,length(test)-1);
  Memo1.Text := test;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  Memo1.Clear;
end;

procedure TForm1.Button8Click(Sender: TObject);
var
  i: integer;
  LoadIt: integer;
begin
ListView1.Clear;
  if OpenDialog1.execute then
    for i := 0 to OpenDialog1.Files.Count - 1 do
      if SameText(ExtractFileExt(OpenDialog1.Files[i]), '.mp3') then
      begin
        // Bug fix here. Changing from the original TEdit to the OpenDialog.Files[i] fixed song titles in list.
        LoadIt := ID3v2Tag.LoadFromFile(OpenDialog1.Files[i]);
        AddFileToTree(ListView1, OpenDialog1.Files[i]);
      end;
end;

end.

LoadIt.dfm

object Form1: TForm1
  Left = 0
  Top = 0
  Caption = 'Form1'
  ClientHeight = 375
  ClientWidth = 688
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OnCreate = FormCreate
  TextHeight = 13
  object SidePanel: TPanel
    Left = -4
    Top = 128
    Width = 189
    Height = 252
    TabOrder = 0
    object Label1: TLabel
      Left = 4
      Top = 40
      Width = 38
      Height = 19
      Caption = 'Artist'
      Font.Charset = DEFAULT_CHARSET
      Font.Color = clWindowText
      Font.Height = -16
      Font.Name = 'Tahoma'
      Font.Style = []
      ParentFont = False
    end
    object Label2: TLabel
      Left = 4
      Top = 95
      Width = 31
      Height = 19
      Caption = 'Title'
      Font.Charset = DEFAULT_CHARSET
      Font.Color = clWindowText
      Font.Height = -16
      Font.Name = 'Tahoma'
      Font.Style = []
      ParentFont = False
    end
    object Label3: TLabel
      Left = 4
      Top = 68
      Width = 47
      Height = 19
      Caption = 'Album'
      Font.Charset = DEFAULT_CHARSET
      Font.Color = clWindowText
      Font.Height = -16
      Font.Name = 'Tahoma'
      Font.Style = []
      ParentFont = False
    end
    object Label5: TLabel
      Left = 4
      Top = 120
      Width = 56
      Height = 19
      Caption = 'Track #'
      Font.Charset = DEFAULT_CHARSET
      Font.Color = clWindowText
      Font.Height = -16
      Font.Name = 'Tahoma'
      Font.Style = []
      ParentFont = False
    end
    object strTitle: TEdit
      Left = 61
      Top = 93
      Width = 121
      Height = 21
      TabOrder = 0
      Text = 'Song Title'
    end
    object strArtist: TEdit
      Left = 61
      Top = 39
      Width = 121
      Height = 21
      TabOrder = 1
      Text = 'Artist Name'
    end
    object strAlbum: TEdit
      Left = 61
      Top = 66
      Width = 121
      Height = 21
      TabOrder = 2
      Text = 'Artist Name'
    end
    object strTrack: TEdit
      Left = 61
      Top = 120
      Width = 121
      Height = 21
      TabOrder = 3
      Text = 'Artist Name'
    end
    object saveButton: TButton
      Left = 21
      Top = 156
      Width = 59
      Height = 22
      Caption = 'Save...'
      Font.Charset = DEFAULT_CHARSET
      Font.Color = clWindowText
      Font.Height = -11
      Font.Name = 'Tahoma'
      Font.Style = []
      ParentFont = False
      TabOrder = 4
      OnClick = saveButtonClick
    end
  end
  object MainPanel: TPanel
    Left = 200
    Top = 130
    Width = 441
    Height = 201
    TabOrder = 1
    object ListView1: TListView
      Left = 104
      Top = 27
      Width = 200
      Height = 150
      Columns = <>
      MultiSelect = True
      RowSelect = True
      SortType = stText
      TabOrder = 0
      ViewStyle = vsReport
      OnSelectItem = ListView1SelectItem
    end
  end
  object TopPanel: TPanel
    Left = 8
    Top = 8
    Width = 649
    Height = 114
    TabOrder = 2
    object Label4: TLabel
      Left = 352
      Top = 15
      Width = 31
      Height = 19
      Caption = 'Path'
      Font.Charset = DEFAULT_CHARSET
      Font.Color = clWindowText
      Font.Height = -16
      Font.Name = 'Tahoma'
      Font.Style = []
      ParentFont = False
    end
    object SelectedLabel: TLabel
      Left = 81
      Top = 45
      Width = 89
      Height = 13
      Caption = 'All Selected Paths.'
    end
    object Button8: TButton
      Left = 9
      Top = 12
      Width = 59
      Height = 22
      Caption = 'Select...'
      Font.Charset = DEFAULT_CHARSET
      Font.Color = clWindowText
      Font.Height = -11
      Font.Name = 'Tahoma'
      Font.Style = []
      ParentFont = False
      TabOrder = 0
      OnClick = Button8Click
    end
    object Edit1: TEdit
      Left = 74
      Top = 13
      Width = 265
      Height = 21
      Font.Charset = DEFAULT_CHARSET
      Font.Color = clWindowText
      Font.Height = -11
      Font.Name = 'Tahoma'
      Font.Style = []
      ParentFont = False
      TabOrder = 1
    end
    object strPath: TEdit
      Left = 389
      Top = 12
      Width = 244
      Height = 21
      TabOrder = 2
      Text = 'strPath'
    end
    object Memo1: TMemo
      Left = 74
      Top = 64
      Width = 505
      Height = 41
      ScrollBars = ssVertical
      TabOrder = 3
    end
    object Button1: TButton
      Left = 192
      Top = 40
      Width = 91
      Height = 25
      Caption = 'Get All Selected'
      TabOrder = 4
      OnClick = Button1Click
    end
    object Button2: TButton
      Left = 308
      Top = 40
      Width = 75
      Height = 25
      Caption = 'Clear Paths'
      TabOrder = 5
      OnClick = Button2Click
    end
  end
  object OpenDialog1: TOpenDialog
    Filter = 'MP3|*.mp3'
    Options = [ofHideReadOnly, ofAllowMultiSelect, ofEnableSizing]
    Left = 598
    Top = 324
  end
end

Project1.dpr

program Project1;

uses
  Forms,
  LoadIt in 'LoadIt.pas' {Form1};

{$R *.res}

begin
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TForm1, Form1);
  Application.Run;

end.
1

There are 1 best solutions below

4
Remy Lebeau On BEST ANSWER

For what you are attempting, you need to keep track of the changes being made (ie, have a look at the TEdit.Modified property) and then loop through the selected items saving those changes to each individual file as needed.

Also, AddFileToTree() is leaking the TSearchRec resources as it is not calling FindClose(). But, since you are not actually using the TSearchRec for anything useful, you should just use FileExists() instead. Or better, just load the file unconditionally and then check whether ID3v2Tag.LoadFromFile() succeeded or failed.

Try something more like this:

unit LoadIt;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, ComCtrls, StdCtrls, ID3v1Library, ID3v2Library, UITypes, StrUtils;

type
  TForm1 = class(TForm)
    SidePanel: TPanel;
    MainPanel: TPanel;
    ListView1: TListView;
    OpenDialog1: TOpenDialog;
    strTitle: TEdit;
    strArtist: TEdit;
    Label1: TLabel;
    Label2: TLabel;
    strAlbum: TEdit;
    Label3: TLabel;
    strTrack: TEdit;
    Label5: TLabel;
    TopPanel: TPanel;
    Button8: TButton;
    Edit1: TEdit;
    Label4: TLabel;
    strPath: TEdit;
    saveButton: TButton;
    Memo1: TMemo;
    SelectedLabel: TLabel;
    Button1: TButton;
    Button2: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormDeestroy(Sender: TObject);
    procedure Button8Click(Sender: TObject);
    procedure ListView1SelectItem(Sender: TObject; Item: TListItem;
      Selected: Boolean);
    procedure saveButtonClick(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
    ID3v1Tag: TID3v1Tag;
    ID3v2Tag: TID3v2Tag;
    procedure AddFileToTree(FileName: String);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  CurrentAPICIndex: Integer = - 1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
var
  Column: TListColumn;
begin
  ID3v1Tag := TID3v1Tag.Create;
  ID3v2Tag := TID3v2Tag.Create;

  TopPanel.Align := alTop;
  SidePanel.Align := alLeft;
  MainPanel.Align := alClient;
  ListView1.Align := alClient;

  Column := ListView1.Columns.Add;
  Column.Caption := 'Track ID';
  Column.Alignment := taLeftJustify;
  Column.Width := 60;

  Column := ListView1.Columns.Add;
  Column.Caption := 'Artist';
  Column.Alignment := taLeftJustify;
  Column.Width := 200;

  Column := ListView1.Columns.Add;
  Column.Caption := 'Album';
  Column.Alignment := taLeftJustify;
  Column.Width := 200;

  Column := ListView1.Columns.Add;
  Column.Caption := 'Title';
  Column.Alignment := taLeftJustify;
  Column.Width := 200;

  Column := ListView1.Columns.Add;
  Column.Caption := 'File Allocation';
  Column.Alignment := taLeftJustify;
  Column.Width := 0;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  ID3v1Tag.Free;
  ID3v2Tag.Free;
end;

procedure TForm1.AddFileToTree(FileName: String);
var
  ErrorCode: integer;
  ListItem: TListItem;
begin
  ErrorCode := ID3v2Tag.LoadFromFile(FileName);
  if ErrorCode <> ID3V2LIBRARY_SUCCESS then begin
    MessageDlg('Error loading ID3v2 tag from file: ' + FileName + #13#10 + 'Error code: ' + IntToStr(ErrorCode) + #13#10 + ID3v2TagErrorCode2String(ErrorCode), mtError, [mbOk], 0);
    Exit;
  end;

  ListItem := ListView1.Items.Add;

  // get the MP3 File name
  // Track Number
  ListItem.Caption := ID3v2Tag.GetUnicodeText('TRCK');  //0

  // get the Artist
  ListItem.SubItems.Add(ID3v2Tag.GetUnicodeText('TPE1'));   // 1

  // get the Album
  ListItem.SubItems.Add(ID3v2Tag.GetUnicodeText('TALB'));   // 2

  // get the Song title
  ListItem.SubItems.Add(ID3v2Tag.GetUnicodeText('TIT2'));   // 3

  // get the path to the MP3 file
  ListItem.SubItems.Add(FileName);// 4

  //  First we get the MP3 File name
  ListItem.SubItems.Add(ExtractFileName(FileName));  //5
end;

procedure TForm1.ListView1SelectItem(Sender: TObject; Item: TListItem;
  Selected: Boolean);
var
  ListItem: TListItem;
  sTrack, sArtist, sSong, sAlbum, sPath: string;

  procedure CheckForMulti(var selectedStr: string; const value: string);
  begin
    if selectedStr = '' then
      selectedStr := value
    else if selectedStr <> value then
      selectedStr := '<multi>';
  end;

begin
  ListItem := ListView1.Selected;
  while ListItem <> nil do
  begin
    CheckForMulti(sTrack,  ListItem.Caption);
    CheckForMulti(sArtist, ListItem.SubItems[0]);
    CheckForMulti(sSong,   ListItem.SubItems[1]);
    CheckForMulti(sAlbum,  ListItem.SubItems[2]);
    CheckForMulti(sPath,   ListItem.SubItems[3]);
    ListItem := ListView1.GetNextItem(ListItem, sdAll, [isSelected]);
  end;

  if not strTrack.Modified then begin
    strTrack.Text      := sTrack;
    strTrack.Modified  := False;
  end;

  if not strArtist.Modified then begin
    strArtist.Text     := sArtist;
    strArtist.Modified := False;
  end;

  if not strTitle.Modified then begin
    strTitle.Text      := sSong;
    strTitle.Modified  := False;
  end;

  if not strAlbum.Modified then begin
    strAlbum.Text      := sAlbum;
    strAlbum.Modified  := False;
  end;

  strPath.Text := sPath;
end;

procedure TForm1.saveButtonClick(Sender: TObject);
var
  FileName : string;
  ListItem: TListItem;
  ErrorCode: Integer;
begin

  if (not strTrack.Modified) and
     (not strArtist.Modified) and
     (not strTitle.Modified) and
     (not strAlbum.Modified) then
  begin
    MessageDlg('No changes to save', mtInformation, [mbOk], 0);
    Exit;
  end;

  if ID3v2Tag.MajorVersion = 2 then begin
    if MessageDlg('The tag in the file is an ID3v2.2 tag, if you click ''Yes'' it will be saved as an ID3v2.3 or ID3v2.4 tag. Do you want to save the tag?', mtConfirmation, [mbYes, mbCancel], 0) <> mrYes then begin
      Exit;
    end;
  end;

  ListItem := ListView1.Selected;
  while ListItem <> nil do
  begin
    if strTrack.Modified then
      sTrack := strTrack.Text
    else
      sTrack := ListItem.Caption;

    if strArtist.Modified then
      sArtist := strArtist.Text
    else
      sArtist := ListItem.SubItems[0];

    if strTitle.Modified then
      sSong := strTitle.Text
    else
      sSong := ListItem.SubItems[1];

    if strAlbum.Modified then
      sAlbum := strAlbum.Text
    else
      sAlbum := ListItem.SubItems[2];

    ID3v2Tag.SetUnicodeText('TPE1', sArtist);
    ID3v2Tag.SetUnicodeText('TALB', sAlbum);
    ID3v2Tag.SetUnicodeText('TIT2', sSong);
    ID3v2Tag.SetUnicodeText('TRCK', sTrack);

    FileName := ListItem.SubItems[3];

    ErrorCode := ID3v2Tag.SaveToFile(FileName);
    //  ErrorCode := ID3v2Tag.SaveToFile(FileName{$IFDEF DEBUG}, False{$ENDIF});

    if ErrorCode <> ID3V2LIBRARY_SUCCESS then begin
      MessageDlg('Error saving ID3v2 tag, error code: ' + IntToStr(ErrorCode) + #13#10 + ID3v2TagErrorCode2String(ErrorCode), mtError, [mbOk], 0);
    end;

    ListItem := ListView1.GetNextItem(ListItem, sdAll, [isSelected]);
  end;

  strTrack.Modified  := False;
  strArtist.Modified := False;
  strTitle.Modified  := False;
  strAlbum.Modified  := False;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  ListItem: TListItem;
  test: String;
begin
  test := '';
  ListItem := ListView1.Selected;
  if ListItem <> nil then
  begin
    do
      test := test + ListItem.SubItems[3] + ',';
      ListItem := ListView1.GetNextItem(ListItem, sdAll, [isSelected]);
    until ListItem = nil;
    // Remove the last character (comma) in the string.
    SetLength(test, Length(test)-1);
  end;
  Memo1.Text := test;
  
  { alternatively: 

  SL := TStringList.Create;
  try
    ListItem := ListView1.Selected;
    while ListItem <> nil do
    begin
      SL.Add(ListItem.SubItems[3]);
      ListItem := ListView1.GetNextItem(ListItem, sdAll, [isSelected]);
    end;
    Memo1.Text := SL.CommaText;
  finally
    SL.Free;
  end;
  }
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  Memo1.Clear;
end;

procedure TForm1.Button8Click(Sender: TObject);
var
  i: integer;
  FileName: string;
begin
  ListView1.Clear;
  if OpenDialog1.Execute then
  begin
    for i := 0 to OpenDialog1.Files.Count - 1 do
    begin
      FileName := OpenDialog1.Files[i];
      if SameText(ExtractFileExt(FileName), '.mp3') then
        AddFileToTree(FileName);
    end;
  end;
end;

end.