![]() |
Codeprüfung/-vorschläge | Thema: MP3, ID-Infos
Hi,
ich habe just for fun mal eine Unit zum extrahieren der ID-Infos aus MP3 Dateien geschrieben und es sieht so aus, als würde diese auch gut funktionieren. Nun möchte ich gern mal das 'tausend-augen-prinzip' nutzen und euch mal bitten, dass ihr euch den Code mal anseht, ob da evtl. Fehler drin sind oder was ich noch verbessern kann. Das ganze bezieht sich bisher nur auf ID3v1 Tags, die ID3v2´s kommen später mit rein. Ich danke allen, die sich die Mühe machen, im vorraus. Der Delphicode ist die komplette .pas Datei.
Delphi-Quellcode:
Wer das ganze mal testen möchte, braucht eine Form, einen Button und einen FileDialog (bei mir aus den Jedi Sachen, kann aber auch der Standartdialog sein), 6 Labels und einen Verweis auf die obige Unit.
unit mp3idvx;
interface uses classes, SysUtils; type TID3v1 = record Songtitel: string; Kuenstler: string; Album: string; Erscheinungsjahr: string; Kommentar: string; Genre: string; end; TMP3ID3vX = class private public function getID3v1Information(filenameWithPath: string; var ID3v1Infos: TID3v1): integer; end; var clsMP3ID3vX: TMP3ID3vX; implementation function TMP3ID3vX.getID3v1Information(filenameWithPath: string; var ID3v1Infos: TID3v1): integer; var fstream: TFileStream; buffer: string; i: integer; begin // Stream öffnen fstream := TFileStream.Create(filenameWithPath,fmOpenRead or fmShareDenyNone); // Pufferlänge einstellen SetLength(buffer,128); // An den Anfang der ID3v1 Informationen gehen fstream.Seek(-128,soFromEnd); // 128 Bytes in den Puffer laden if (fstream.Read(buffer[1],128) < 128) then begin // Datenmenge reicht nicht für einlesen aus, Fehler result := -1; exit; end; // Freigeben des FileStreams, da nicht mehr benötigt fstream.Free; // Songtitel extrahieren ID3v1Infos.Songtitel := ''; for i:=4 to 33 do ID3v1Infos.Songtitel := ID3v1Infos.Songtitel+buffer[i]; // Künstler/Interpret extrahieren ID3v1Infos.Kuenstler := ''; for i:=34 to 63 do ID3v1Infos.Kuenstler := ID3v1Infos.Kuenstler+buffer[i]; // Albuminformationen extrahieren ID3v1Infos.Album := ''; for i:=64 to 93 do ID3v1Infos.Album := ID3v1Infos.Album+buffer[i]; // Erscheinungsjahr extrahieren ID3v1Infos.Erscheinungsjahr := ''; for i:=94 to 97 do ID3v1Infos.Erscheinungsjahr := ID3v1Infos.Erscheinungsjahr+buffer[i]; // Kommentar extrahieren ID3v1Infos.Kommentar := ''; for i:=98 to 127 do ID3v1Infos.Kommentar := ID3v1Infos.Kommentar+buffer[i]; // Genre extrahieren ID3v1Infos.Genre := buffer[128]; // Alles okay, Rückgabe result := 0; end; end.
Delphi-Quellcode:
procedure TForm1.Button1Click(Sender: TObject);
var mp3info: TID3v1; begin if JvOpenDialog1.Execute then begin if (clsMP3ID3vX.getID3v1Information(JvOpenDialog1.FileName,mp3info) >= 0) then begin Label1.Caption := 'Songtitel: '+mp3info.Songtitel; Label2.Caption := 'Künstler/Interpret: '+mp3info.Kuenstler; Label3.Caption := 'Album: '+mp3info.Album; Label4.Caption := 'Erscheinungsjahr: '+mp3info.Erscheinungsjahr; Label5.Caption := 'Kommentar: '+mp3info.Kommentar; Label6.Caption := 'Genre: '+mp3info.Genre; end else begin MessageDlg('Es trat ein Fehler auf!', mtError, [mbOK], 0); end; end; end; |
Re: Codeprüfung/-vorschläge | Thema: MP3, ID-Infos
Die Kommentare sind alle Überflüssig.
Die Methode getID3v1Information würde ich private machen, das "g" groß schreiben und eine entsprechende read-Property einführen. Später kannst du dann die read-Property noch um eine write-Property erweitern, wenn man dann auch Informationen schreiben kann mit deiner Klasse. Als Rückgabetyp würde ich Boolean nehmen, wenn du keine weiteren eigenen Fehlercodes definierts. Warum die gloable Variabel clsMP3ID3vX? Wie wäre es mit einem Konstruktor, dem du den Dateinamen übergibst? Ich würde die Daten nicht in einem Record zurückgeben, sondern über Properties. Und das:
Delphi-Quellcode:
Sieht auch komisch aus. Wenn der Titel kürzer ist, aber das String-Ende Zeichen fehlt, schreibst du Mist in das Feld. Zumindest ein Trim sollte noch folgen. Und könnte man da nicht SetString nehmen oder so?
for i:=4 to 33 do
ID3v1Infos.Songtitel := ID3v1Infos.Songtitel+buffer[i];
Delphi-Quellcode:
So. Zwei Konstruktoren, damit, wenn man meherer Datein abfragen will, nicht immer die Instanz neu anlegen muss. Dafür brauchen wir auch die Property Filename. SetFilename sollte eine Exception auslösen, wenn die Datei nicht existiert. GetMp3Info sollte auch eine Exception auslösen, wenn die ID3 Informationen nicht gelesen werden können und auch noch mal, überprüfen, ob die Datei immer noch da ist und dann eventuell auch eine Exception auslösen. (Kann ja sein, dass sie nicht mehr da ist, weil sie gelöscht, umbenannt wurde oder das Laufwerk nicht mehr verfügbar ist.) Insofern könnte GetMp3Info auch eine Prozedur sein.
type
TMp3Info = class(TObject); private procedure SetFilename(const Filename: String); function GetFilename: String; function GetMp3Info: Boolean; function GetTitle: String; function GetArtist: String; // .. public procedure Create(const Filename: String); override; procedure Create; override; property Filename: String read GetFilename write SetFilename; property Title: String read GetTitle; property Artist: String read GetArtist; // ... end; Das wäre meine Lösung. Muss nicht die Meinung aller treffen, aber so sieht es für mich sauber und schlüssig aus. |
Re: Codeprüfung/-vorschläge | Thema: MP3, ID-Infos
Zitat:
Vor allem für Leute, die meinen Code später mal lesen wollen, erhöht das die Lesbarkeit. Solange die Kommentare keine Zeitliche verzögerung bedeuten oder das compilierte Programm vergrößern, können die drinbleiben (das ist ja eh ansichtssache des einzelnen Entwicklers). Zitat:
Zitat:
Zitat:
Zitat:
Zitat:
Zitat:
Laut Spec ist das Feld _genau_ 30 Zeichen groß! Zitat:
Aber mich freut es schon mal, dass du keine Fehler 'an-sich' gefunden hast. |
Re: Codeprüfung/-vorschläge | Thema: MP3, ID-Infos
Hi,
ich denke da gibt es wirklich ein paar Punkte, die du dir noch mal überlegen solltest. Ok, sehe gerade den roten Kasten und möchte die Kritik dort ergänzen. An sich solltest du wirklich überlegen, ob du hier ein Record verwenden möchtest. Deine TMP3ID3vX ist ja schon mal eine Klasse, da sehe ich dann wenig Sinn nicht komplett auf die OOP zu setzen. Das ganze bringt dir schon mal mindestens den Vorteil, dass du dich weniger um die Speicherverwaltung kümmern musst. Zudem kannst du das var beim übergeben weglassen, denn hier würde aut. eine Referenz übergeben werden. Was völlig fehlt ist bei dir jegliches Fehlermanagment. Das wurde zwar schon gesagt, aber auffällig ist, dass du hier davon ausgehst, dass du einen gültigen Pfad bekommst. Was aber wenn der nicht mehr gültig ist (jmd. wählt die Datei aus und verschiebt die kurze Zeit später, löscht sie, ...). Da solltest du auf jeden Fall mit einem Ressourcenschutzblock arbeiten und an sich immer dafür sorgen, dass alle erzeugten Ressourcen immer sicher frei gegeben werden. Ganz schlecht ist auch die Art und Weise, wie du hier mit den Strings arbeitest. Strings sind eine sehr besondere Struktur, du solltest sie immer als eine Klasse mit statischem impliziten Konstruktor betrachten. Anders gesagt, jede Änderung des Strings bring etwas Overhead mit sich, da größerer Speicher alloziert wird, der alte String dort hineinkopiert und um den neuen String erweitert wird. Besser als eine Schleife und ein s + x ist es, wenn du hier einfach copy verwendest und direkt den entsprechenden Bereich in einem Rutsch kopierst. Dann solltest du auch noch schauen, um welche Version von ID3v1 es sich handelt, wenn ich mich da richtig erinnere, gab es da zwei und die solltest du auch unterscheiden (einmal mit und einmal ohne Genre?). Zu guter letzt würde ich jetzt noch sagen, dass dein Klassenname TMP3ID3vX gaaaanz schlecht gewählt ist. Erstens solltest du hier nicht einfach nur Großbuchstaben (sehen wir vom x ab) verwenden. Nur der Anfangsbuchstabe eines Wortes, aber auch eines Akronyms, etc. sollte groß geschrieben werden, das macht es viel leichter möglich zu erkennen, worum es sich hier handelt. Der Name TMp3Id3vX wäre aber immernoch schlecht gewählt. vX? Da würde ich jetzt nicht davon ausgehen, dass ich ein TId3v1 bekomme, sondern die Auswahl habe (bei dir noch nicht berücksichtigt, geht aber nicht aus dem Namen hervor). Gruß Der Unwissende Zitat:
Zitat:
Zitat:
|
Re: Codeprüfung/-vorschläge | Thema: MP3, ID-Infos
Was auf jeden Fall fehlt: Eine Abfrage, ob der ID3-Tag überhaupt vorhanden ist!
Denn bei Byte 128 vom Ende aus gesehen muss kein ID3v1-Tag beginnen. Er kann da beginnen. Wenn dort keiner zu finden ist, gibt deine Funktion Datenmüll zurück, wahrscheinlich die letzten paar Bytes des (der) letzten MPEG-Frames. Ein ID3v1Tag fängt mit 'TAG' an. Weiter wäre Unterstützung des v1.1-Tags schön. Das ist eine kleine Erweiterung: Wenn das 29. Byte des Kommentarfeldes 0 ist, und das 30. ungleich 0, dann enthält dieses letzte Kommentar-Byte die Tracknummer. Zitat:
|
Re: Codeprüfung/-vorschläge | Thema: MP3, ID-Infos
Zitat:
Delphi-Quellcode:
Muss ich nur kommentieren, wenn in SongTitel nachher die Fläche eines n-Dimensionalen Oktaeders steht anstatt des SongTitels. Das Feld heißt doch schon Songtitel, was soll da anders drinne stehen?
// Songtitel extrahieren
ID3v1Infos.Songtitel := ''; for i:=4 to 33 do ID3v1Infos.Songtitel := ID3v1Infos.Songtitel+buffer[i]; Zitat:
Zitat:
Zitat:
Zitat:
Zitat:
Zitat:
Deine Lösung erfordert übrigens keine Klasse. Wozu? besteht doch nur aus einer Funktion. Und so wie du Klassen nutzt, mit einer globalen Variable ist das äußerst schlecht. Wo wird in deinem Beispiel Code eigentlich die Klasse instanziert? getID3v1Information ist doch keine Klassen-Methode. Nuimm mal die gloable Variable raus und du wirst sehen, dass du erst den Konstruktor aufrufen musst. |
Re: Codeprüfung/-vorschläge | Thema: MP3, ID-Infos
Zitat:
Zitat:
Zitat:
In der Doku finde ich dazu nichts, auch in einzelnen MP3 ist nichts zu finden, was eine Unterscheidung möglich macht... Zitat:
|
Re: Codeprüfung/-vorschläge | Thema: MP3, ID-Infos
Zitat:
Bisher habe ich aber nichts dazu in den entsprechenden Specs gefunden, was da reinsoll, wenn nichts da ist. Ich gehe daher davon aus, dass die restlichen Zeichen Leerzeichen sind. In meiner Sammlung habe ich bisher keine Datei gefunden, wo dies nicht so wäre. Wenn aber jemand was findet, immer her damit :) Zitat:
Zur Klasse: Ja, bisher ist es nur eine Funktion, aber die Klasse soll ja mal alle ID-Tags extrahieren...also kommen da noch weitere Funktionen rein (z.B. getID3v2Info, wenn ich so weitermachen _würde_). Mir ging es erstmal drum, ob es MP3 Daten mit ID3v1 Daten gibt, die damit nicht oder fehlerhaft gelesen werden würden. Unter dem Entdeckerdrang litt dann auch die Codeschönheit, was aber nun korrigiert wird. Ich mache das meist so, dass ich ein Gebiet erstmal 'Quick&Dirty' erforsche, dann teste ob es läuft und dann den Code nochmal neu schreibe, mit all den Erfahrungen und Erkentnissen, die ich aus den vorangegangenen Phasen gesammelt haben. Zitat:
Schönheit und Stabilität wird nachgereicht! |
Re: Codeprüfung/-vorschläge | Thema: MP3, ID-Infos
Zitat:
|
Re: Codeprüfung/-vorschläge | Thema: MP3, ID-Infos
Zitat:
Zitat:
Zu den Specs: ![]() Zitat:
|
Re: Codeprüfung/-vorschläge | Thema: MP3, ID-Infos
Danke nochmal für alle Infos.
Sobald wieder Zeit vorhanden ist, führe ich das ganze weiter. Bis dahin gehe ich im allgemeinen Arbeitsstreß unter *glugg* |
Re: Codeprüfung/-vorschläge | Thema: MP3, ID-Infos
Nochwas: Warum liest du keinen Record anstatt eines Buffers? Dann könntest du dir auch die Schleifen sparen können. Siehe Lib in der Signatur.
|
Re: Codeprüfung/-vorschläge | Thema: MP3, ID-Infos
Nochmal was zum Thema lesen, schreiben und Filenamen übergeben... Ich hab mehrere Libs in Gebrauch, die einen etwas anderen Ansatz haben: Es gibt ein Objekt namens z.B. TAudioInfo, das Properties wie Interpret (Artist), Songtitel, usw. hat, und Methoden, um diese Infos in ein File zu schreiben bzw. daraus zu lesen. Eine Objektinstanz fest an ein File zu binden... naja, Ansichtssache, mir gefällt obige Idee halt einfach besser :)
BTW, warum wollt ihr Fehler immer über Exceptions "rausreichen"? Schlechte Angewohnheit? Oder "Hamwer schon immer so gemacht"? Im "Windows von heute" haben Exceptions im Vergleich zu eigenem Errorhandling enormen Overhead, Microsoft (man kann von den Jungs halten was man will, aber grossenteils können die schon was) setzt nicht umsonst auf andere Wege. Neuere Methoden (Win32 API, MFC, ATL, usw.) liefern in den allermeisten Fällen ein BOOL zurück anhand dessen man feststellen kann obs einen Fehler gab - und wenns einen gab, dann kann man über GetLastError rausfinden, was passiert ist. Ausnahmen gibts natürlich auch hier, es gibt z.B. CreateFile-Aufrufe (bei den MFC-Klassen zumindest) denen ein Pointer auf ein vorgefertigtes Exception-Objekt mitgegeben wird. Falls dann der Rückgabewert FALSE ist, schaut man halt ins Exception-Objekt rein, um rauszufinden was Sache ist. Exceptions sollte man sich für die wirklich bösen Showstopper aufsparen, Access Violations zum Beispiel. ![]() Zitat:
|
Re: Codeprüfung/-vorschläge | Thema: MP3, ID-Infos
Wegen der Instanz und dem Dateinamen: Deswegen zwei Konstruktoren, da kann es sich jeder aussuchen. Beim Zuweisen der Datei-Property ist eine Exception gerechtfertigt, da ohne Datei das ganz keinen Sinn macht. Die Exception bei GetMp3Info ist geschmackssache.
|
Alle Zeitangaben in WEZ +1. Es ist jetzt 18:17 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 by Thomas Breitkreuz