Search code examples
delphiaudiomp3delphi-7id3

Remove or edit ID3Tag version 2 from MP3 file using Delphi 7


I'm using both old good MPGTools and own, simple method of setting ID3 Tag in my MP3 files. But both approaches are too old to support ID3Tag version 2. I'm looking for any solution that would allow my application, written in Delphi 7, to either remove ID3Tag from each file it process or to set it to exactly the same values as ID3Tag version 1 is set.

Currently I'm removing ID3Tagv2 manually, using quick keyboard combination in Winamp.

I don't use v2 or album art or all these "new" addition, so the quickiest way to get rid of ID3Tagv2 (if it exists in particular file) would be all I need.

Of course I've tried to search the Internet using Google, but either I've got bad day or I'm asking wrong question, because all the results I'm getting on above mentioned questions are fake result from search engine stealers like Software Informer etc.


Solution

  • As it happens, one of my projects sitting here that is awaiting completion (about 80%, I'm more a hobbyist when it comes to Delphi and had more pressing stuff come up, then I found a program I was able to download which fit my requirements precisely) is a full ID3 tag editor for MP3 files. While v1 was super-easy, v2 is much harder. You can refer to the standard document for v2.3 here. But I will confine myself to the points addressed here.

    You might want ID3v2 tags depending on the application. My portable MP3 player only accepts v2 tags, which is what pushed me to do the project in the first place.

    ID3v2 tags are written at the beginning of files in a variable length manner with variable numbers of tags which may or may not be present. Fortunately, the full length of the data should be in the first record if it's an ID3v2 tagged file. Hence, read the file locate the length of the ID3v2 data, then rewrite the file without the ID3v2 data and the tags are removed. Having the data at the beginning makes this necessary and is indeed a frustration. Anything I do in the future to the code would involve trying to change data in place. Some very dirty code follows, which AFAIR worked, but you will need to clean up if you use (I'm sure some here will be content to point out exactly how I should). But test it well just to be sure. Also be sure to ask if I missed anything from the unit I copied this out of (it's a 19.3KB pas file) that you would need:

    type
      sarray = array[0..3] of byte;
      psarray = ^sarray;
    
      ID3v2Header = packed record
         identifier: array[0..2] of char;
         major_version: byte;
         minor_version: byte;
         flags: byte;
         size: DWord;
      end;
    
     function size_decodeh(insize: DWord): DWord;
       { decodes the size headers only, which does not use bit 7 in each byte,
         requires MSB conversion as well }
       var
         outdval: DWord;
         outd, ind: psarray;
         tnext2, pnext2: byte;
    
       begin
         outdval := 0;
         outd := @outdval;
         ind := @insize;
         tnext2 := ind^[2] shr 1;
         pnext2 := ind^[1] shr 2;
    
         outd^[0] := ind^[3] or ((ind^[2] and $01) shl 7);
         outd^[1] := tnext2 or ((ind^[1] and $03) shl 6);
         outd^[2] := pnext2 or ((ind^[0] and $07) shl 5);
         outd^[3] := ind^[0] shr 3;
         Result := outdval;
       end;
    
    procedure ID3v2_LoadData(filename: string; var memrec: pointer;
                           var outsize: integer);
      { procedure loads ID3v2 data from "filename".  Returns outsize = 0 if
        there is no ID3v2 data }
    
    var
      infile: file;
      v1h: ID3V2Header;
    begin
      assign(infile, filename);
      reset(infile, 1);
    // read main header to get id3v2 size
      blockread(infile, v1h, sizeof(v1h));
    // detect if there is id3v2 data
      if v1h.identifier = 'ID3' then
        begin
          outsize := size_decodeh(v1h.size);
          // read ID3v2 header data
          getmem(memrec, outsize);
          blockread(infile, memrec^, outsize);
          Close(infile);
        end
      else
        outsize := 0;
      end;
    
     function id3v2_erase(infilestr: string): boolean;
      { erase all ID3v2 data.  Data are stored at the beginning of file, so file
        must be rewritten }
       const
         tempfilename = 'TMp@!0X.MP3';
       var
         memrec: pointer;
         outsize, dataread: integer;
         IsID3v2: boolean;
         databuffer: array[1..32768] of byte;
         newfile, origfile: file;
       begin
      // reuse service routine to get information
         Id3v2_loaddata(infilestr, memrec, outsize);
      // is there ID3v2 data?
         if outsize > 0 then
           begin
            // need to clean up after the service routine
             freemem(memrec);
            // get amount of data to erase
            outsize := outsize + sizeof(Id3v2Header);
            writeln('Data to delete is: ', outsize, ' bytes.');
            // now rewrite the file
            AssignFile(origfile, infilestr);
            reset(origfile, 1);
            AssignFile(newfile, tempfilename);
            rewrite(newfile, 1);
            Seek(origfile, outsize);
            repeat
              blockread(origfile, databuffer, sizeof(databuffer), dataread);
              blockwrite(newfile, databuffer, dataread);
            until dataread = 0;
            CloseFile(origfile);
            CloseFile(newfile);
           // rename temp file and delete original
            DeleteFile(infilestr);
            RenameFile(tempfilename, infilestr);
            IsID3v2 := true;
          end
      else
         IsID3v2 := false;
      Result := IsID3v2;
    end;
    

    Full editing capability that works in most all situations is obviously a tougher hill to climb than that, but all the details are there in that document I linked to. Hopefully this helps you out.