diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-01 19:17:32 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-01 19:17:32 +0000 |
commit | e38d2351b83fa65c66ccde443777647ef5cb6cff (patch) | |
tree | 1897fc20e9f73a81c520a5b9f76f8ed042124883 /src/translators/audiofileimporter.cpp | |
download | tellico-e38d2351b83fa65c66ccde443777647ef5cb6cff.tar.gz tellico-e38d2351b83fa65c66ccde443777647ef5cb6cff.zip |
Added KDE3 version of Tellico
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/tellico@1097620 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'src/translators/audiofileimporter.cpp')
-rw-r--r-- | src/translators/audiofileimporter.cpp | 424 |
1 files changed, 424 insertions, 0 deletions
diff --git a/src/translators/audiofileimporter.cpp b/src/translators/audiofileimporter.cpp new file mode 100644 index 0000000..f825964 --- /dev/null +++ b/src/translators/audiofileimporter.cpp @@ -0,0 +1,424 @@ +/*************************************************************************** + copyright : (C) 2004-2007 by Robby Stephenson + email : [email protected] + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 <config.h> + +#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 <taglib/fileref.h> +#include <taglib/tag.h> +#include <taglib/id3v2tag.h> +#include <taglib/mpegfile.h> +#include <taglib/vorbisfile.h> +#include <taglib/flacfile.h> +#include <taglib/audioproperties.h> +#endif + +#include <klocale.h> +#include <kapplication.h> + +#include <qlabel.h> +#include <qlayout.h> +#include <qvgroupbox.h> +#include <qcheckbox.h> +#include <qdir.h> +#include <qwhatsthis.h> + +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, SIGNAL(signalCancelled(ProgressItem*)), SLOT(slotCancel())); + ProgressItem::Done done(this); + + // TODO: allow remote audio file importing + QStringList dirs = url().path(); + if(m_recursive->isChecked()) { + dirs += Tellico::findAllSubDirs(dirs[0]); + } + + if(m_cancelled) { + return 0; + } + + const bool showProgress = options() & ImportProgress; + + QStringList files; + for(QStringList::ConstIterator it = dirs.begin(); !m_cancelled && it != dirs.end(); ++it) { + if((*it).isEmpty()) { + continue; + } + + QDir dir(*it); + dir.setFilter(QDir::Files | QDir::Readable | QDir::Hidden); // hidden since I want directory files + const QStringList list = dir.entryList(); + for(QStringList::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 QString title = QString::fromLatin1("title"); + const QString artist = QString::fromLatin1("artist"); + const QString year = QString::fromLatin1("year"); + const QString genre = QString::fromLatin1("genre"); + const QString track = QString::fromLatin1("track"); + const QString comments = QString::fromLatin1("comments"); + const QString file = QString::fromLatin1("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(QString::fromLatin1("column1"), i18n("Files")); + if(addBitrate) { + f->setProperty(QString::fromLatin1("columns"), QChar('2')); + f->setProperty(QString::fromLatin1("column2"), i18n("Bitrate")); + } else { + f->setProperty(QString::fromLatin1("columns"), QChar('1')); + } + } + + QMap<QString, Data::EntryPtr> albumMap; + + QStringList directoryFiles; + const uint stepSize = QMAX(static_cast<size_t>(1), files.count() / 100); + + bool changeTrackTitle = true; + uint j = 0; + for(QStringList::ConstIterator it = files.begin(); !m_cancelled && it != files.end(); ++it, ++j) { + TagLib::FileRef f(QFile::encodeName(*it)); + if(f.isNull() || !f.tag()) { + if((*it).endsWith(QString::fromLatin1("/.directory"))) { + directoryFiles += *it; + } + continue; + } + + TagLib::Tag* tag = f.tag(); + QString album = 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(QString::fromLatin1("track%1").arg(disc))) { + Data::FieldPtr f2 = new Data::Field(QString::fromLatin1("track%1").arg(disc), + i18n("Tracks (Disc %1)").arg(disc), + Data::Field::Table); + f2->setFormatFlag(Data::Field::FormatTitle); + f2->setProperty(QString::fromLatin1("columns"), QChar('3')); + f2->setProperty(QString::fromLatin1("column1"), i18n("Title")); + f2->setProperty(QString::fromLatin1("column2"), i18n("Artist")); + f2->setProperty(QString::fromLatin1("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)").arg(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); + QString a = 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, QString::number(tag->year())); + } + if(!tag->genre().isEmpty()) { + entry->setField(genre, TStringToQString(tag->genre()).stripWhiteSpace()); + } + + if(!tag->title().isEmpty()) { + int trackNum = tag->track(); + if(trackNum <= 0) { // try to figure out track number from file name + QFileInfo f(*it); + QString fileName = f.baseName(); + QString 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) { + QString t = TStringToQString(tag->title()).stripWhiteSpace(); + t += "::" + a; + const int len = f.audioProperties()->length(); + if(len > 0) { + t += "::" + Tellico::minutes(len); + } + QString realTrack = disc > 1 ? track + QString::number(disc) : track; + entry->setField(realTrack, insertValue(entry->field(realTrack), t, trackNum)); + if(addFile) { + QString fileValue = *it; + if(addBitrate) { + fileValue += "::" + QString::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()) { + QString c = entry->field(comments); + if(!c.isEmpty()) { + c += QString::fromLatin1("<br/>"); + } + if(!tag->title().isEmpty()) { + c += QString::fromLatin1("<em>") + TStringToQString(tag->title()).stripWhiteSpace() + QString::fromLatin1("</em> - "); + } + c += 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; + } + + QTextStream ts; + QRegExp iconRx(QString::fromLatin1("Icon\\s*=\\s*(.*)")); + for(QStringList::ConstIterator it = directoryFiles.begin(); !m_cancelled && it != directoryFiles.end(); ++it, ++j) { + QFile file(*it); + if(!file.open(IO_ReadOnly)) { + continue; + } + ts.unsetDevice(); + ts.setDevice(&file); + for(QString line = ts.readLine(); !line.isNull(); line = ts.readLine()) { + if(!iconRx.exactMatch(line)) { + continue; + } + QDir thisDir(*it); + thisDir.cdUp(); + QFileInfo fi(thisDir, iconRx.cap(1)); + Data::EntryPtr entry = albumMap[thisDir.dirName()]; + if(!entry) { + continue; + } + KURL u; + u.setPath(fi.absFilePath()); + QString id = ImageFactory::addImage(u, true); + if(!id.isEmpty()) { + entry->setField(QString::fromLatin1("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 +} + +QWidget* AudioFileImporter::widget(QWidget* parent_, const char* name_) { + if(m_widget) { + return m_widget; + } + + m_widget = new QWidget(parent_, name_); + QVBoxLayout* l = new QVBoxLayout(m_widget); + + QVGroupBox* box = new QVGroupBox(i18n("Audio File Options"), m_widget); + + m_recursive = new QCheckBox(i18n("Recursive &folder search"), box); + QWhatsThis::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 QCheckBox(i18n("Include file &location"), box); + QWhatsThis::add(m_addFilePath, i18n("If checked, the file names for each track are added to the entries.")); + m_addFilePath->setChecked(false); + connect(m_addFilePath, SIGNAL(toggled(bool)), SLOT(slotAddFileToggled(bool))); + + m_addBitrate = new QCheckBox(i18n("Include &bitrate"), box); + QWhatsThis::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! +QString AudioFileImporter::insertValue(const QString& str_, const QString& value_, uint pos_) { + QStringList list = Data::Field::split(str_, true); + for(uint i = list.count(); i < pos_; ++i) { + list += QString::null; + } + 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(QString::fromLatin1("; ")); +} + +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 + QString disc; + if(TagLib::MPEG::File* file = dynamic_cast<TagLib::MPEG::File*>(ref_.file())) { + if(file->ID3v2Tag() && !file->ID3v2Tag()->frameListMap()["TPOS"].isEmpty()) { + disc = TStringToQString(file->ID3v2Tag()->frameListMap()["TPOS"].front()->toString()).stripWhiteSpace(); + } + } else if(TagLib::Ogg::Vorbis::File* file = dynamic_cast<TagLib::Ogg::Vorbis::File*>(ref_.file())) { + if(file->tag() && !file->tag()->fieldListMap()["DISCNUMBER"].isEmpty()) { + disc = TStringToQString(file->tag()->fieldListMap()["DISCNUMBER"].front()).stripWhiteSpace(); + } + } else if(TagLib::FLAC::File* file = dynamic_cast<TagLib::FLAC::File*>(ref_.file())) { + if(file->xiphComment() && !file->xiphComment()->fieldListMap()["DISCNUMBER"].isEmpty()) { + disc = 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" |