From 4b381e00fdaf7f6e0eaa6ce65fb311828e95746f Mon Sep 17 00:00:00 2001 From: David Sansome Date: Sun, 29 Aug 2010 19:22:21 +0000 Subject: [PATCH] Transcode files when copying them to devices --- src/core/musicstorage.h | 11 +++ src/core/organise.cpp | 132 +++++++++++++++++++++++--- src/core/organise.h | 29 +++++- src/devices/connecteddevice.cpp | 12 +++ src/devices/connecteddevice.h | 3 +- src/devices/devicedatabasebackend.cpp | 4 +- src/devices/devicedatabasebackend.h | 12 +-- src/devices/devicemanager.cpp | 6 +- src/devices/devicemanager.h | 4 +- src/devices/deviceproperties.cpp | 34 ++----- src/devices/mtpdevice.cpp | 8 ++ src/transcoder/transcoder.cpp | 17 ++++ src/transcoder/transcoder.h | 1 + 13 files changed, 214 insertions(+), 59 deletions(-) diff --git a/src/core/musicstorage.h b/src/core/musicstorage.h index 33217f9fd..bc1813b18 100644 --- a/src/core/musicstorage.h +++ b/src/core/musicstorage.h @@ -36,6 +36,13 @@ public: Role_FreeSpace, }; + // Values are saved in the database - don't change + enum TranscodeMode { + Transcode_Always = 1, + Transcode_Never = 2, + Transcode_Unsupported = 3, + }; + typedef boost::function ProgressFunction; struct CopyJob { @@ -53,6 +60,10 @@ public: virtual QString LocalPath() const { return QString(); } + virtual TranscodeMode GetTranscodeMode() const { return Transcode_Never; } + virtual Song::FileType GetTranscodeFormat() const { return Song::Type_Unknown; } + virtual QList SupportedFiletypes() { return QList(); } + virtual void StartCopy() {} virtual bool CopyToStorage(const CopyJob& job) = 0; virtual void FinishCopy(bool success) {} diff --git a/src/core/organise.cpp b/src/core/organise.cpp index 661b83567..773e2ee33 100644 --- a/src/core/organise.cpp +++ b/src/core/organise.cpp @@ -33,18 +33,24 @@ Organise::Organise(TaskManager* task_manager, const QStringList& files, bool eject_after) : thread_(NULL), task_manager_(task_manager), + transcoder_(new Transcoder(this)), destination_(destination), format_(format), copy_(copy), overwrite_(overwrite), - files_(files), eject_after_(eject_after), + task_count_(files.count()), + transcode_suffix_(1), started_(false), task_id_(0), progress_(0), song_progress_(0) { original_thread_ = thread(); + + foreach (const QString& filename, files) { + tasks_pending_ << Task(filename); + } } void Organise::Start() { @@ -56,6 +62,7 @@ void Organise::Start() { thread_ = new QThread; connect(thread_, SIGNAL(started()), SLOT(ProcessSomeFiles())); + connect(transcoder_, SIGNAL(JobComplete(QString,bool)), SLOT(FileTranscoded(QString,bool))); moveToThread(thread_); thread_->start(); @@ -63,12 +70,21 @@ void Organise::Start() { void Organise::ProcessSomeFiles() { if (!started_) { + transcode_temp_name_.open(); + supported_filetypes_ = destination_->SupportedFiletypes(); + destination_->StartCopy(); started_ = true; } // None left? - if (progress_ >= files_.count()) { + if (tasks_pending_.isEmpty()) { + if (!tasks_transcoding_.isEmpty()) { + // Just wait - FileTranscoded will start us off again in a little while + qDebug() << "Waiting for transcoding jobs"; + return; + } + UpdateProgress(); destination_->FinishCopy(files_with_errors_.isEmpty()); @@ -89,34 +105,75 @@ void Organise::ProcessSomeFiles() { return; } - QDir dir; - // We process files in batches so we can be cancelled part-way through. - - const int n = qMin(files_.count(), progress_ + kBatchSize); - for ( ; progress_AddJob(task.filename_, preset, task.transcoded_filename_); + transcoder_->Start(); + continue; + } + } + MusicStorage::CopyJob job; - job.source_ = filename; + job.source_ = task.transcoded_filename_.isEmpty() ? + task.filename_ : task.transcoded_filename_; job.destination_ = format_.GetFilenameForSong(song); job.metadata_ = song; job.overwrite_ = overwrite_; @@ -124,14 +181,47 @@ void Organise::ProcessSomeFiles() { job.progress_ = boost::bind(&Organise::SetSongProgress, this, _1); if (!destination_->CopyToStorage(job)) { - files_with_errors_ << filename; + files_with_errors_ << task.filename_; } + + progress_++; } SetSongProgress(0); QTimer::singleShot(0, this, SLOT(ProcessSomeFiles())); } +Song::FileType Organise::CheckTranscode(Song::FileType original_type) const { + if (original_type == Song::Type_Stream) + return Song::Type_Unknown; + + const MusicStorage::TranscodeMode mode = destination_->GetTranscodeMode(); + const Song::FileType format = destination_->GetTranscodeFormat(); + + switch (mode) { + case MusicStorage::Transcode_Never: + return Song::Type_Unknown; + + case MusicStorage::Transcode_Always: + if (original_type == format) + return Song::Type_Unknown; + return format; + + case MusicStorage::Transcode_Unsupported: + if (supported_filetypes_.isEmpty() || supported_filetypes_.contains(original_type)) + return Song::Type_Unknown; + + if (format != Song::Type_Unknown) + return format; + + // The user hasn't visited the device properties page yet to set a + // preferred format for the device, so we have to pick the best + // available one. + return Transcoder::PickBestFormat(supported_filetypes_); + } + return Song::Type_Unknown; +} + void Organise::SetSongProgress(float progress) { song_progress_ = qBound(0, int(progress * 100), 99); UpdateProgress(); @@ -139,6 +229,18 @@ void Organise::SetSongProgress(float progress) { void Organise::UpdateProgress() { const int progress = progress_ * 100 + song_progress_; - const int total = files_.count() * 100; + const int total = task_count_ * 100; task_manager_->SetTaskProgress(task_id_, progress, total); } + +void Organise::FileTranscoded(const QString& filename, bool success) { + qDebug() << "File finished" << filename << success; + + Task task = tasks_transcoding_.take(filename); + if (!success) { + files_with_errors_ << filename; + } else { + tasks_pending_ << task; + } + QTimer::singleShot(0, this, SLOT(ProcessSomeFiles())); +} diff --git a/src/core/organise.h b/src/core/organise.h index 969590e45..ad4e950b1 100644 --- a/src/core/organise.h +++ b/src/core/organise.h @@ -18,10 +18,12 @@ #define ORGANISE_H #include +#include #include #include "organiseformat.h" +#include "transcoder/transcoder.h" class MusicStorage; class TaskManager; @@ -44,20 +46,41 @@ signals: private slots: void ProcessSomeFiles(); - void SetSongProgress(float progress); - void UpdateProgress(); + void FileTranscoded(const QString& filename, bool success); private: + void SetSongProgress(float progress); + void UpdateProgress(); + Song::FileType CheckTranscode(Song::FileType original_type) const; + +private: + struct Task { + Task(const QString& filename = QString()) : filename_(filename) {} + + QString filename_; + QString transcoded_filename_; + QString new_extension_; + Song::FileType new_filetype_; + }; + QThread* thread_; QThread* original_thread_; TaskManager* task_manager_; + Transcoder* transcoder_; boost::shared_ptr destination_; + QList supported_filetypes_; const OrganiseFormat format_; const bool copy_; const bool overwrite_; - QStringList files_; const bool eject_after_; + int task_count_; + + QTemporaryFile transcode_temp_name_; + int transcode_suffix_; + + QList tasks_pending_; + QMap tasks_transcoding_; bool started_; diff --git a/src/devices/connecteddevice.cpp b/src/devices/connecteddevice.cpp index af822d007..98e4a2f98 100644 --- a/src/devices/connecteddevice.cpp +++ b/src/devices/connecteddevice.cpp @@ -94,3 +94,15 @@ void ConnectedDevice::FinishCopy(bool) { void ConnectedDevice::FinishDelete(bool) { lister_->UpdateDeviceFreeSpace(unique_id_); } + +MusicStorage::TranscodeMode ConnectedDevice::GetTranscodeMode() const { + int index = manager_->FindDeviceById(unique_id_); + return MusicStorage::TranscodeMode( + manager_->index(index).data(DeviceManager::Role_TranscodeMode).toInt()); +} + +Song::FileType ConnectedDevice::GetTranscodeFormat() const { + int index = manager_->FindDeviceById(unique_id_); + return Song::FileType( + manager_->index(index).data(DeviceManager::Role_TranscodeFormat).toInt()); +} diff --git a/src/devices/connecteddevice.h b/src/devices/connecteddevice.h index 0b290aa26..eabab8ed7 100644 --- a/src/devices/connecteddevice.h +++ b/src/devices/connecteddevice.h @@ -44,7 +44,8 @@ public: virtual void Init() = 0; - virtual QList SupportedFiletypes() { return QList(); } + virtual TranscodeMode GetTranscodeMode() const; + virtual Song::FileType GetTranscodeFormat() const; DeviceLister* lister() const { return lister_; } QString unique_id() const { return unique_id_; } diff --git a/src/devices/devicedatabasebackend.cpp b/src/devices/devicedatabasebackend.cpp index 9d979275b..de401d831 100644 --- a/src/devices/devicedatabasebackend.cpp +++ b/src/devices/devicedatabasebackend.cpp @@ -52,7 +52,7 @@ DeviceDatabaseBackend::DeviceList DeviceDatabaseBackend::GetAllDevices() { dev.friendly_name_ = q.value(2).toString(); dev.size_ = q.value(3).toLongLong(); dev.icon_name_ = q.value(4).toString(); - dev.transcode_mode_ = TranscodeMode(q.value(5).toInt()); + dev.transcode_mode_ = MusicStorage::TranscodeMode(q.value(5).toInt()); dev.transcode_format_ = Song::FileType(q.value(6).toInt()); ret << dev; } @@ -118,7 +118,7 @@ void DeviceDatabaseBackend::RemoveDevice(int id) { void DeviceDatabaseBackend::SetDeviceOptions(int id, const QString &friendly_name, const QString &icon_name, - TranscodeMode mode, Song::FileType format) { + MusicStorage::TranscodeMode mode, Song::FileType format) { QMutexLocker l(db_->Mutex()); QSqlDatabase db(db_->Connect()); diff --git a/src/devices/devicedatabasebackend.h b/src/devices/devicedatabasebackend.h index b9eca4106..109439ea1 100644 --- a/src/devices/devicedatabasebackend.h +++ b/src/devices/devicedatabasebackend.h @@ -21,6 +21,7 @@ #include +#include "core/musicstorage.h" #include "core/song.h" class Database; @@ -31,13 +32,6 @@ class DeviceDatabaseBackend : public QObject { public: Q_INVOKABLE DeviceDatabaseBackend(QObject* parent = 0); - // Values are saved in the database - don't change - enum TranscodeMode { - Transcode_Always = 1, - Transcode_Never = 2, - Transcode_Unsupported = 3, - }; - struct Device { Device() : id_(-1) {} @@ -47,7 +41,7 @@ public: quint64 size_; QString icon_name_; - TranscodeMode transcode_mode_; + MusicStorage::TranscodeMode transcode_mode_; Song::FileType transcode_format_; }; typedef QList DeviceList; @@ -63,7 +57,7 @@ public: void SetDeviceOptions(int id, const QString& friendly_name, const QString& icon_name, - TranscodeMode mode, Song::FileType format); + MusicStorage::TranscodeMode mode, Song::FileType format); private: boost::shared_ptr db_; diff --git a/src/devices/devicemanager.cpp b/src/devices/devicemanager.cpp index 18f6171f3..aa4404b47 100644 --- a/src/devices/devicemanager.cpp +++ b/src/devices/devicemanager.cpp @@ -60,7 +60,7 @@ const int DeviceManager::kDeviceIconOverlaySize = 16; DeviceManager::DeviceInfo::DeviceInfo() : database_id_(-1), - transcode_mode_(DeviceDatabaseBackend::Transcode_Unsupported), + transcode_mode_(MusicStorage::Transcode_Unsupported), transcode_format_(Song::Type_Unknown), task_percentage_(-1) { @@ -618,7 +618,7 @@ void DeviceManager::Forget(int row) { void DeviceManager::SetDeviceOptions(int row, const QString& friendly_name, const QString& icon_name, - DeviceDatabaseBackend::TranscodeMode mode, Song::FileType format) { + MusicStorage::TranscodeMode mode, Song::FileType format) { DeviceInfo& info = devices_[row]; info.friendly_name_ = friendly_name; info.LoadIcon(QVariantList() << icon_name, friendly_name); @@ -676,6 +676,8 @@ void DeviceManager::TasksChanged() { DeviceInfo& info = devices_[index.row()]; info.task_percentage_ = -1; emit dataChanged(index, index); + + active_tasks_.remove(active_tasks_.key(index)); } } diff --git a/src/devices/devicemanager.h b/src/devices/devicemanager.h index cf4dc72bb..b870c3e8b 100644 --- a/src/devices/devicemanager.h +++ b/src/devices/devicemanager.h @@ -85,7 +85,7 @@ public: void SetDeviceOptions(int row, const QString& friendly_name, const QString& icon_name, - DeviceDatabaseBackend::TranscodeMode mode, Song::FileType format); + MusicStorage::TranscodeMode mode, Song::FileType format); // QAbstractListModel int rowCount(const QModelIndex &parent) const; @@ -151,7 +151,7 @@ private: QString icon_name_; QIcon icon_; - DeviceDatabaseBackend::TranscodeMode transcode_mode_; + MusicStorage::TranscodeMode transcode_mode_; Song::FileType transcode_format_; int task_percentage_; diff --git a/src/devices/deviceproperties.cpp b/src/devices/deviceproperties.cpp index 36156fd4d..463bf7e1a 100644 --- a/src/devices/deviceproperties.cpp +++ b/src/devices/deviceproperties.cpp @@ -187,18 +187,18 @@ void DeviceProperties::UpdateFormats() { manager_->GetConnectedDevice(index_.row()); // Transcode mode - DeviceDatabaseBackend::TranscodeMode mode = DeviceDatabaseBackend::TranscodeMode( + MusicStorage::TranscodeMode mode = MusicStorage::TranscodeMode( index_.data(DeviceManager::Role_TranscodeMode).toInt()); switch (mode) { - case DeviceDatabaseBackend::Transcode_Always: + case MusicStorage::Transcode_Always: ui_->transcode_all->setChecked(true); break; - case DeviceDatabaseBackend::Transcode_Never: + case MusicStorage::Transcode_Never: ui_->transcode_off->setChecked(true); break; - case DeviceDatabaseBackend::Transcode_Unsupported: + case MusicStorage::Transcode_Unsupported: default: ui_->transcode_unsupported->setChecked(true); break; @@ -238,13 +238,13 @@ void DeviceProperties::accept() { QDialog::accept(); // Transcode mode - DeviceDatabaseBackend::TranscodeMode mode = DeviceDatabaseBackend::Transcode_Unsupported; + MusicStorage::TranscodeMode mode = MusicStorage::Transcode_Unsupported; if (ui_->transcode_all->isChecked()) - mode = DeviceDatabaseBackend::Transcode_Always; + mode = MusicStorage::Transcode_Always; else if (ui_->transcode_off->isChecked()) - mode = DeviceDatabaseBackend::Transcode_Never; + mode = MusicStorage::Transcode_Never; else if (ui_->transcode_unsupported->isChecked()) - mode = DeviceDatabaseBackend::Transcode_Unsupported; + mode = MusicStorage::Transcode_Unsupported; // Transcode format Song::FileType format = Song::FileType(ui_->transcode_format->itemData( @@ -288,23 +288,7 @@ void DeviceProperties::UpdateFormatsFinished() { if (preset.type_ == Song::Type_Unknown) { // The user hasn't chosen a format for this device yet, so work our way down // a list of some preferred formats, picking the first one that is supported - QList best_formats; - best_formats << Song::Type_Mpeg; - best_formats << Song::Type_OggVorbis; - best_formats << Song::Type_Asf; - - foreach (Song::FileType type, best_formats) { - if (list.isEmpty() || list.contains(type)) { - preset = Transcoder::PresetForFileType(type); - break; - } - } - - if (preset.type_ == Song::Type_Unknown) { - // Still haven't found a good format - pick the first one that the - // device advertises. - preset = Transcoder::PresetForFileType(list[0]); - } + preset = Transcoder::PresetForFileType(Transcoder::PickBestFormat(list)); } ui_->transcode_format->setCurrentIndex(ui_->transcode_format->findText(preset.name_)); diff --git a/src/devices/mtpdevice.cpp b/src/devices/mtpdevice.cpp index 71523b575..34c039ecd 100644 --- a/src/devices/mtpdevice.cpp +++ b/src/devices/mtpdevice.cpp @@ -84,6 +84,9 @@ static int ProgressCallback(uint64_t const sent, uint64_t const total, } bool MtpDevice::CopyToStorage(const CopyJob& job) { + if (!connection_->is_valid()) + return false; + // Convert metadata LIBMTP_track_t track; job.metadata_.ToMTP(&track); @@ -165,6 +168,11 @@ QList MtpDevice::SupportedFiletypes() { QMutexLocker l(&db_busy_); MtpConnection connection(url_.host()); + if (!connection.is_valid()) { + qWarning() << "Error connecting to MTP device, couldn't get list of supported filetypes"; + return ret; + } + if (LIBMTP_Get_Supported_Filetypes(connection.device(), &list, &length) || !list || !length) return ret; diff --git a/src/transcoder/transcoder.cpp b/src/transcoder/transcoder.cpp index 252d5d73a..684e80c2a 100644 --- a/src/transcoder/transcoder.cpp +++ b/src/transcoder/transcoder.cpp @@ -206,6 +206,23 @@ TranscoderPreset Transcoder::PresetForFileType(Song::FileType type) { } } +Song::FileType Transcoder::PickBestFormat(QList supported) { + if (supported.isEmpty()) + return Song::Type_Unknown; + + QList best_formats; + best_formats << Song::Type_Mpeg; + best_formats << Song::Type_OggVorbis; + best_formats << Song::Type_Asf; + + foreach (Song::FileType type, best_formats) { + if (supported.isEmpty() || supported.contains(type)) + return type; + } + + return supported[0]; +} + void Transcoder::AddJob(const QString& input, const TranscoderPreset& preset, const QString& output) { diff --git a/src/transcoder/transcoder.h b/src/transcoder/transcoder.h index af75969d2..9e4edf8cd 100644 --- a/src/transcoder/transcoder.h +++ b/src/transcoder/transcoder.h @@ -55,6 +55,7 @@ class Transcoder : public QObject { static TranscoderPreset PresetForFileType(Song::FileType type); static QList GetAllPresets(); + static Song::FileType PickBestFormat(QList supported); int max_threads() const { return max_threads_; }