int PrintInformation(char *sFileName, const ID3_Tag &myTag) { bool firstLine = true; const ID3_Frame * myFrame; ID3_Tag::ConstIterator *Iter=myTag.CreateIterator(); for (size_t nFrames = 0; nFrames < myTag.NumFrames(); nFrames++) { myFrame = Iter->GetNext(); if(firstLine) { std::cout << "id3v2 tag info for " << sFileName << ":" << std::endl; firstLine = false; } if (NULL != myFrame) { const char* desc = myFrame->GetDescription(); if (!desc) desc = ""; std::cout << myFrame->GetTextID() << " (" << desc << "): "; ID3_FrameID eFrameID = myFrame->GetID(); switch (eFrameID) { case ID3FID_ALBUM: case ID3FID_BPM: case ID3FID_COMPOSER: case ID3FID_COPYRIGHT: case ID3FID_DATE: case ID3FID_PLAYLISTDELAY: case ID3FID_ENCODEDBY: case ID3FID_LYRICIST: case ID3FID_FILETYPE: case ID3FID_TIME: case ID3FID_CONTENTGROUP: case ID3FID_TITLE: case ID3FID_SUBTITLE: case ID3FID_INITIALKEY: case ID3FID_LANGUAGE: case ID3FID_SONGLEN: case ID3FID_MEDIATYPE: case ID3FID_ORIGALBUM: case ID3FID_ORIGFILENAME: case ID3FID_ORIGLYRICIST: case ID3FID_ORIGARTIST: case ID3FID_ORIGYEAR: case ID3FID_FILEOWNER: case ID3FID_LEADARTIST: case ID3FID_BAND: case ID3FID_CONDUCTOR: case ID3FID_MIXARTIST: case ID3FID_PARTINSET: case ID3FID_PUBLISHER: case ID3FID_TRACKNUM: case ID3FID_RECORDINGDATES: case ID3FID_NETRADIOSTATION: case ID3FID_NETRADIOOWNER: case ID3FID_SIZE: case ID3FID_ISRC: case ID3FID_ENCODERSETTINGS: case ID3FID_YEAR: { char *sText = ID3_GetString(myFrame, ID3FN_TEXT); std::cout << sText << std::endl; delete [] sText; break; } case ID3FID_CONTENTTYPE: { const char* genre_str; int genre_id = 255; char *sText = ID3_GetString(myFrame, ID3FN_TEXT); sscanf(sText, "(%d)", &genre_id); if (genre_id == 255) { genre_str = sText; genre_id = GetNumFromGenre(sText); } else genre_str = GetGenreFromNum(genre_id); std::cout << genre_str << " (" << genre_id << ")" << std::endl; delete [] sText; break; } case ID3FID_USERTEXT: { char *sText = ID3_GetString(myFrame, ID3FN_TEXT), *sDesc = ID3_GetString(myFrame, ID3FN_DESCRIPTION); std::cout << "(" << sDesc << "): " << sText << std::endl; delete [] sText; delete [] sDesc; break; } case ID3FID_TERMSOFUSE: { char *sText = ID3_GetString(myFrame, ID3FN_TEXT), *sLang = ID3_GetString(myFrame, ID3FN_LANGUAGE); std::cout << "[" << sLang << "]: " << sText << std::endl; delete [] sText; delete [] sLang; break; } case ID3FID_COMMENT: case ID3FID_UNSYNCEDLYRICS: { char *sText = ID3_GetString(myFrame, ID3FN_TEXT), *sDesc = ID3_GetString(myFrame, ID3FN_DESCRIPTION), *sLang = ID3_GetString(myFrame, ID3FN_LANGUAGE); std::cout << "(" << sDesc << ")[" << sLang << "]: " << sText << std::endl; delete [] sText; delete [] sDesc; delete [] sLang; break; } case ID3FID_WWWAUDIOFILE: case ID3FID_WWWARTIST: case ID3FID_WWWAUDIOSOURCE: case ID3FID_WWWCOMMERCIALINFO: case ID3FID_WWWCOPYRIGHT: case ID3FID_WWWPUBLISHER: case ID3FID_WWWPAYMENT: case ID3FID_WWWRADIOPAGE: { char *sURL = ID3_GetString(myFrame, ID3FN_URL); std::cout << sURL << std::endl; delete [] sURL; break; } case ID3FID_WWWUSER: { char *sURL = ID3_GetString(myFrame, ID3FN_URL), *sDesc = ID3_GetString(myFrame, ID3FN_DESCRIPTION); std::cout << "(" << sDesc << "): " << sURL << std::endl; delete [] sURL; delete [] sDesc; break; } case ID3FID_INVOLVEDPEOPLE: { // This isn't the right way to do it---will only get first person size_t nItems = myFrame->Field(ID3FN_TEXT).GetNumTextItems(); for (size_t nIndex = 1; nIndex <= nItems; nIndex++) { char *sPeople = ID3_GetString(myFrame, ID3FN_TEXT, nIndex); std::cout << sPeople; delete [] sPeople; if (nIndex < nItems) { std::cout << ", "; } } std::cout << std::endl; break; } case ID3FID_PICTURE: { char *sMimeType = ID3_GetString(myFrame, ID3FN_MIMETYPE), *sDesc = ID3_GetString(myFrame, ID3FN_DESCRIPTION), *sFormat = ID3_GetString(myFrame, ID3FN_IMAGEFORMAT); size_t nPicType = myFrame->Field(ID3FN_PICTURETYPE).Get(), nDataSize = myFrame->Field(ID3FN_DATA).Size(); std::cout << "(" << sDesc << ")[" << sFormat << ", " << nPicType << "]: " << sMimeType << ", " << nDataSize << " bytes" << std::endl; delete [] sMimeType; delete [] sDesc; delete [] sFormat; break; } case ID3FID_GENERALOBJECT: { char *sMimeType = ID3_GetString(myFrame, ID3FN_MIMETYPE), *sDesc = ID3_GetString(myFrame, ID3FN_DESCRIPTION), *sFileName = ID3_GetString(myFrame, ID3FN_FILENAME); size_t nDataSize = myFrame->GetField(ID3FN_DATA)->Size(); std::cout << "(" << sDesc << ")[" << sFileName << "]: " << sMimeType << ", " << nDataSize << " bytes" << std::endl; delete [] sMimeType; delete [] sDesc; delete [] sFileName; break; } case ID3FID_UNIQUEFILEID: { char *sOwner = ID3_GetString(myFrame, ID3FN_OWNER); size_t nDataSize = myFrame->Field(ID3FN_DATA).Size(); std::cout << sOwner << ", " << nDataSize << " bytes" << std::endl; delete [] sOwner; break; } case ID3FID_PLAYCOUNTER: { size_t nCounter = myFrame->Field(ID3FN_COUNTER).Get(); std::cout << nCounter << std::endl; break; } case ID3FID_POPULARIMETER: { char *sEmail = ID3_GetString(myFrame, ID3FN_EMAIL); size_t nCounter = myFrame->Field(ID3FN_COUNTER).Get(), nRating = myFrame->Field(ID3FN_RATING).Get(); std::cout << sEmail << ", counter=" << nCounter << " rating=" << nRating; delete [] sEmail; break; } case ID3FID_CRYPTOREG: case ID3FID_GROUPINGREG: { char *sOwner = ID3_GetString(myFrame, ID3FN_OWNER); size_t nSymbol = myFrame->Field(ID3FN_ID).Get(), nDataSize = myFrame->Field(ID3FN_DATA).Size(); std::cout << "(" << nSymbol << "): " << sOwner << ", " << nDataSize << " bytes"; break; } case ID3FID_AUDIOCRYPTO: case ID3FID_EQUALIZATION: case ID3FID_EVENTTIMING: case ID3FID_CDID: case ID3FID_MPEGLOOKUP: case ID3FID_OWNERSHIP: case ID3FID_PRIVATE: case ID3FID_POSITIONSYNC: case ID3FID_BUFFERSIZE: case ID3FID_VOLUMEADJ: case ID3FID_REVERB: case ID3FID_SYNCEDLYRICS: case ID3FID_SYNCEDTEMPO: case ID3FID_METACRYPTO: { std::cout << " (unimplemented)" << std::endl; break; } default: { std::cout << " frame" << std::endl; break; } } } } delete Iter; if(firstLine) return 1; return 0; }
//*************************************************************************** bool Kwave::MP3Decoder::parseID3Tags(ID3_Tag &tag) { if (tag.NumFrames() < 1) return true; // no tags, nothing to do QDate creation_date; QTime creation_time; int year = -1; int month = -1; int day = -1; ID3_Tag::Iterator *it = tag.CreateIterator(); ID3_Frame *frame = 0; Kwave::FileInfo info(metaData()); while (it && (frame = it->GetNext())) { const ID3_FrameID id = frame->GetID(); const Kwave::FileProperty property = m_property_map.property(id); const ID3_PropertyMap::Encoding encoding = m_property_map.encoding(id); switch (encoding) { case ID3_PropertyMap::ENC_TEXT_PARTINSET: { QString s = parseId3Frame2String(frame); int cd = 0; int cds = 0; if (s.contains(QLatin1Char('/'))) { int i = s.indexOf(QLatin1Char('/')); cd = s.left(i).toInt(); cds = s.mid(i + 1).toInt(); } else { cd = s.toInt(); } if (cd > 0) info.set(Kwave::INF_CD , QVariant(cd)); if (cds > 0) info.set(Kwave::INF_CDS, QVariant(cds)); break; } case ID3_PropertyMap::ENC_TRACK_NUM: { QString s = parseId3Frame2String(frame); int track = 0; int tracks = 0; if (s.contains(QLatin1Char('/'))) { int i = s.indexOf(QLatin1Char('/')); track = s.left(i).toInt(); tracks = s.mid(i + 1).toInt(); } else { track = s.toInt(); } if (track > 0) info.set(Kwave::INF_TRACK , QVariant(track)); if (tracks > 0) info.set(Kwave::INF_TRACKS, QVariant(tracks)); break; } case ID3_PropertyMap::ENC_TERMS_OF_USE: // the same as ENC_COMMENT, but without "Description" /* FALLTHROUGH */ case ID3_PropertyMap::ENC_COMMENT: { QString s = parseId3Frame2String(frame); // optionally prepend language char *lang = ID3_GetString(frame, ID3FN_LANGUAGE); if (lang) { s = _("[") + _(lang) + _("] ") + s; ID3_FreeString(lang); } // append to already existing tag, separated by a slash if (info.contains(property)) s = info.get(property).toString() + _(" / ") + s; info.set(property, QVariant(s)); break; } case ID3_PropertyMap::ENC_GENRE_TYPE: { QString s = parseId3Frame2String(frame); int genre = Kwave::GenreType::fromID3(s); if (genre >= 0) s = Kwave::GenreType::name(genre, false); info.set(property, QVariant(s)); break; } case ID3_PropertyMap::ENC_LENGTH: { // length in ms -> convert this to samples QString s = parseId3Frame2String(frame); const double rate = info.rate(); bool ok = false; const double ms = s.toDouble(&ok) + 0.5; if (ok && (rate > 0)) { // NOTE: this overwrites the length found in the header! sample_index_t length = static_cast<sample_index_t>( (rate * ms) / 1000.0); info.setLength(length); } break; } case ID3_PropertyMap::ENC_TEXT_TIMESTAMP: { if (!creation_date.isValid()) { QString s = parseId3Frame2String(frame); switch (id) { case ID3FID_RECORDINGDATES: // should be a ISO 8601 timestamp or similar s = Kwave::string2date(s); if (s.length()) creation_date = QDate::fromString(s, Qt::ISODate); break; case ID3FID_DATE: { // DDMM unsigned int ddmm = s.toUInt(); day = ddmm / 100; month = ddmm % 100; break; } case ID3FID_YEAR: /* FALLTHROUGH */ case ID3FID_ORIGYEAR: // YYYY year = s.toUInt(); break; default: break; } } if (creation_time.isValid()) { switch (id) { case ID3FID_TIME: creation_time = QTime::fromString(_("hhmm")); break; default: break; } } break; } case ID3_PropertyMap::ENC_TEXT_SLASH: { // append to already existing tag, separated by a slash QString s = parseId3Frame2String(frame); if (info.contains(property)) s = info.get(property).toString() + _(" / ") + s; info.set(property, QVariant(s)); break; } case ID3_PropertyMap::ENC_TEXT_URL: /* FALLTHROUGH */ case ID3_PropertyMap::ENC_TEXT: info.set(property, QVariant(parseId3Frame2String(frame))); break; case ID3_PropertyMap::ENC_NONE: /* FALLTHROUGH */ default: { QString s = parseId3Frame2String(frame); qWarning("unsupported ID3 tag: %d, descr: '%s', text: '%s'", id, frame->GetDescription(), DBG(s)); break; } } } /* * try to build a valid creation date/time */ if (!creation_date.isValid()) { // no complete creation date - try to reassemble from found y/m/d creation_date = QDate(year, month, day); } if (creation_date.isValid() && creation_time.isValid()) { // full date + time QDateTime dt(creation_date, creation_time); info.set(Kwave::INF_CREATION_DATE, dt.toString( _("yyyy-MM-ddTHH:mm:ss"))); } else if (creation_date.isValid()) { // date without time info.set(Kwave::INF_CREATION_DATE, creation_date.toString( _("yyyy-MM-dd"))); } else if (year > 0) { // only year creation_date = QDate(year, 1, 1); info.set(Kwave::INF_CREATION_DATE, creation_date.toString(_("yyyy"))); } metaData().replace(Kwave::MetaDataList(info)); return true; }
//*************************************************************************** bool Kwave::MP3Decoder::open(QWidget *widget, QIODevice &src) { qDebug("MP3Decoder::open()"); metaData().clear(); Q_ASSERT(!m_source); if (m_source) qWarning("MP3Decoder::open(), already open !"); /* open the file in readonly mode with seek enabled */ if (src.isSequential()) return false; if (!src.open(QIODevice::ReadOnly)) { qWarning("unable to open source in read-only mode!"); return false; } /* read all available ID3 tags */ ID3_Tag tag; ID3_QIODeviceReader adapter(src); tag.Link(adapter, static_cast<flags_t>(ID3TT_ALL)); qDebug("NumFrames = %u", Kwave::toUint(tag.NumFrames())); /** @bug: id3lib crashes in this line on some MP3 files */ if (tag.GetSpec() != ID3V2_UNKNOWN) { qDebug("Size = %u", Kwave::toUint(tag.Size())); } qDebug("HasLyrics = %d", tag.HasLyrics()); qDebug("HasV1Tag = %d", tag.HasV1Tag()); qDebug("HasV2Tag = %d", tag.HasV2Tag()); m_prepended_bytes = tag.GetPrependedBytes(); m_appended_bytes = tag.GetAppendedBytes(); qDebug("prepended=%lu, appended=%lu", m_prepended_bytes, m_appended_bytes); const Mp3_Headerinfo *mp3hdr = tag.GetMp3HeaderInfo(); if (!mp3hdr) { Kwave::MessageBox::sorry(widget, i18n("The opened file is no MPEG file or it is damaged.\n" "No header information has been found.")); return false; } /* parse the MP3 header */ if (!parseMp3Header(*mp3hdr, widget)) return false; /* parse the ID3 tags */ if (!parseID3Tags(tag)) return false; /* accept the source */ m_source = &src; Kwave::FileInfo info(metaData()); info.set(Kwave::INF_MIMETYPE, _("audio/mpeg")); metaData().replace(Kwave::MetaDataList(info)); // allocate a transfer buffer with 128 kB if (m_buffer) free(m_buffer); m_buffer_size = (128 << 10); m_buffer = static_cast<unsigned char *>(malloc(m_buffer_size)); if (!m_buffer) return false; // out of memory :-( return true; }
int main(int argc, char* argv[]) { // Parse any command-line options. namespace po = boost::program_options; po::options_description desc("Allowed options"); desc.add_options() ("help", "show help") ("debug-httpd", po::value<bool>(&mp3d_debug_httpd), "show httpd debug output") ("root", po::value<std::string>(&mp3d_music_root), "root of file system mp3 tree") ("port", po::value<int>(&mp3d_port), "httpd port number") ; po::variables_map args; po::store(po::parse_command_line(argc, argv, desc), args); po::notify(args); if (args.count("help")) { std::cout << desc << std::endl; return 1; } // Index all the mp3s. Paths paths; find_mp3_files(mp3d_music_root, paths); std::cerr << ".mp3 files found: " << paths.size() << std::endl; int old_percentage = -1; size_t id = 0; for (Paths::const_iterator it = paths.begin(); it != paths.end(); ++it) { Mp3Info mp3; mp3.filename = (*it).string(); const ID3_Tag tag(mp3.filename.c_str()); ID3_Tag::ConstIterator* it = tag.CreateIterator(); for (size_t i = 0; i < tag.NumFrames(); ++i) { const ID3_Frame* frame = it->GetNext(); if (frame != 0) { std::string* dst; switch (frame->GetID()) { case ID3FID_ALBUM: dst = &mp3.album; break; case ID3FID_LEADARTIST: dst = &mp3.artist; break; case ID3FID_TITLE: dst = &mp3.title; break; default: continue; } char* text = ID3_GetString(frame, ID3FN_TEXT); dst->assign(text); ID3_FreeString(text); } } // FIXME: maybe a hash, to enable bookmarks? mp3.id = id++; all_mp3s.push_back(mp3); // Show progress. Not really useful when we're not debugging. // FIXME: start the web server straight away, and say "Indexing..." there. const int new_percentage = (100*all_mp3s.size())/paths.size(); if (new_percentage != old_percentage) { std::cout << "\rScanned: " << new_percentage << "%" << std::flush; old_percentage = new_percentage; } } std::cout << "\r.mp3 files scanned: " << all_mp3s.size() << std::endl; // Set up the static files we need to serve. read_static_file("/static/add.png", "/usr/share/icons/gnome/16x16/actions/gtk-add.png"); read_static_file("/static/play.png", "/usr/share/icons/gnome/16x16/actions/gtk-media-play-ltr.png"); read_static_file("/static/remove.png", "/usr/share/icons/gnome/16x16/actions/gtk-remove.png"); static_file_map["/static/site.css"] = make_css(); // Start the mp3 player thread. boost::thread mp3_player_thread(mp3_play_loop); // Start the HTTP server. std::cerr << "Starting HTTP server on port " << mp3d_port << "..." << std::endl; const int mhd_flags = MHD_USE_SELECT_INTERNALLY; MHD_Daemon* daemon = MHD_start_daemon(mhd_flags, mp3d_port, 0, 0, &handle_request, 0, MHD_OPTION_END); if (daemon == 0) { fail("MHD_start_daemon failed!"); } getchar(); // Wait for the user to hit enter. MHD_stop_daemon(daemon); //mp3_player_thread.join(); return 0; }