Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Multimedia (https://www.delphipraxis.net/16-multimedia/)
-   -   Delphi WAV-Dateien und ID3Tag? (https://www.delphipraxis.net/97500-wav-dateien-und-id3tag.html)

Martin K 11. Aug 2007 16:37


WAV-Dateien und ID3Tag?
 
Hallo,
weiß jemand wie man ID3Tags aus wav-Dateien auslesen bzw. sie ändern kann?
Für MP3s hab ich entsprechende Units gefunden, aber das funzt leider nicht mit Wave-Dateien....

Andreas L. 11. Aug 2007 16:41

Re: WAV-Dateien und ID3Tag?
 
Ich wusste gar nicht das wav-Dateien auch ID3-Tags aufnehmen können.

Martin K 11. Aug 2007 16:45

Re: WAV-Dateien und ID3Tag?
 
Liste der Anhänge anzeigen (Anzahl: 1)
Ja, das wusste ich auch nicht, bis ich es selber gesehen habe.
Hier mal ein Beispiel als Anhang:
(WMP zeigt die Tags manchmal an, QuickTime zeigt sie immer an, Winamp nicht getestet)

Gausi 11. Aug 2007 16:55

Re: WAV-Dateien und ID3Tag?
 
In diesem File sind keine ID3Tags enthalten. Was dabei der eine oder andere Player manchmal anzeigt, sind wohl irgendwelche anderen Tags, die sich am Ende der Datei verstecken (im Hexeditor kann man das erkennen). Vielleicht hilft dir das hier weiter.

Martin K 11. Aug 2007 17:07

Re: WAV-Dateien und ID3Tag?
 
Ok, danke erstmal.
Dachte, das wären auch ID3-Tags.
Nur hab ich jetzt keine Ahnung, wie ich diesen "INFO List Chunk" auslesen kann oder viel wichtiger, ihn zu ändern bzw. komplett zu entfernen...

Der_Unwissende 11. Aug 2007 17:42

Re: WAV-Dateien und ID3Tag?
 
Zitat:

Zitat von Martin K
Ok, danke erstmal.
Dachte, das wären auch ID3-Tags.
Nur hab ich jetzt keine Ahnung, wie ich diesen "INFO List Chunk" auslesen kann oder viel wichtiger, ihn zu ändern bzw. komplett zu entfernen...

Hi,
einfach mal nach der Spezifiktion googlen bzw. dem Link folgen, der genannt wurde. Da findest Du die einzelne Elemente. Ohne mich selbst detailiert mit der richtigen Spezifikation beschäftigt zu haben, Grob haben sie wohl den Aufbau, dass sie durch ein "INFO" eingeleitet werden. Dem folgen Einträge in der Form <ID><Länge><Wert>, wobei ID eben die Art des Eintrags identifiziert, Länge ist ein 32 Bit Wert (Cardinal) der angibt, wie lang der zugehörige Eintrag ist, Wert enthält dann den String/Wert. Die Strings werden dabei (musst Du schauen ob in der Länge enthalten oder nicht) mit #0#0 beendet, dem folgt dann der nächste String.
An sich musst Du also nur schauen, ob es im unteren Teil der Wave-Datei eine INFO-Struktur gibt und Dir dann merken, wo der letzte Eintrag kommt, diesen Teil kannst Du löschen (z.B. durch einen Stream, aus dem Du alle Byte vor dieser Struktur und alle hinter der Struktur in einen neuen Stream kopierst). In diesem Fall endet die Datei sogar mit dieser Struktur, hier solltest Du aber lieber auf Nummer sicher gehen und Dich nicht darauf verlassen.
Um die Wave-Datei nicht unnötig zu durchsuchen solltest Du vielleicht schauen, ob Du der Wave-Spezifikation entnehmen kannst, wo der normale Wave-Teil endet und eben optionale Informationen beginnen.

Gruß Der Unwissende

dominikkv 11. Aug 2007 17:56

Re: WAV-Dateien und ID3Tag?
 
du kannst dir ja mal die ATL anschauen -> klick

semo 11. Aug 2007 20:33

Re: WAV-Dateien und ID3Tag?
 
schau dir mal im wiki den entsprechenden beitrag zu RIFF WAVE an.
da ist alles haargenau erklärt.
http://de.wikipedia.org/wiki/RIFF_WAVE

es gibt aber auch genug beispiele im netz.
google mal nach "delphi twave"

Martin K 13. Aug 2007 00:22

Re: WAV-Dateien und ID3Tag?
 
Liste der Anhänge anzeigen (Anzahl: 1)
Erstmal vielen Dank an alle, die hier noch geantwortet haben, dachte nicht, dass ich noch so viel Hilfe bekomme :thumb:
Auf Basis des Beitrags von Der_Unwissende hab ich mir mal was zurecht gebastelt, wäre nett wenn ihr mal drüber gucken könntet, vielleicht fällt euch noch was auf, was man anders/besser machen könnte, aber funktionieren tut es perfekt!
Um nicht die ganze Datei Byte für Byte nach einem "INFO" zu durchsuchen, hab ich einfach mal beim Dateiende minus einer Konstanten angefangen zu suchen, die Konstante erhält die in etwa maximale Größe des INFO List Chunks:
Delphi-Quellcode:
const
  MAX_LISTCHUNK_SIZE = 100000;


{ INFO List Chunk von einer Datei einlesen: die ChunkID und die einsprechende Beschreibung dazu werden in übergebenen TStrings gespeichert }

function ReadINFOListChunk(const FileName: string; var ChunkID, Description: TStrings): Boolean;
var Stream: TStream; i: Integer; s: array[0..3] of Char; sl: array[0..255] of Char;
begin
  s := #0#0#0#0;
  Result := False;
  Stream := TFileStream.Create(FileName, fmOpenRead, fmShareDenyWrite);
  try
    Stream.Seek(-MAX_LISTCHUNK_SIZE, soFromEnd);
    // bis zu 'INFO' einlesen
    while Stream.Read(s, 1) = 1 do
      if s[0] = 'I' then
      begin
        if Stream.Read(s, 3) = 3 then
        begin
          if (s = 'NFO') then Break
          else Stream.Seek(-3, soFromCurrent);
        end
        else Exit;
      end;
    if s <> 'NFO' then Exit;
    // INFO Chunk einlesen
    Result := True;
    while Stream.Read(s, 4) = 4 do
    begin
      ChunkID.Append(s); // Chunk ID
      if Stream.Read(i, 4) = 4 then
      begin
        if Stream.Read(sl, i) = i then
          Description.Append(sl) // Description
        else Exit;
      end
      else Exit;
      // Null-Bytes überspringen
      while (Stream.Read(s, 1) = 1) and (s[0] = #0) do
      begin end;
      Stream.Seek(-1, soFromCurrent);
    end;
  finally
    FreeAndNil(Stream);
  end;
end;


{ INFO List Chunk in eine Datei schreiben: die ChunkID und die einsprechende Beschreibung werden in TStrings übergebenen }

procedure WriteINFOListChunk(const FileName: string; const ChunkID, Description: TStrings);
var Stream: TStream; i, j: Integer;
begin
  // INFO List Chunk löschen, falls vorhanden
  RemoveINFOListChunk(FileName);
  Stream := TFileStream.Create(FileName, fmOpenReadWrite, fmShareDenyWrite);
  try
    Stream.Seek(0, soFromEnd);
    // Header schreiben
    Stream.Write('LIST:'+#0#0#0+'INFO', 12);
    for i := 0 to ChunkID.Count - 1 do
    begin
      Stream.Write(PChar(ChunkID[i])^, 4); // Chunk ID
      j := Length(Description[i]) + 1;
      Stream.Write(j, 4); // Länge
      Stream.Write(PChar(Description[i] + #0)^, j); // Description
    end;
  finally
    FreeAndNil(Stream);
  end;
end;


{ INFO List Chunk von einer Datei löschen }

function RemoveINFOListChunk(const FileName: string): Boolean;
var Stream: TStream; s: array[0..3] of Char;
begin
  s := #0#0#0#0;
  Result := False;
  Stream := TFileStream.Create(FileName, fmOpenReadWrite, fmShareDenyWrite);
  try
    Stream.Seek(-MAX_LISTCHUNK_SIZE, soFromEnd);
    // bis zu 'INFO' einlesen
    while Stream.Read(s, 1) = 1 do
      if s[0] = 'I' then
      begin
        if Stream.Read(s, 3) = 3 then
        begin
          if (s = 'NFO') then Break
          else Stream.Seek(-3, soFromCurrent);
        end
        else Exit;
      end;
    if s <> 'NFO' then Exit;
    Stream.Seek(-12, soFromCurrent);
    // List Chunk enthält nur INFO -> kompletten List Chunk löschen
    if (Stream.Read(s, 4) = 4) and (s = 'LIST') then
      Stream.Size := Stream.Position - 4
    else
      Stream.Size := Stream.Position + 4;
    Result := True;
  finally
    FreeAndNil(Stream);
  end;
end;
Ach ja, eine ausführliche Erklärung zu den einzelnen Chunk ID's lässt sich hier finden, unter dem Absatz "INFO List Chunk".

Hier noch Beispiele zur Anwendung:
Delphi-Quellcode:
procedure TForm1.ButtonReadClick(Sender: TObject);
var ChunkID, Description: TStrings; i: Integer; s: String;
begin
  ChunkID := TStringList.Create;
  Description := TStringList.Create;
  try
    if ReadINFOListChunk('C:\test.wav', ChunkID, Description) then
    begin
      s := '';
      for i := 0 to ChunkID.Count - 1 do
        s := s + ChunkID[i] +#9+ Description[i] +#13#10;
      ShowMessage('Datei "C:\test.wav" enthält folgenden INFO List Chunk:' +#13#10#13#10+ s);
    end
    else
      ShowMessage('Datei "C:\test.wav" enthält keinen INFO List Chunk.');
  finally
    ChunkID.Free;
    Description.Free;
  end;
end;

procedure TForm1.ButtonWriteClick(Sender: TObject);
var ChunkID, Description: TStrings;
begin
  ChunkID := TStringList.Create;
  Description := TStringList.Create;
  try
    ChunkID.Add('IART');
    Description.Add('Interpret');
    ChunkID.Add('INAM');
    Description.Add('Titel');
    WriteINFOListChunk('C:\test.wav', ChunkID, Description);
  finally
    ChunkID.Free;
    Description.Free;
  end;
end;

procedure TForm1.ButtonRemoveClick(Sender: TObject);
begin
  if RemoveINFOListChunk('C:\test.wav') then
    ShowMessage('INFO List Chunk wurde gelöscht.')
  else
    ShowMessage('Kein INFO List Chunk vorhanden.');
end;
Im Anhang noch der Code als Unit:

Der_Unwissende 13. Aug 2007 11:06

Re: WAV-Dateien und ID3Tag?
 
Hi,
auf den ersten Blick fällt mir auf, dass Du das Einlesen/Suchen nach dem Chunk nocht optimieren könntest. So kommt die Datei, die Du lesen möchtest ja von der Festplatte. So eine Festplatte ist etwas anders aufgebaut als der RAM, man hat nicht ganz so schön einen Wahlfreien Zugriff. Natürlich kannst Du ein beliebiges Datum lesen, aber dazu muss es erst auf der Platte gesucht werden. Insbesondere gehört dazu die Platzierung des Lesekopfes (besser gesagt der Leseköpfe) auf der richtigen Spur und dann muss die Platte auch noch warten, bis das richtige Datum unter dem Kopf ist. Einmal gefunden kann diese dann aber sehr sehr sehr schnell viele Daten lesen und als großen Strom weiterreichen. Es gibt aber an sich auch eine untere Grenze, die Blockgröße des Systems.
Worauf ich eigentlich hinaus möchte, nach dem 'I' byteweise zu suchen ist total ineffizient! Zwar greifen hier schon einige Caching-Mechanismen des OS, es wird eben ohnehin nicht nur ein Byte ausgelesen, aber etwas besser geht es, wenn Du hier einen Lesepuffer verwendest. Wenn Du von 100.000 Byte max. Größe ausgehst, könntest Du diese eigentlich schon komplett in den Speicher laden (sind knappe 98 KByte, die hat man ja doch noch frei). Der Vorteil liegt einfach darin, dass der ganze Overhead (ggf. Zugriff auf den Hintergrundspeicher, auslesen aus dem Stream, Stream.Position aktualisieren, ...) nun auf die 98 KByte aufgeteilt wird. Der Unterschied ist überraschend deutlich und sollte selbst bei Deiner kurzen Suchen (in nur 100.000 max. Schritten) spürbar oder zumindestens messbar sein.
An sich kannst Du aber auch die Suche nach dem 'Info' noch weiter vereinfachen. Das was Du aus dem Stream lädst sind erstmal nur rohe Bytes, die eben interpretiert werden. Ein String funktioniert auf die gleiche Art und Weise. Was Du also machen kannst ist einfach die Daten in einen Stream kopieren. Wichtig dabei, Du musst vorher die Länge des Strings setzen!

Grob geht das wie folgt:
Delphi-Quellcode:
function getInfoChunkPos(const buffer: TByteDynArray): Integer;
var s: String;
    index: Integer;
    count: Integer;
begin
  result := -1;
  index := 0;
 
  // über den ganzen Puffer laufen
  while (index < length(buffer)) and (result < 0) do
  begin
    // max. Länge durch 1024 oder noch nicht gelesene Daten im Puffer beschränken
    count := min(1024, length(buffer) - index);
    // Größe des String setzen
    setLength(s, count);
    // Daten aus dem Puffer in den String kopieren
    move(buffer[index], s[1], count);
    // neuen Startindex im Puffer setzen
    inc(index, count);

    // mit dem String arbeiten
    result := pos('Info', s) - 1;
  end;
end;
Ist jetzt ein einfaches Beispiel, bei dem Du ein beliebiges dyn. Array von Byte übergibst (z.B. aus einer Wave-Datei). In einer Schleife werden immer in 1024-Byte-Happen die Daten aus dem Puffer in einen String kopiert (optimieren kannst Du das natürlich, wenn Du sicherstellst, dass der Puffer ein vielfaches von 1024 ist!). Auf den String wird dann die Pos-Funktion angewendet, die Dir den Index des ersten Vorkommens des Substrings 'Info'. Das zusätzliche -1 bei der letzten Zuweisung kommt daher, dass ein String immer 1 indiziert ist, das erste Element in einem Stream/dyn. Array/... aber immer die 0 bekommt. Somit wäre die Position hier um 1 versetzt.
Ob 1024 hier eine gute Größe ist, darüber lässt sich natürlich streiten. Zudem solltest Du natürlich beachten, dass man den String in zwei Teilen aus dem Puffer holen kann, also ein String mit 'Inf' endet und das erste Zeichen des nächsten Strings dann eben mit 'o' beginnt. Um das zu vermeiden sollte man bessert inc(index, count - 4) verwenden, das sollte sicherstellen, dass das Ende des Vorgängerstrings noch mit berücksichtigt wird.

Ansonsten sieht der Code doch ganz ordentlich aus, ein paar Kleinigkeiten gibt es natürlich dennoch (nichts Wildes!). Zum Beispiel kannst Du bei lokalen Variablen wie dem Stream auf das FreeAndNil verzichten. Wird eine Referenz nach ihrer Freigabe eh nicht mehr erreicht, reicht auch ein Free. Es schadet aber auch nichts zusätzlich die Referenz auf NIL zu setzen. Auch der Typ der Variable Stream kann direkt als TFileStream gesetzt werden. Stream verwendet man eigentlich immer dort, wo es einem völlig egal ist, was für ein Stream übergeben wird. macht auch hier nichts, Du könntest eben nur nicht auf Besonderheiten eines TFileStream (ohne Cast) zugreifen.
Die letzte Kleinigkeit, die noch am ehesten berücksichtigt werden sollte ist die von Dir verwendete Sperre, wenn Du die Dateien veränderst. Beim Lesen ist das fmShareDenyWrite natürlich sinnvoll, da Du so beliebig vielen Prozessen das Lesen ermöglichst. Aber wenn Du selbst die Datei schreibst, dann sollte möglichst kein anderer Prozess versuchen die Datei während dessen zu lesen. Was man sonst zu sehen bekommt kann eben doch irgendwas völlig Inkonsistentes sein. Besser wäre es, wenn Du hier gleich exklusiv (Lesen und Schreiben) sperrst (fmShareExclusive).

Gruß Der Unwissende


Alle Zeitangaben in WEZ +1. Es ist jetzt 18:01 Uhr.
Seite 1 von 2  1 2      

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz