![]() |
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.... |
Re: WAV-Dateien und ID3Tag?
Ich wusste gar nicht das wav-Dateien auch ID3-Tags aufnehmen können.
|
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) |
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
![]() |
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... |
Re: WAV-Dateien und ID3Tag?
Zitat:
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 |
Re: WAV-Dateien und ID3Tag?
du kannst dir ja mal die ATL anschauen ->
![]() |
Re: WAV-Dateien und ID3Tag?
schau dir mal im wiki den entsprechenden beitrag zu RIFF WAVE an.
da ist alles haargenau erklärt. ![]() es gibt aber auch genug beispiele im netz. google mal nach "delphi twave" |
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:
Ach ja, eine ausführliche Erklärung zu den einzelnen Chunk ID's lässt sich
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; ![]() Hier noch Beispiele zur Anwendung:
Delphi-Quellcode:
Im Anhang noch der Code als Unit:
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; |
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:
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.
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; 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 16:06 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz