Recent

Author Topic: [SOLVED] Trouble reading mp3 file data  (Read 5591 times)

bzman24

  • Jr. Member
  • **
  • Posts: 71
[SOLVED] Trouble reading mp3 file data
« on: July 30, 2021, 04:26:14 am »
Hello,

This is what I am trying to do:

I have over 1000 mp3 files that need to be renamed.  I want to rename the files by using the date created as part of the filename.  I found by inspecting the mp3 files, the date is imbedded into the first 125 bytes of each file.  It is not always in the same location, but it is always found in the first 125 bytes.

I wrote a program that reads the first 125 bytes of the mp3 file(s), saves it to a temp file and then reads the temp file and extracts out the ASCII data that contains the date.  This procedure works fine except for about 50 of the mp3 files.

My question is, why doesn't it work on the 50 when it works correctly on all of the others??

I have included the Unit file code below and attached the project and sample mp3 data files.  The mp3 files are truncated to only the first 1000 bytes of each file which give me the same results as a full length mp3 file.

Thanks,

Bzman


Code: Pascal  [Select][+][-]
  1.  
  2. UNIT Unit1;
  3.  
  4. {$mode objfpc}{$H+}
  5.  
  6. INTERFACE
  7.  
  8. USES
  9.   Classes, Sysutils, Fileutil, Forms, Controls, Graphics, Dialogs, StdCtrls,
  10.   CheckLst, EditBtn, FileCtrl;
  11.  
  12. TYPE
  13.  
  14.   { Tform1 }
  15.  
  16.   Tform1 = CLASS(Tform)
  17.     Button_Extract: Tbutton;
  18.     Button_Refresh: Tbutton;
  19.     Button_Quit: Tbutton;
  20.     CheckListBox1: TCheckListBox;
  21.     DirectoryEdit1: TDirectoryEdit;
  22.     FileListBox1: TFileListBox;
  23.     Memo1: Tmemo;
  24.     PROCEDURE Button_ExtractClick(Sender: Tobject);
  25.     PROCEDURE Button_RefreshClick(Sender: Tobject);
  26.     PROCEDURE Button_QuitClick(Sender: Tobject);
  27.     PROCEDURE DirectoryEdit1Change(Sender: Tobject);
  28.     PROCEDURE FormCreate(Sender: Tobject);
  29.   Private
  30.  
  31.   Public
  32.  
  33.   END;
  34.  
  35. VAR
  36.   Form1: Tform1;
  37.  
  38. IMPLEMENTATION
  39.  
  40. {$R *.lfm}
  41.  
  42. { Tform1 }
  43.  
  44. PROCEDURE Tform1.FormCreate(Sender: Tobject);
  45. BEGIN
  46.   DirectoryEdit1.Directory := 'C:\MP3_Dat_Test';
  47.   FileListBox1.Mask := '*.mp3';
  48.   FileListBox1.Directory := DirectoryEdit1.Directory;
  49.   CheckListBox1.Items := FileListBox1.Items;
  50.   FileListBox1.Visible := False;
  51. END;
  52.  
  53. PROCEDURE Tform1.Button_QuitClick(Sender: Tobject);
  54. BEGIN
  55.   CheckListBox1.Free;
  56.   Release;
  57.   Application.Terminate;
  58. END;
  59.  
  60. PROCEDURE Tform1.DirectoryEdit1Change(Sender: Tobject);
  61. BEGIN
  62.   FileListBox1.Directory := DirectoryEdit1.Directory;
  63.   CheckListBox1.Items := FileListBox1.Items;
  64. END;
  65.  
  66. PROCEDURE Tform1.Button_RefreshClick(Sender: Tobject);
  67. BEGIN
  68.   FileListBox1.UpdateFileList;
  69.   CheckListBox1.Items := FileListBox1.Items;
  70.   Memo1.Clear;
  71. END;
  72.  
  73. PROCEDURE Tform1.Button_ExtractClick(Sender: Tobject);
  74. VAR
  75.   MyBuffer : ARRAY[0..125] OF byte;
  76.   InputFile1, OutputFile1 : FILE;
  77.   Counter : Integer;
  78.   LocalCounter : Integer;
  79.   Line, Space : STRING;
  80.   NewLine : STRING;
  81.   TempFileName1 : STRING;
  82.   InputFile2, OutputFile2 : Text;
  83. BEGIN
  84. {Go thru the list of checkbox items, find the}
  85. {one that is checked & process that file}
  86. FOR LocalCounter := 0 TO CheckListBox1.Items.Count - 1 DO
  87.   BEGIN
  88.     IF CheckListBox1.Checked[LocalCounter] = True
  89.       THEN
  90.         BEGIN
  91.           Line := '';
  92.           TempFileName1 := DirectoryEdit1.Directory + '\' + CheckListBox1.Items.Strings[LocalCounter];
  93.           AssignFile(InputFile1, TempFileName1);
  94.           AssignFile(OutputFile1, 'C:\MP3_Dat_Test\Testout.dat');
  95.           Reset(InputFile1);
  96.           Rewrite(OutputFile1);
  97.           BlockRead(InputFile1, MyBuffer, 1);
  98.           BlockWrite(OutputFile1, MyBuffer, 1);
  99.           CloseFile(InputFile1);
  100.           CloseFile(OutputFile1);
  101.           AssignFile(OutputFile2, 'C:\MP3_Dat_Test\Testout.dat');
  102.           Append(OutputFile2);
  103.           WriteLn(OutputFile2, Chr(13));
  104.           WriteLn(OutputFile2, TempFileName1);
  105.           CloseFile(OutputFile2);
  106.           AssignFile(InputFile2, 'C:\MP3_Dat_Test\Testout.dat');
  107.           Reset(InputFile2);
  108.           ReadLn(InputFile2, Line);
  109.           NewLine := '';
  110.           Space := '   ';
  111.           {Go thru 'Line' and pull out the valid ASCII bytes}
  112.           BEGIN
  113.             FOR Counter := 1 TO 126 DO
  114.               IF (Line[Counter] >= #33) AND (Line[Counter] <= #126)
  115.                 THEN NewLine := NewLine + Line[Counter];
  116.           END;
  117.           Memo1.Lines.AddStrings(NewLine);
  118.           Memo1.Lines.AddStrings(Space);
  119.           CloseFile(InputFile2);
  120.         END;
  121.   END;
  122. END;
  123.  
  124. END.                                  
  125.  
  126.  
« Last Edit: July 30, 2021, 04:42:09 pm by bzman24 »
==============
OS: Win7 - i5 - win64
Linux Mint 17.3
Lazarus: V1.8.2 / FPC - 3.0.4

speter

  • Sr. Member
  • ****
  • Posts: 337
Re: Trouble reading mp3 file data
« Reply #1 on: July 30, 2021, 06:35:02 am »
I'm not sure whether it helps, but according to Wikipedia (https://en.wikipedia.org/wiki/MP3) some MP3 files' headers include some "encapsulation"...

See https://upload.wikimedia.org/wikipedia/commons/thumb/0/01/Mp3filestructure.svg/1508px-Mp3filestructure.svg.png

cheers
S.
I climbed mighty mountains, and saw that they were actually tiny foothills. :)

bzman24

  • Jr. Member
  • **
  • Posts: 71
Re: Trouble reading mp3 file data
« Reply #2 on: July 30, 2021, 02:07:42 pm »
First - to the moderator, thanks for putting this in the correct topic area. :-[ :-[

speter, let me look into that, maybe I can find something there to answer my question.

If you look at the sample mp3 files I attached, you will see I have some noted as 'working' and others 'not working'.  Other than a few bits being different, I see no difference in them as to why one file works and the other doesn't.  Look at the files in a hex editor and you will see what I am talking about.

Thanks,

Bzman
==============
OS: Win7 - i5 - win64
Linux Mint 17.3
Lazarus: V1.8.2 / FPC - 3.0.4

Fred vS

  • Hero Member
  • *****
  • Posts: 3158
    • StrumPract is the musicians best friend
Re: Trouble reading mp3 file data
« Reply #3 on: July 30, 2021, 03:29:12 pm »
I found by inspecting the mp3 files, the date is imbedded into the first 125 bytes of each file.

Hello.

Imho, this works only for ID3v1 and the 128 bytes are placed at end of file.
For ID3v2, it is indeed placed at beginning of file but it is totally different, see
https://en.wikipedia.org/wiki/ID3

Quote
There are two unrelated versions of ID3: ID3v1 and ID3v2. ID3v1 takes the form of a 128-byte segment at the end of an MP3 file containing a fixed set of data fields. ID3v1.1 is a slight modification which adds a "track number" field at the expense of a slight shortening of the "comment" field. ID3v2 is structurally very different from ID3v1, consisting of an extensible set of "frames" located at the start of the file, each with a frame identifier (a three- or four-byte string) and one piece of data. 83 types of frames are declared in the ID3v2.4 specification, and applications can also define their own types.

Maybe you could use OvOTag, it works perfectly and will give you the "date" tag detecting if it is ID3v1 or ID3v2.
https://github.com/varianus/ovoplayer/tree/master/src/ovotag

[EDIT] Most of the case (not always) when there is ID3v2 tag, ID3v1 is added too at end of file.
So maybe you may try your solution but using the last 128 bytes of the file (not the first).

Fre;D
« Last Edit: July 30, 2021, 03:48:25 pm by Fred vS »
I use Lazarus 2.2.0 32/64 and FPC 3.2.2 32/64 on Debian 11 64 bit, Windows 10, Windows 7 32/64, Windows XP 32,  FreeBSD 64.
Widgetset: fpGUI, MSEgui, Win32, GTK2, Qt.

https://github.com/fredvs
https://gitlab.com/fredvs
https://codeberg.org/fredvs

rsz

  • New Member
  • *
  • Posts: 45
Re: Trouble reading mp3 file data
« Reply #4 on: July 30, 2021, 04:05:30 pm »
Hi,

1. if you enable debugging and build in debug mode then you get a RunError(201) which is a range check exception on the ones which don't work. You're looping over the Line variable up to index 125 regardless of how long the line that you actually read is. You're reading in some portion from memory outside of your string. See: https://wiki.freepascal.org/IDE_Window:_Compiler_Options#Adding_a_release_and_debug_build_modes

2. I'm wondering why you're doing all this writing to a temporary file at all, and also overwriting this temporary file multiple times. Is there a specific reason for doing this? Why not just read in 125 bytes directly and then use that rather than using ReadLn?

3. I also see that you're using the old procedural style for file I/O. You could read in the 125 bytes using BlockRead with it, but I'd recommend using TFileStream instead. The API for it is in my opinion a lot more readable and cleaner than the old procedural style. See: https://wiki.freepascal.org/File_Handling_In_Pascal#Object_style

If my assumptions about what you're trying to do are correct, then have a look at the following code which I believe does what you're trying to do. It should be self documenting but if it's not clear, then just ask.

Code: Pascal  [Select][+][-]
  1. PROCEDURE Tform1.Button_ExtractClick(Sender: Tobject);
  2.   function IsAscii(const B: Byte): Boolean;
  3.   begin
  4.     Result := (B >= 33) AND (B <= 126);
  5.   end;
  6.   function ExtractString(constref Buffer: array of Byte; const Len: Integer): String;
  7.   var
  8.     B: Byte;
  9.     I: Integer;
  10.   begin
  11.     Result := '';
  12.     for I := Low(Buffer) to Len-1 do
  13.     begin
  14.       B := Buffer[I];
  15.       if IsAscii(B) then Result := Result + Chr(B);
  16.     end;
  17.   end;
  18. type
  19.   TMyBuffer = array[0..124] of Byte;
  20. VAR
  21.   MyBuffer : TMyBuffer;
  22.   BytesRead: Integer;
  23.   LocalCounter : Integer;
  24.   ExtractedString: String;
  25.   InputFilePath: String;
  26.   Stream: TFileStream;
  27. BEGIN
  28. {Go thru the list of checkbox items, find the}
  29. {one that is checked & process that file}
  30. FOR LocalCounter := 0 TO CheckListBox1.Items.Count - 1 DO
  31.   BEGIN
  32.     IF CheckListBox1.Checked[LocalCounter] = True
  33.       THEN
  34.         BEGIN;
  35.           MyBuffer := Default(TMyBuffer);
  36.           InputFilePath := DirectoryEdit1.Directory + '/' + CheckListBox1.Items.Strings[LocalCounter];
  37.           Stream := TFileStream.Create(InputFilePath, fmOpenRead);
  38.           try
  39.             BytesRead := Stream.Read(MyBuffer, Length(MyBuffer));
  40.             if BytesRead = 0 then
  41.               raise Exception.Create('Error! Read 0 bytes...');
  42.             ExtractedString := ExtractString(MyBuffer, BytesRead);
  43.             Memo1.Lines.AddStrings([ExtractedString, '', '   ']);
  44.           finally
  45.             Stream.Free;
  46.           end;
  47.         END;
  48.   END;
  49. END;

I agree with Fre;D, extracting the first 125 bytes may not always work and the better solution would be to parse the mp3 according to spec.
« Last Edit: July 30, 2021, 04:13:51 pm by rsz »

wp

  • Hero Member
  • *****
  • Posts: 11830
Re: Trouble reading mp3 file data
« Reply #5 on: July 30, 2021, 04:09:09 pm »
You could also have a look a JVCLLaz: there are TJvID3v1 and TJvID3v2 components in the JvMMLaz package. If you don't want to install the entire library you only need to check the JvMMLazD package in OPM; the minimum required other packages will be installed automatically. The examples folder contains demos for JvID3v1 and JvID3v2 (I don't know if you get the examples folder upon partial installation, though).

bzman24

  • Jr. Member
  • **
  • Posts: 71
Re: Trouble reading mp3 file data
« Reply #6 on: July 30, 2021, 04:40:41 pm »
To all that replied - Thank You.

Fred - OvOTag looks promising and I can learn a lot from it.

rsz -
1 - You found the problem and answered my question.  I have never used debug mode.  In things like this, it looks like a nice friend to have because it pointed us to the real problem and solution.
2 - Old habits are hard to give up.  I have been doing it that way to extract out data since TP4.
3 - Again old habit.  It works so why change it.  Yes, I do need to be using some of the newer procedures that are provided.

Yes, your code works exactly as I want.  Thank You!  As you can see, I have a lot to learn that would actually make my projects easier to do.

wp - I'll have to check out JVCLLaz.  It probably will do exactly what I want with out going thru my antiquated method of doing things.

Thanks,

Bill
==============
OS: Win7 - i5 - win64
Linux Mint 17.3
Lazarus: V1.8.2 / FPC - 3.0.4

Fred vS

  • Hero Member
  • *****
  • Posts: 3158
    • StrumPract is the musicians best friend
Re: Trouble reading mp3 file data
« Reply #7 on: July 30, 2021, 04:59:24 pm »
Fred - OvOTag looks promising and I can learn a lot from it.

In attachment a simple console program that shows ID3v1 and ID3v2 tag.
It uses some OvOTag files (AudioTag.pas, basetag.pas, file_mp3.pas).

In the demo the mp3 is hard-coded, just change it at your desire.

Code: Pascal  [Select][+][-]
  1. ReadTag('/home/fred/Music/mp3/Good juice/04 the beast.mp3');

Fre;D
I use Lazarus 2.2.0 32/64 and FPC 3.2.2 32/64 on Debian 11 64 bit, Windows 10, Windows 7 32/64, Windows XP 32,  FreeBSD 64.
Widgetset: fpGUI, MSEgui, Win32, GTK2, Qt.

https://github.com/fredvs
https://gitlab.com/fredvs
https://codeberg.org/fredvs

BobDog

  • Sr. Member
  • ****
  • Posts: 394
Re: [SOLVED] Trouble reading mp3 file data
« Reply #8 on: July 30, 2021, 08:03:11 pm »

If you are using windows then this primitive method might work.
The date/time here on dir is in the form
19/09/2020  14:47
i.e.
(19th September 2020 at 14 47)
So this code is based on that format.
Code: Pascal  [Select][+][-]
  1.  
  2.    program testfile ;
  3.  
  4.   function  system(s:pchar):integer ; cdecl external 'msvcrt.dll' name 'system';
  5.  
  6.  
  7.    function loadfile(filename:ansistring):ansistring;
  8.    var
  9.     ret:ansistring;
  10.     ans:ansistring='';
  11.     f:textfile;
  12.    begin
  13.     AssignFile(f,filename);
  14.     reset(f);
  15.     while not eof(f) do
  16.     begin
  17.       readln(f, ret);
  18.       ans:=ans+ret;
  19.     end;
  20.     CloseFile(f);
  21.     exit(ans);
  22.    end;
  23.  
  24.  
  25.    function datetime(filename:ansistring):ansistring ;
  26.    var
  27.    a,f,answer:ansistring;
  28.    i:integer;
  29.    begin
  30.    f:='';
  31.    answer:='';
  32.    a:='dir '+ filename + '>tempdirfile.txt';
  33.     system(pchar(a));
  34.     f:=loadfile('tempdirfile.txt');
  35.     //writeln(f);
  36.      for i:=1 to length(f) do
  37.      begin
  38.      if f[i]='/' then
  39.      begin
  40.      answer:= f[i-2 .. i+18];
  41.      break;
  42.      end;
  43.      end;
  44.   system('erase  tempdirfile.txt');
  45.   exit(answer);
  46. end;
  47.  
  48.  //  =======================  tester ============================== //
  49.      type aos=array of string;
  50.  
  51.    procedure loadfiletoarray(filename:ansistring;var a:aos);
  52.    var
  53.     ret:ansistring;
  54.     f:textfile;
  55.     counter:integer;
  56.    begin
  57.    counter:=0;
  58.     AssignFile(f,filename);
  59.     reset(f);
  60.     while not eof(f) do
  61.     begin
  62.     counter:=counter+1;
  63.       readln(f, ret);
  64.     end;
  65.     setlength(a,counter);
  66.     reset(f);
  67.     counter:=0;
  68.      while not eof(f) do
  69.     begin
  70.     counter:=counter+1;
  71.       readln(f, a[counter]);
  72.     end;
  73.     CloseFile(f);
  74.    end;
  75.  
  76.    var A:aos;
  77.    var i:integer;
  78.  
  79.     begin
  80.     system('dir /b > verytempfile.txt');
  81.  
  82.      loadfiletoarray('verytempfile.txt',A);
  83.  
  84.      for i:=1 to high(A)-1 do
  85.    begin
  86.  
  87.     if (pos('.',A[i])<>0) then    writeln(A[i]:40,'  ',datetime(A[i]));
  88.  
  89.    end;
  90.        system('erase verytempfile.txt');
  91.    writeln;
  92.    writeln('last test');
  93.  writeln(datetime('C:\Windows\notepad.exe'));
  94.  writeln(datetime('C:\Windows\system32\dxdiag.exe'));
  95.  
  96.   readln;
  97.  end.
  98.  
  99.  

You can uncomment writeln(f); if your format is different to see what you need.
Remember to pop the executable into a folder of your choice to see the contents and date/time.


 

TinyPortal © 2005-2018