id3tag Klasse mit id3v1 id3v2
-
Hi,
ich hab mal vor ein paar Wochen mir eine id3tag Klasse zum auslesen der Tags gebaut v1 und v2.
Der Sourcecode ist zwar nicht der beste, aber besser als nix.Zwei Sachen gefallen mir noch nicht:
1. Wenn ein Attribute gezipped ist, wird es nicht richtig angezeigt (da hab ich dann aufgehört) - sprich der ausgelesen Text ist nicht lesbar
2. Um die Bitrate zu lesen, habe ich mir den Code von einem anderen Thread hier im Forum geholt. Hier wird die Datei dann noch mal geöffnet und diesmal mit C-Funktionen ausgelesen. Das müsste noch auf C++ umgestellt werden um sauber zu sein...Wenn jemand lust hat, kann er ja noch die angesprochenen Punkte verbessern und hier wieder posten
Ist vielleicht was fürs FAQ, da ja schon öfters nach id3tag Klassen gefragt wurde.
Generell: Wenn man mehr braucht würde ich eine schon fertige Lib (z.b. http://id3lib.sourceforge.net/) benutzen. Die id3lib benutze ich selbst auch in meinen Touchscreen Projekt ( http://www.sally-project.de/ ).
#pragma once #include <fstream> #include <string> enum MP3TAG_VERSION {MP3TAG_VERSION_NONE, MP3TAG_VERSION_V1, MP3TAG_VERSION_V2}; class CMp3TagInfo { private: MP3TAG_VERSION m_eMp3TagVersion; std::string m_strFilename; bool m_bInit; // Headerinfo std::string m_strBitRate; std::string m_strMpegLayer; std::string m_strMpegVersion; // V1 std::string m_strTagVersionNumber; std::string m_strArtist; std::string m_strTitle; std::string m_strAlbum; std::string m_strComment; std::string m_strTrack; std::string m_strYear; std::string m_strGenre; // V2 ext std::string m_strComposer; std::string m_strCopyright; std::string m_strOriginalArtist; std::string m_strURL; std::string GetMp3Genre(std::string number); std::string GetMp3Genre(int number); bool ReadV1(const std::string& filename); bool ReadV2(const std::string& filename); bool FillHeaderInfos(std::string filename); void CleanUp(); public: CMp3TagInfo(); CMp3TagInfo(const std::string& filename); ~CMp3TagInfo(); bool Init(const std::string& filename); bool IsInit(); MP3TAG_VERSION GetMp3TagVersion(); std::string GetFilename(); std::string GetBitRate(); std::string GetMpegLayer(); std::string GetMpegVersion(); std::string GetTagVersionNumber(); std::string GetArtist(); std::string GetTitle(); std::string GetAlbum(); std::string GetComment(); std::string GetTrack(); std::string GetYear(); std::string GetGenre(); std::string GetComposer(); std::string GetCopyright(); std::string GetOriginalArtist(); std::string GetURL(); };
#include "Mp3TagInfo.h" // bitrate info int bitratetable[2][16]={0,8,16,24,32,64,80,56,64,128,160,112,128,256,320,0,0,32,40,48,56,64,80,96,112,128,160,192,224,256,320,0}; // todo: put into the string utils std::string RemoveUnnecessarySpaces(std::string inString) { // Remove spaces at start while ((inString.length() > 0) && (inString[0] == ' ')) inString.erase(0,1); // Remove spaces from end while ((inString.length() > 0) && (inString[inString.length() -1] == ' ')) inString.erase(inString.length() -1,1); return inString; } CMp3TagInfo::CMp3TagInfo() :m_eMp3TagVersion(MP3TAG_VERSION_NONE), m_bInit(false), m_strBitRate("0") { } CMp3TagInfo::CMp3TagInfo(const std::string& filename) :m_eMp3TagVersion(MP3TAG_VERSION_NONE), m_bInit(false), m_strBitRate("0") { Init(filename); } CMp3TagInfo::~CMp3TagInfo() { } void CMp3TagInfo::CleanUp() { m_eMp3TagVersion = MP3TAG_VERSION_NONE; m_bInit = false; m_strBitRate = "0"; m_strMpegLayer = ""; m_strMpegVersion = ""; m_strArtist = ""; m_strTitle = ""; m_strAlbum = ""; m_strComment = ""; m_strTrack = ""; m_strYear = ""; m_strGenre = ""; m_strComposer = ""; m_strCopyright = ""; m_strOriginalArtist = ""; m_strURL = ""; m_strFilename = ""; } bool CMp3TagInfo::Init(const std::string& filename) { // have we already loaded a file? // than reset all if (m_bInit) CleanUp(); m_strFilename = filename; // try to read v2 if (ReadV2(filename) == false) { // try to read v1 if (ReadV1(filename) == false) { return false; } } FillHeaderInfos(filename); m_bInit = true; return true; } bool CMp3TagInfo::ReadV1(const std::string& filename) { std::fstream file; file.open(filename.c_str(), std::ios::in | std::ios::binary); if (!file) return false; try { // go to then end file.seekg(-128, std::ios::end); // check if we have here a MP3 Tag char id[3]; file.read(id, 3); if (strncmp(id, "TAG", 3) != 0) return false; // now read all values from the file char buffer[31]; file.read(buffer, 30); buffer[30] = '\0'; m_strTitle = buffer; file.read(buffer, 30); buffer[30] = '\0'; m_strArtist = buffer; file.read(buffer, 30); buffer[30] = '\0'; m_strAlbum = buffer; file.read(buffer, 4); buffer[4] = '\0'; m_strYear = buffer; file.read(buffer, 28); buffer[28] = '\0'; m_strComment = buffer; file.read(buffer, 2); buffer[2] = '\0'; m_strTrack = buffer; file.read(buffer, 1); buffer[1] = '\0'; m_strGenre = GetMp3Genre(buffer[0]); } catch (std::exception) { file.close(); return false; } m_strTagVersionNumber = "1"; // close the file file.close(); // cleanup the strings RemoveUnnecessarySpaces(m_strTitle); RemoveUnnecessarySpaces(m_strArtist); RemoveUnnecessarySpaces(m_strAlbum); RemoveUnnecessarySpaces(m_strYear); RemoveUnnecessarySpaces(m_strComment); RemoveUnnecessarySpaces(m_strTrack); RemoveUnnecessarySpaces(m_strGenre); m_eMp3TagVersion = MP3TAG_VERSION_V1; return true; } bool CMp3TagInfo::ReadV2(const std::string& filename) { std::fstream file; std::ifstream::pos_type fileSize; // open file file.open(filename.c_str(), std::ios::in | std::ios::binary | std::ios::ate); if (!file) return false; try { // get file length fileSize = file.tellg(); file.seekg(0, std::ios::beg); // do we have a id3v2 tag char version[3]; file.read(version, 3); if (strncmp(version, "ID3", 3)) { // no id3v2 tag - exit file.close(); return false; } // the max we read with this buffer char temp[10]; int tagLength = 0; int headerSize = 0; int frameSize = 0; // ID3v2/file identifier ID3 // ID3v2 version $03 00 // ID3v2 flags %abc00000 // ID3v2 size 4 * %0xxxxxxx // get the version m_strTagVersionNumber = "2."; char versionTemp[11]; file.read(temp, 1); int versionNumber = temp[0]; _itoa_s(versionNumber, versionTemp, 10); m_strTagVersionNumber.append(versionTemp); m_strTagVersionNumber.append("."); file.read(temp, 1); int versionRevison = temp[0]; _itoa_s(versionRevison, versionTemp, 10); m_strTagVersionNumber.append(versionTemp); // go to the flags file.read(temp, 1); // jump over the flags // read the length file.read(temp, 4); headerSize = ((temp[3]&0x7F) + ((temp[2]&0x7F)<<7) + ((temp[1]&0x7F)<<14)+ ((temp[0]&0x7F)<<21))-1 ; // now read the frames char frameName[5]; file.read(frameName, 4); frameName[4] = '\0'; while(((frameName[0] + frameName[1] + frameName[2] + frameName[3]) != 0) && (headerSize > 7) // Still more info in the file? ) { file.read(temp, 4); frameSize = ( (((unsigned int)temp[3])&0x000000FF) + ((((unsigned int)temp[2])&0x000000FF)<<8 )+ ((((unsigned int)temp[1])&0x000000FF)<<16)+ ((((unsigned int)temp[0])&0x000000FF)<<24) ); file.read(temp, 3); //if ((unsigned int) temp[0]) bool f = ((((unsigned int)temp[0])&0x0000000F)<<4); // subtract bytes read from total header size headerSize -= (7 + frameSize); // is there data in this block? if(frameSize > 0) { char* tempFrameBuffer = new char[frameSize]; frameSize -= 1; // read the frame data file.read(tempFrameBuffer, frameSize); tempFrameBuffer[frameSize] = '\0'; if (strncmp(frameName, "TIT2", 4) == 0) { m_strTitle = tempFrameBuffer; } else if (strncmp(frameName, "TALB", 4) == 0) { m_strAlbum = tempFrameBuffer; } else if (strncmp(frameName, "TCON", 4) == 0) { m_strGenre = GetMp3Genre(tempFrameBuffer); } else if (strncmp(frameName, "TPE1", 4) == 0) { m_strArtist = tempFrameBuffer; } else if (strncmp(frameName, "COMM", 4) == 0) { m_strComment = tempFrameBuffer; } else if (strncmp(frameName, "TYER", 4) == 0) { m_strYear = tempFrameBuffer; } else if (strncmp(frameName, "TRCK", 4) == 0) { m_strTrack = tempFrameBuffer; } else if (strncmp(frameName, "TCOM", 4) == 0) { m_strComposer = tempFrameBuffer; } else if (strncmp(frameName, "TOPE", 4) == 0) { m_strOriginalArtist = tempFrameBuffer; } else if (strncmp(frameName, "WCOP", 4) == 0) { m_strCopyright = tempFrameBuffer; } else if (strncmp(frameName, "WXXX", 4) == 0) { m_strURL = tempFrameBuffer; } delete[] tempFrameBuffer; } ////////////////////////////////////////////////////////////////////////// // read now the next frame // reset the memory memset(frameName, 5, 0); // read from file, if we are not at the end if (file.tellg() < ((int) fileSize) - 4) { file.read(frameName, 4) ; } } } catch (std::exception) { file.close(); return false; } RemoveUnnecessarySpaces(m_strTitle); RemoveUnnecessarySpaces(m_strArtist); RemoveUnnecessarySpaces(m_strAlbum); RemoveUnnecessarySpaces(m_strYear); RemoveUnnecessarySpaces(m_strComment); RemoveUnnecessarySpaces(m_strTrack); RemoveUnnecessarySpaces(m_strGenre); RemoveUnnecessarySpaces(m_strComposer); RemoveUnnecessarySpaces(m_strCopyright); RemoveUnnecessarySpaces(m_strOriginalArtist); RemoveUnnecessarySpaces(m_strURL); m_eMp3TagVersion = MP3TAG_VERSION_V2; file.close(); return true; } bool CMp3TagInfo::FillHeaderInfos(std::string filename) { FILE* file = NULL; fopen_s(&file, filename.c_str(), "rb"); if(!file) return false; int curch; // finding first frame for MPEG 1 LAYER III bool flag = false; do { if ((curch = fgetc(file)) == EOF) return false; if (curch == 255) { if ((curch = fgetc(file)) == EOF) return false; if (tolower(curch/16) == 15) flag = true; } } while(!flag); // version check MPEG 1/2... int impegversion=tolower(((curch%16)/4)/2); if(impegversion==1) m_strMpegVersion = "MPEG 1"; else if (impegversion==0) m_strMpegVersion = "MPEG 2"; // layer check.... int layer = tolower((((curch%16)/4)%2)*2+(((curch%16)%4)/2)); switch (layer) { case 0: m_strMpegLayer = "Reserved"; break; case 1: m_strMpegLayer = "Layer III"; break; case 2: m_strMpegLayer = "Layer II"; break; case 3: m_strMpegLayer = "Layer I"; break; } //get next byte to read 3rd byte of header. if ((curch = fgetc(file)) == EOF) return false; int mp3bitrate = bitratetable[impegversion][tolower(curch/16)]; char temp[11]; _itoa_s(mp3bitrate, temp, 10); m_strBitRate = temp; fclose(file); return true; } std::string CMp3TagInfo::GetMp3Genre(std::string number) { // is the genre for example (09) than we have to resolve the number if (number.length() != 4) return number; if ((number[0] != '(') || (number[3] != ')')) return number; number = number.substr(1, number.length() - 2); int i = atoi(number.c_str()); return GetMp3Genre(i); } std::string CMp3TagInfo::GetMp3Genre(int number) { switch(number) { case 0: return "Blues"; case 1: return "Classic Rock"; case 2: return "Country"; case 3: return "Dance"; case 4: return "Disco"; case 5: return "Funk"; case 6: return "Grunge"; case 7: return "Hip-Hop"; case 8: return "Jazz"; case 9: return "Metal"; case 10: return "New Age"; case 11: return "Oldies"; case 12: return "Other"; case 13: return "Pop"; case 14: return "R&B"; case 15: return "Rap"; case 16: return "Reggae"; case 17: return "Rock"; case 18: return "Techno"; case 19: return "Industrial"; case 20: return "Alternative"; case 21: return "Ska"; case 22: return "Death Metal"; case 23: return "Pranks"; case 24: return "Soundtrack"; case 25: return "Euro-Techno"; case 26: return "Ambient"; case 27: return "Trip-Hop"; case 28: return "Vocal"; case 29: return "Jazz&Funk"; case 30: return "Fusion"; case 31: return "Trance"; case 32: return "Classical"; case 33: return "Instrumental"; case 34: return "Acid"; case 35: return "House"; case 36: return "Game"; case 37: return "Sound Clip"; case 38: return "Gospel"; case 39: return "Noise"; case 40: return "Alternative Rock"; case 41: return "Bass"; case 42: return "Soul"; case 43: return "Punk"; case 44: return "Space"; case 45: return "Meditative"; case 46: return "Instrumental Pop"; case 47: return "Instrumental Rock"; case 48: return "Ethnic"; case 49: return "Gothic"; case 50: return "Darkwave"; case 51: return "Techno-Industrial"; case 52: return "Electronic"; case 53: return "Pop-Folk"; case 54: return "Eurodance"; case 55: return "Dream"; case 56: return "Southern Rock"; case 57: return "Comedy"; case 58: return "Cult"; case 59: return "Gangsta"; case 60: return "Top 40"; case 61: return "Christian Rap"; case 62: return "Pop/Funk"; case 63: return "Jungle"; case 64: return "Native US"; case 65: return "Cabaret"; case 66: return "New Wave"; case 67: return "Psychedelic"; case 68: return "Rave"; case 69: return "Showtunes"; case 70: return "Trailer"; case 71: return "Lo-Fi"; case 72: return "Tribal"; case 73: return "Acid Punk"; case 74: return "Acid Jazz"; case 75: return "Polka"; case 76: return "Retro"; case 77: return "Musical"; case 78: return "Rock & Roll"; case 79: return "Hard Rock"; case 80: return "Folk"; case 81: return "Folk-Rock"; case 82: return "National Folk"; case 83: return "Swing"; case 84: return "Fast Fusion"; case 85: return "Bebop"; case 86: return "Latin"; case 87: return "Revival"; case 88: return "Celtic"; case 89: return "Bluegrass"; case 90: return "Avantgarde"; case 91: return "Gothic Rock"; case 92: return "Progressive Rock"; case 93: return "Psychedelic Rock"; case 94: return "Symphonic Rock"; case 95: return "Slow Rock"; case 96: return "Big Band"; case 97: return "Chorus"; case 98: return "Easy Listening"; case 99: return "Acoustic"; case 100: return "Humour"; case 101: return "Speech"; case 102: return "Chanson"; case 103: return "Opera"; case 104: return "Chamber Music"; case 105: return "Sonata"; case 106: return "Symphony"; case 107: return "Booty Bass"; case 108: return "Primus"; case 109: return "Porn Groove"; case 110: return "Satire"; case 111: return "Slow Jam"; case 112: return "Club"; case 113: return "Tango"; case 114: return "Samba (Musik)"; case 115: return "Folklore"; case 116: return "Ballad"; case 117: return "Power Ballad"; case 118: return "Rhytmic Soul"; case 119: return "Freestyle"; case 120: return "Duet"; case 121: return "Punk Rock"; case 122: return "Drum Solo"; case 123: return "Acapella"; case 124: return "Euro-House"; case 125: return "Dance Hall"; case 126: return "Goa"; case 127: return "Drum’n’Bass"; case 128: return "Club-House"; case 129: return "Hardcore"; case 130: return "Terror"; case 131: return "Indie"; case 132: return "BritPop"; case 133: return "Negerpunk"; case 134: return "Polsk Punk"; case 135: return "Beat"; case 136: return "Christian Gangsta"; case 137: return "Heavy Metal"; case 138: return "Black Metal"; case 139: return "Crossover"; case 140: return "Contemporary Christian"; case 141: return "Christian Rock"; case 142: return "Merengue"; case 143: return "Salsa"; case 144: return "Thrash Metal"; case 145: return "Anime"; case 146: return "JPop"; case 147: return "SynthPop"; default: return ""; } } bool CMp3TagInfo::IsInit() { return m_bInit; } ////////////////////////////////////////////////////////////////////////// // all the getter ////////////////////////////////////////////////////////////////////////// MP3TAG_VERSION CMp3TagInfo::GetMp3TagVersion() { return m_eMp3TagVersion; } std::string CMp3TagInfo::GetFilename() { return m_strFilename; } std::string CMp3TagInfo::GetBitRate() { return m_strBitRate; } std::string CMp3TagInfo::GetMpegLayer() { return m_strMpegLayer; } std::string CMp3TagInfo::GetMpegVersion() { return m_strMpegVersion; } std::string CMp3TagInfo::GetTagVersionNumber() { return m_strTagVersionNumber; } std::string CMp3TagInfo::GetArtist() { return m_strArtist; } std::string CMp3TagInfo::GetTitle() { return m_strTitle; } std::string CMp3TagInfo::GetAlbum() { return m_strAlbum; } std::string CMp3TagInfo::GetComment() { return m_strComment; } std::string CMp3TagInfo::GetTrack() { return m_strTrack; } std::string CMp3TagInfo::GetYear() { return m_strYear; } std::string CMp3TagInfo::GetGenre() { return m_strGenre; } std::string CMp3TagInfo::GetComposer() { return m_strComposer; } std::string CMp3TagInfo::GetCopyright() { return m_strCopyright; } std::string CMp3TagInfo::GetOriginalArtist() { return m_strOriginalArtist; } std::string CMp3TagInfo::GetURL() { return m_strURL; }
-
Dieser Thread wurde von Moderator/in evilissimo aus dem Forum C++ in das Forum Projekte verschoben.
Im Zweifelsfall bitte auch folgende Hinweise beachten:
C/C++ Forum :: FAQ - Sonstiges :: Wohin mit meiner Frage?Dieses Posting wurde automatisch erzeugt.
-
Oh man, wo soll man da anfangen?
-
Kóyaánasqatsi schrieb:
Oh man, wo soll man da anfangen?
Mir wäre nicht aufgefallen, dass der OP um Kommentare zur Code-Qualität oder ähnlichem gefragt hätte.
-
Ich wollte keinen Code Review... ich dachte nur dass es jemand brauchen kann.
Aber wenn jemand was zum anmerken am Code hat freu ich mich natürlich auch.
Allerdings wurde das ganze auch nur schnell zusammen gebaut... weiß selber das es nicht das Beste ist – aber es funktioniert!
-
Kóyaánasqatsi schrieb:
Oh man, wo soll man da anfangen?
Was genau gefällt dir den an dem Code nicht?
Will ja auch was hinzu lernen...
-
Der_Knob schrieb:
Was genau gefällt dir den an dem Code nicht?
Will ja auch was hinzu lernen...Header:
-Dateiheader in einer Struktur ablagern, damit die Datenstruktur "abgekapselt" ist und die Hauptklasse nicht so riesig wird
-Bei GetMp3Genre(std::string) und GetMp3Genre(int i) können die Datentypen ruhig const und die fnc's auch const seinCpp:
-CleanUp() sieht böse aus
-Zeile 109-134 schreien gerade zu automatisiert zu werden
-GetMp3Genre() hm, da fällt mir sicherlich auch noch was schöneres ein...Ansonsten:
-Es sollte nicht Sinn einer Klasse sein, Getter für alle mem-vars bereitzustellen, das ist schlechtes Design
-Mit std::ifstream::exception() lassen sich flags zur Exception-Kontrolle setzen
-Eindeutige Namen für die mem-fncs erfinden (wtf ist V1 oder V2!?)
-
Kóyaánasqatsi schrieb:
-GetMp3Genre() hm, da fällt mir sicherlich auch noch was schöneres ein...
Fängt es mit Tab an und hört mit elle auf?
Kóyaánasqatsi schrieb:
-Eindeutige Namen für die mem-fncs erfinden (wtf ist V1 oder V2!?)
Ich denke, das ist im Kontext der MP3-Tags klar. http://de.wikipedia.org/wiki/ID3-Tag#ID3v1
-
Danke für das Feedback
Eine paar Fragen habe ich noch:
Macht es Laufzeittechnisch einen Unterschied wenn ich bei einer Funktion den int Übergabewert const mache (GetMp3Genre(int i))?
-Es sollte nicht Sinn einer Klasse sein, Getter für alle mem-vars bereitzustellen, das ist schlechtes Design
Wäre eine Funktion in der man einen Identifier rein gibt und dann das entsprechende"Object" zurück bekommt besser? oder woran hast du gedacht?
-CleanUp() sieht böse aus
Ich ruf jedesmal cleanup auf wenn auf der Instance ein Init aufgerufen wird. Wie könnte / sollte ich das besser lösen?
Die anderen Sachen sind mir soweit klar und ich stimme dir zu
@volkard
Ja genau, ich denke das mit v1 und v2 erklärt im MP3-Tag Kontext
-
Der_Knob schrieb:
Danke für das Feedback
Eine paar Fragen habe ich noch:
Macht es Laufzeittechnisch einen Unterschied wenn ich bei einer Funktion den int Übergabewert const mache (GetMp3Genre(int i))?
Nein, macht keinen Unterschied.
Auch wird Laufzeittechnisch alles was du machst im Vergleich zum File-IO verblassen.
Solche Mikro-Optimierungen, in Funktionen, die aus Files lesen müssen (oder in Files schreiben), machen überhaupt garkeinen Sinn.-Es sollte nicht Sinn einer Klasse sein, Getter für alle mem-vars bereitzustellen, das ist schlechtes Design
Wäre eine Funktion in der man einen Identifier rein gibt und dann das entsprechende"Object" zurück bekommt besser? oder woran hast du gedacht?
Ich weiss nicht woran er gedacht hat, aber ich würde es vermutlich eher so angehen, dass ich eine Klasse habe die den Tag-Inhalt beschreibt. Und dann habe ich eine Helper-Funktion, die die Infos aus einem File lädt, und so eine "TagContents" Klasse zurückgibt. Der "TagContents" Klasse dann Getter und Setter für alle möglichen Felder zu verpassen, halte ich dagegen eher für guten Stil.
Die Definition wie so ein Tag-Inhalt aussieht, und wie man ihn aus einem File laden kann, sind für mich zwei verschiedene Dinge. Und sollten daher auch getrennt werden.
-CleanUp() sieht böse aus
Ich ruf jedesmal cleanup auf wenn auf der Instance ein Init aufgerufen wird. Wie könnte / sollte ich das besser lösen?
Wenn du den ganzen IO/Parsing Code aus der Tag-Klasse raus nimmst, brauchst du auch keine CleanUp() Funktion mehr.
Stattdessen solltest du vermutlich sicher stellen dass der Assignment-Operator korrekt funktioniert, und dass ein default-konstruiertes Tag Objekt "leer" ist. Wenn du dann irgendwo die "Cleanup" Funktionalität brauchst, kannst du einfach schreiben:
Tag tag = LoadTagFromFile(...); // ... tag = Tag(); // "cleanup" // ...
-
gracias für die Antworten
Auch wird Laufzeittechnisch alles was du machst im Vergleich zum File-IO verblassen.
Solche Mikro-Optimierungen, in Funktionen, die aus Files lesen müssen (oder in Files schreiben), machen überhaupt garkeinen Sinn.Ich denk aber man sollte trotzdem so gut wie möglich optimieren. Wenn man einen Thread hat welcher immer im Hintergrund läuft dann kann man denk ich über mehrere Minuten hinweg schon ein paar Sekunden sparen!?!? Aber stimmt schon, wenn IO im Spiel ist, ist das immer das Bottleneck...