/*************************************************************************** copyright : (C) 2004-2007 by Robby Stephenson email : robby@periapsis.org ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of version 2 of the GNU General Public License as * * published by the Free Software Foundation; * * * ***************************************************************************/ #include #include "audiofileimporter.h" #include "../collections/musiccollection.h" #include "../entry.h" #include "../field.h" #include "../latin1literal.h" #include "../imagefactory.h" #include "../tellico_utils.h" #include "../tellico_kernel.h" #include "../progressmanager.h" #include "../tellico_debug.h" #ifdef HAVE_TAGLIB #include #include #include #include #include #include #include #endif #include #include #include #include #include #include #include #include using Tellico::Import::AudioFileImporter; AudioFileImporter::AudioFileImporter(const KURL& url_) : Tellico::Import::Importer(url_) , m_coll(0) , m_widget(0) , m_cancelled(false) { } bool AudioFileImporter::canImport(int type) const { return type == Data::Collection::Album; } Tellico::Data::CollPtr AudioFileImporter::collection() { #ifndef HAVE_TAGLIB return 0; #else if(m_coll) { return m_coll; } ProgressItem& item = ProgressManager::self()->newProgressItem(this, i18n("Scanning audio files..."), true); item.setTotalSteps(100); connect(&item, TQT_SIGNAL(signalCancelled(ProgressItem*)), TQT_SLOT(slotCancel())); ProgressItem::Done done(this); // TODO: allow remote audio file importing TQStringList dirs = url().path(); if(m_recursive->isChecked()) { dirs += Tellico::findAllSubDirs(dirs[0]); } if(m_cancelled) { return 0; } const bool showProgress = options() & ImportProgress; TQStringList files; for(TQStringList::ConstIterator it = dirs.begin(); !m_cancelled && it != dirs.end(); ++it) { if((*it).isEmpty()) { continue; } TQDir dir(*it); dir.setFilter(TQDir::Files | TQDir::Readable | TQDir::Hidden); // hidden since I want directory files const TQStringList list = dir.entryList(); for(TQStringList::ConstIterator it2 = list.begin(); it2 != list.end(); ++it2) { files += dir.absFilePath(*it2); } // kapp->processEvents(); not needed ? } if(m_cancelled) { return 0; } item.setTotalSteps(files.count()); const TQString title = TQString::tqfromLatin1("title"); const TQString artist = TQString::tqfromLatin1("artist"); const TQString year = TQString::tqfromLatin1("year"); const TQString genre = TQString::tqfromLatin1("genre"); const TQString track = TQString::tqfromLatin1("track"); const TQString comments = TQString::tqfromLatin1("comments"); const TQString file = TQString::tqfromLatin1("file"); m_coll = new Data::MusicCollection(true); const bool addFile = m_addFilePath->isChecked(); const bool addBitrate = m_addBitrate->isChecked(); Data::FieldPtr f; if(addFile) { f = m_coll->fieldByName(file); if(!f) { f = new Data::Field(file, i18n("Files"), Data::Field::Table); m_coll->addField(f); } f->setProperty(TQString::tqfromLatin1("column1"), i18n("Files")); if(addBitrate) { f->setProperty(TQString::tqfromLatin1("columns"), TQChar('2')); f->setProperty(TQString::tqfromLatin1("column2"), i18n("Bitrate")); } else { f->setProperty(TQString::tqfromLatin1("columns"), TQChar('1')); } } TQMap albumMap; TQStringList directoryFiles; const uint stepSize = TQMAX(static_cast(1), files.count() / 100); bool changeTrackTitle = true; uint j = 0; for(TQStringList::ConstIterator it = files.begin(); !m_cancelled && it != files.end(); ++it, ++j) { TagLib::FileRef f(TQFile::encodeName(*it)); if(f.isNull() || !f.tag()) { if((*it).endsWith(TQString::tqfromLatin1("/.directory"))) { directoryFiles += *it; } continue; } TagLib::Tag* tag = f.tag(); TQString album = TQString(TStringToQString(tag->album())).stripWhiteSpace(); if(album.isEmpty()) { // can't do anything since tellico entries are by album kdWarning() << "Skipping: no album listed for " << *it << endl; continue; } int disc = discNumber(f); if(disc > 1 && !m_coll->hasField(TQString::tqfromLatin1("track%1").tqarg(disc))) { Data::FieldPtr f2 = new Data::Field(TQString::tqfromLatin1("track%1").tqarg(disc), i18n("Tracks (Disc %1)").tqarg(disc), Data::Field::Table); f2->setFormatFlag(Data::Field::FormatTitle); f2->setProperty(TQString::tqfromLatin1("columns"), TQChar('3')); f2->setProperty(TQString::tqfromLatin1("column1"), i18n("Title")); f2->setProperty(TQString::tqfromLatin1("column2"), i18n("Artist")); f2->setProperty(TQString::tqfromLatin1("column3"), i18n("Length")); m_coll->addField(f2); if(changeTrackTitle) { Data::FieldPtr newTrack = new Data::Field(*m_coll->fieldByName(track)); newTrack->setTitle(i18n("Tracks (Disc %1)").tqarg(1)); m_coll->modifyField(newTrack); changeTrackTitle = false; } } bool various = false; bool exists = true; Data::EntryPtr entry = 0; if(!(entry = albumMap[album.lower()])) { entry = new Data::Entry(m_coll); albumMap.insert(album.lower(), entry); exists = false; } // album entries use the album name as the title entry->setField(title, album); TQString a = TQString(TStringToQString(tag->artist())).stripWhiteSpace(); if(!a.isEmpty()) { if(exists && entry->field(artist).lower() != a.lower()) { various = true; entry->setField(artist, i18n("(Various)")); } else { entry->setField(artist, a); } } if(tag->year() > 0) { entry->setField(year, TQString::number(tag->year())); } if(!tag->genre().isEmpty()) { entry->setField(genre, TQString(TStringToQString(tag->genre())).stripWhiteSpace()); } if(!tag->title().isEmpty()) { int trackNum = tag->track(); if(trackNum <= 0) { // try to figure out track number from file name TQFileInfo f(*it); TQString fileName = f.baseName(); TQString numString; int i = 0; const int len = fileName.length(); while(fileName[i].isNumber() && i < len) { i++; } if(i == 0) { // does not start with a number i = len - 1; while(i >= 0 && fileName[i].isNumber()) { i--; } // file name ends with a number if(i != len - 1) { numString = fileName.mid(i + 1); } } else { numString = fileName.mid(0, i); } bool ok; int number = numString.toInt(&ok); if(ok) { trackNum = number; } } if(trackNum > 0) { TQString t = TQString(TStringToQString(tag->title())).stripWhiteSpace(); t += "::" + a; const int len = f.audioProperties()->length(); if(len > 0) { t += "::" + Tellico::minutes(len); } TQString realTrack = disc > 1 ? track + TQString::number(disc) : track; entry->setField(realTrack, insertValue(entry->field(realTrack), t, trackNum)); if(addFile) { TQString fileValue = *it; if(addBitrate) { fileValue += "::" + TQString::number(f.audioProperties()->bitrate()); } entry->setField(file, insertValue(entry->field(file), fileValue, trackNum)); } } else { myDebug() << *it << " contains no track number and track number cannot be determined, so the track is not imported." << endl; } } else { myDebug() << *it << " has an empty title, so the track is not imported." << endl; } if(!tag->comment().stripWhiteSpace().isEmpty()) { TQString c = entry->field(comments); if(!c.isEmpty()) { c += TQString::tqfromLatin1("
"); } if(!tag->title().isEmpty()) { c += TQString::tqfromLatin1("") + TQString(TStringToQString(tag->title())).stripWhiteSpace() + TQString::tqfromLatin1(" - "); } c += TQString(TStringToQString(tag->comment().stripWhiteSpace())).stripWhiteSpace(); entry->setField(comments, c); } if(!exists) { m_coll->addEntries(entry); } if(showProgress && j%stepSize == 0) { ProgressManager::self()->setTotalSteps(this, files.count() + directoryFiles.count()); ProgressManager::self()->setProgress(this, j); kapp->processEvents(); } /* kdDebug() << "-- TAG --" << endl; kdDebug() << "title - \"" << tag->title().to8Bit() << "\"" << endl; kdDebug() << "artist - \"" << tag->artist().to8Bit() << "\"" << endl; kdDebug() << "album - \"" << tag->album().to8Bit() << "\"" << endl; kdDebug() << "year - \"" << tag->year() << "\"" << endl; kdDebug() << "comment - \"" << tag->comment().to8Bit() << "\"" << endl; kdDebug() << "track - \"" << tag->track() << "\"" << endl; kdDebug() << "genre - \"" << tag->genre().to8Bit() << "\"" << endl;*/ } if(m_cancelled) { m_coll = 0; return 0; } TQTextStream ts; TQRegExp iconRx(TQString::tqfromLatin1("Icon\\s*=\\s*(.*)")); for(TQStringList::ConstIterator it = directoryFiles.begin(); !m_cancelled && it != directoryFiles.end(); ++it, ++j) { TQFile file(*it); if(!file.open(IO_ReadOnly)) { continue; } ts.unsetDevice(); ts.setDevice(TQT_TQIODEVICE(&file)); for(TQString line = ts.readLine(); !line.isNull(); line = ts.readLine()) { if(!iconRx.exactMatch(line)) { continue; } TQDir thisDir(*it); thisDir.cdUp(); TQFileInfo fi(thisDir, iconRx.cap(1)); Data::EntryPtr entry = albumMap[thisDir.dirName()]; if(!entry) { continue; } KURL u; u.setPath(fi.absFilePath()); TQString id = ImageFactory::addImage(u, true); if(!id.isEmpty()) { entry->setField(TQString::tqfromLatin1("cover"), id); } break; } if(showProgress && j%stepSize == 0) { ProgressManager::self()->setProgress(this, j); kapp->processEvents(); } } if(m_cancelled) { m_coll = 0; return 0; } return m_coll; #endif } TQWidget* AudioFileImporter::widget(TQWidget* parent_, const char* name_) { if(m_widget) { return m_widget; } m_widget = new TQWidget(parent_, name_); TQVBoxLayout* l = new TQVBoxLayout(m_widget); TQVGroupBox* box = new TQVGroupBox(i18n("Audio File Options"), m_widget); m_recursive = new TQCheckBox(i18n("Recursive &folder search"), box); TQWhatsThis::add(m_recursive, i18n("If checked, folders are recursively searched for audio files.")); // by default, make it checked m_recursive->setChecked(true); m_addFilePath = new TQCheckBox(i18n("Include file &location"), box); TQWhatsThis::add(m_addFilePath, i18n("If checked, the file names for each track are added to the entries.")); m_addFilePath->setChecked(false); connect(m_addFilePath, TQT_SIGNAL(toggled(bool)), TQT_SLOT(slotAddFileToggled(bool))); m_addBitrate = new TQCheckBox(i18n("Include &bitrate"), box); TQWhatsThis::add(m_addBitrate, i18n("If checked, the bitrate for each track is added to the entries.")); m_addBitrate->setChecked(false); m_addBitrate->setEnabled(false); l->addWidget(box); l->addStretch(1); return m_widget; } // pos_ is NOT zero-indexed! TQString AudioFileImporter::insertValue(const TQString& str_, const TQString& value_, uint pos_) { TQStringList list = Data::Field::split(str_, true); for(uint i = list.count(); i < pos_; ++i) { list += TQString(); } if(!list[pos_-1].isNull()) { myDebug() << "AudioFileImporter::insertValue() - overwriting track " << pos_ << endl; myDebug() << "*** Old value: " << list[pos_-1] << endl; myDebug() << "*** New value: " << value_ << endl; } list[pos_-1] = value_; return list.join(TQString::tqfromLatin1("; ")); } void AudioFileImporter::slotCancel() { m_cancelled = true; } void AudioFileImporter::slotAddFileToggled(bool on_) { m_addBitrate->setEnabled(on_); if(!on_) { m_addBitrate->setChecked(false); } } int AudioFileImporter::discNumber(const TagLib::FileRef& ref_) const { // default to 1 unless otherwise int num = 1; #ifdef HAVE_TAGLIB TQString disc; if(TagLib::MPEG::File* file = dynamic_cast(ref_.file())) { if(file->ID3v2Tag() && !file->ID3v2Tag()->frameListMap()["TPOS"].isEmpty()) { disc = TQString(TStringToQString(file->ID3v2Tag()->frameListMap()["TPOS"].front()->toString())).stripWhiteSpace(); } } else if(TagLib::Ogg::Vorbis::File* file = dynamic_cast(ref_.file())) { if(file->tag() && !file->tag()->fieldListMap()["DISCNUMBER"].isEmpty()) { disc = TQString(TStringToQString(file->tag()->fieldListMap()["DISCNUMBER"].front())).stripWhiteSpace(); } } else if(TagLib::FLAC::File* file = dynamic_cast(ref_.file())) { if(file->xiphComment() && !file->xiphComment()->fieldListMap()["DISCNUMBER"].isEmpty()) { disc = TQString(TStringToQString(file->xiphComment()->fieldListMap()["DISCNUMBER"].front())).stripWhiteSpace(); } } if(!disc.isEmpty()) { int pos = disc.find('/'); int n; bool ok; if(pos == -1) { n = disc.toInt(&ok); } else { n = disc.left(pos).toInt(&ok); } if(ok && n > 0) { num = n; } } #endif return num; } #include "audiofileimporter.moc"