From ff172f6ed4720975e0fbca78dd8764d909023aa1 Mon Sep 17 00:00:00 2001 From: Andreas Date: Thu, 13 Nov 2014 22:31:49 +0100 Subject: [PATCH] Network remote can now transcode lossless files before sending them to the remote. - It is configurable in the settings. - Any format can be chosen that is supported by the transcoder. - The status of the transcoder is send to the remote. - Transcoder format settings have now a postfix so we can define mutliple transcoder formats and use them separatly. Here one for the normal transcoder and one for the network remote transcode. You can pass the postfix in the constructor. - Fixed Transcoder crash (was introduced with the gstreamer1.0 merge, decodebin doesn't have a "new-decoded-pad"). - Transcoder emits the output filename as well on "JobComplete" signal - Transcoder can now convert a file to a temporary file ("AddTemporaryJob") --- .../remotecontrolmessages.proto | 9 +- src/CMakeLists.txt | 2 + src/core/organise.cpp | 12 +- src/core/organise.h | 2 +- src/core/song.cpp | 12 + src/core/song.h | 2 + src/core/utilities.cpp | 2 + src/networkremote/incomingdataparser.cpp | 4 +- src/networkremote/incomingdataparser.h | 3 - src/networkremote/networkremote.cpp | 10 +- src/networkremote/networkremote.h | 1 + src/networkremote/outgoingdatacreator.cpp | 219 ------------ src/networkremote/outgoingdatacreator.h | 24 +- src/networkremote/remoteclient.cpp | 5 +- src/networkremote/remoteclient.h | 5 + src/networkremote/songsender.cpp | 336 ++++++++++++++++++ src/networkremote/songsender.h | 62 ++++ src/transcoder/transcodedialog.cpp | 6 +- src/transcoder/transcodedialog.h | 2 +- src/transcoder/transcoder.cpp | 31 +- src/transcoder/transcoder.h | 8 +- src/transcoder/transcoderoptionsaac.cpp | 4 +- src/transcoder/transcoderoptionsdialog.cpp | 6 + src/transcoder/transcoderoptionsdialog.h | 2 + src/transcoder/transcoderoptionsflac.cpp | 4 +- src/transcoder/transcoderoptionsinterface.h | 2 + src/transcoder/transcoderoptionsmp3.cpp | 4 +- src/transcoder/transcoderoptionsopus.cpp | 4 +- src/transcoder/transcoderoptionsspeex.cpp | 4 +- src/transcoder/transcoderoptionsvorbis.cpp | 4 +- src/transcoder/transcoderoptionswma.cpp | 4 +- src/ui/networkremotesettingspage.cpp | 45 +++ src/ui/networkremotesettingspage.h | 3 + src/ui/networkremotesettingspage.ui | 107 +++++- src/ui/ripcd.cpp | 6 +- src/ui/ripcd.h | 2 +- 36 files changed, 647 insertions(+), 311 deletions(-) create mode 100644 src/networkremote/songsender.cpp create mode 100644 src/networkremote/songsender.h diff --git a/ext/libclementine-remote/remotecontrolmessages.proto b/ext/libclementine-remote/remotecontrolmessages.proto index aad384bee..76091ed11 100644 --- a/ext/libclementine-remote/remotecontrolmessages.proto +++ b/ext/libclementine-remote/remotecontrolmessages.proto @@ -55,6 +55,7 @@ enum MsgType { LIBRARY_CHUNK = 52; DOWNLOAD_TOTAL_SIZE = 53; GLOBAL_SEARCH_RESULT = 54; + TRANSCODING_FILES = 55; } // Valid Engine states @@ -297,9 +298,14 @@ message ResponseGlobalSearch { repeated SongMetadata song_metadata = 4; } +message ResponseTranscoderStatus { + optional int32 processed = 1; + optional int32 total = 2; +} + // The message itself message Message { - optional int32 version = 1 [default=17]; + optional int32 version = 1 [default=18]; optional MsgType type = 2 [default=UNKNOWN]; // What data is in the message? optional RequestConnect request_connect = 21; @@ -333,4 +339,5 @@ message Message { optional ResponseLibraryChunk response_library_chunk = 34; optional ResponseDownloadTotalSize response_download_total_size = 36; optional ResponseGlobalSearch response_global_search = 38; + optional ResponseTranscoderStatus response_transcoder_status = 39; } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1e03b819c..da72d2819 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -233,6 +233,7 @@ set(SOURCES networkremote/networkremotehelper.cpp networkremote/outgoingdatacreator.cpp networkremote/remoteclient.cpp + networkremote/songsender.cpp networkremote/zeroconf.cpp playlist/dynamicplaylistcontrols.cpp @@ -534,6 +535,7 @@ set(HEADERS networkremote/incomingdataparser.h networkremote/outgoingdatacreator.h networkremote/remoteclient.h + networkremote/songsender.h playlist/dynamicplaylistcontrols.h playlist/playlist.h diff --git a/src/core/organise.cpp b/src/core/organise.cpp index ed89d8ed0..2e040deb9 100644 --- a/src/core/organise.cpp +++ b/src/core/organise.cpp @@ -71,8 +71,8 @@ void Organise::Start() { thread_ = new QThread; connect(thread_, SIGNAL(started()), SLOT(ProcessSomeFiles())); - connect(transcoder_, SIGNAL(JobComplete(QString, bool)), - SLOT(FileTranscoded(QString, bool))); + connect(transcoder_, SIGNAL(JobComplete(QString, QString, bool)), + SLOT(FileTranscoded(QString, QString, bool))); moveToThread(thread_); thread_->start(); @@ -272,13 +272,13 @@ void Organise::UpdateProgress() { task_manager_->SetTaskProgress(task_id_, progress, total); } -void Organise::FileTranscoded(const QString& filename, bool success) { - qLog(Info) << "File finished" << filename << success; +void Organise::FileTranscoded(const QString& input, const QString& output, bool success) { + qLog(Info) << "File finished" << input << success; transcode_progress_timer_.stop(); - Task task = tasks_transcoding_.take(filename); + Task task = tasks_transcoding_.take(input); if (!success) { - files_with_errors_ << filename; + files_with_errors_ << input; } else { tasks_pending_ << task; } diff --git a/src/core/organise.h b/src/core/organise.h index 48f18d991..559fbbea0 100644 --- a/src/core/organise.h +++ b/src/core/organise.h @@ -61,7 +61,7 @@ signals: private slots: void ProcessSomeFiles(); - void FileTranscoded(const QString& filename, bool success); + void FileTranscoded(const QString& input, const QString& output, bool success); private: void SetSongProgress(float progress, bool transcoded = false); diff --git a/src/core/song.cpp b/src/core/song.cpp index f5ff92e8b..14ab5cb8b 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -412,6 +412,18 @@ QString Song::TextForFiletype(FileType type) { } } +bool Song::IsFileLossless() const { + switch (filetype()) { + case Song::Type_Aiff: + case Song::Type_Flac: + case Song::Type_OggFlac: + case Song::Type_Wav: + return true; + default: + return false; + } +} + int CompareSongsName(const Song& song1, const Song& song2) { return song1.PrettyTitleWithArtist().localeAwareCompare( song2.PrettyTitleWithArtist()) < 0; diff --git a/src/core/song.h b/src/core/song.h index a31ff4a10..028b70e6d 100644 --- a/src/core/song.h +++ b/src/core/song.h @@ -75,6 +75,7 @@ class Song { // Don't change these values - they're stored in the database, and defined // in the tag reader protobuf. + // If a new lossless file is added, also add it to IsFileLossless(). enum FileType { Type_Unknown = 0, Type_Asf = 1, @@ -94,6 +95,7 @@ class Song { }; static QString TextForFiletype(FileType type); QString TextForFiletype() const { return TextForFiletype(filetype()); } + bool IsFileLossless() const; // Sort songs alphabetically using their pretty title static void SortSongsListAlphabetically(QList* songs); diff --git a/src/core/utilities.cpp b/src/core/utilities.cpp index 1ce8c63f8..08bc20d07 100644 --- a/src/core/utilities.cpp +++ b/src/core/utilities.cpp @@ -213,6 +213,8 @@ QString GetTemporaryFileName() { QString file; { QTemporaryFile tempfile; + // Do not delete the file, we want to do something with it + tempfile.setAutoRemove(false); tempfile.open(); file = tempfile.fileName(); } diff --git a/src/networkremote/incomingdataparser.cpp b/src/networkremote/incomingdataparser.cpp index d78832746..d3be9cbac 100644 --- a/src/networkremote/incomingdataparser.cpp +++ b/src/networkremote/incomingdataparser.cpp @@ -158,10 +158,10 @@ void IncomingDataParser::Parse(const pb::remote::Message& msg) { emit GetLyrics(); break; case pb::remote::DOWNLOAD_SONGS: - emit SendSongs(msg.request_download_songs(), client); + client->song_sender()->SendSongs(msg.request_download_songs()); break; case pb::remote::SONG_OFFER_RESPONSE: - emit ResponseSongOffer(client, msg.response_song_offer().accepted()); + client->song_sender()->ResponseSongOffer(msg.response_song_offer().accepted()); break; case pb::remote::GET_LIBRARY: emit SendLibrary(client); diff --git a/src/networkremote/incomingdataparser.h b/src/networkremote/incomingdataparser.h index a3e23e070..14c7638ce 100644 --- a/src/networkremote/incomingdataparser.h +++ b/src/networkremote/incomingdataparser.h @@ -46,9 +46,6 @@ signals: bool enqueue); void RemoveSongs(int id, const QList& indices); void SeekTo(int seconds); - void SendSongs(const pb::remote::RequestDownloadSongs& request, - RemoteClient* client); - void ResponseSongOffer(RemoteClient* client, bool accepted); void SendLibrary(RemoteClient* client); void RateCurrentSong(double); diff --git a/src/networkremote/networkremote.cpp b/src/networkremote/networkremote.cpp index 5dd628e78..e1516b44e 100644 --- a/src/networkremote/networkremote.cpp +++ b/src/networkremote/networkremote.cpp @@ -29,6 +29,7 @@ const char* NetworkRemote::kSettingsGroup = "NetworkRemote"; const quint16 NetworkRemote::kDefaultServerPort = 5500; +const char* NetworkRemote::kTranscoderSettingPostfix = "/NetworkRemote"; NetworkRemote::NetworkRemote(Application* app, QObject* parent) : QObject(parent), signals_connected_(false), app_(app) {} @@ -158,15 +159,6 @@ void NetworkRemote::AcceptConnection() { connect(incoming_data_parser_.get(), SIGNAL(GetLyrics()), outgoing_data_creator_.get(), SLOT(GetLyrics())); - connect(incoming_data_parser_.get(), - SIGNAL(SendSongs(pb::remote::RequestDownloadSongs, RemoteClient*)), - outgoing_data_creator_.get(), - SLOT(SendSongs(pb::remote::RequestDownloadSongs, RemoteClient*))); - connect(incoming_data_parser_.get(), - SIGNAL(ResponseSongOffer(RemoteClient*, bool)), - outgoing_data_creator_.get(), - SLOT(ResponseSongOffer(RemoteClient*, bool))); - connect(incoming_data_parser_.get(), SIGNAL(SendLibrary(RemoteClient*)), outgoing_data_creator_.get(), SLOT(SendLibrary(RemoteClient*))); diff --git a/src/networkremote/networkremote.h b/src/networkremote/networkremote.h index ba455bc23..9f53b0f5a 100644 --- a/src/networkremote/networkremote.h +++ b/src/networkremote/networkremote.h @@ -17,6 +17,7 @@ class NetworkRemote : public QObject { public: static const char* kSettingsGroup; static const quint16 kDefaultServerPort; + static const char* kTranscoderSettingPostfix; explicit NetworkRemote(Application* app, QObject* parent = nullptr); ~NetworkRemote(); diff --git a/src/networkremote/outgoingdatacreator.cpp b/src/networkremote/outgoingdatacreator.cpp index d5db3bac4..55ec7c114 100644 --- a/src/networkremote/outgoingdatacreator.cpp +++ b/src/networkremote/outgoingdatacreator.cpp @@ -152,7 +152,6 @@ void OutgoingDataCreator::SendDataToClients(pb::remote::Message* msg) { if (client->isDownloader()) { if (client->State() != QTcpSocket::ConnectedState) { clients_->removeAt(clients_->indexOf(client)); - download_queue_.remove(client); delete client; } continue; @@ -579,224 +578,6 @@ void OutgoingDataCreator::SendLyrics(int id, results_.take(id); } -void OutgoingDataCreator::SendSongs( - const pb::remote::RequestDownloadSongs& request, RemoteClient* client) { - - if (!download_queue_.contains(client)) { - download_queue_.insert(client, QQueue()); - } - - switch (request.download_item()) { - case pb::remote::CurrentItem: { - DownloadItem item(current_song_, 1, 1); - download_queue_[client].append(item); - break; - } - case pb::remote::ItemAlbum: - SendAlbum(client, current_song_); - break; - case pb::remote::APlaylist: - SendPlaylist(client, request.playlist_id()); - break; - case pb::remote::Urls: - SendUrls(client, request); - break; - default: - break; - } - - // Send total file size & file count - SendTotalFileSize(client); - - // Send first file - OfferNextSong(client); -} - -void OutgoingDataCreator::SendTotalFileSize(RemoteClient *client) { - if (!download_queue_.contains(client)) return; - - pb::remote::Message msg; - msg.set_type(pb::remote::DOWNLOAD_TOTAL_SIZE); - - pb::remote::ResponseDownloadTotalSize* response = - msg.mutable_response_download_total_size(); - - response->set_file_count(download_queue_[client].size()); - - int total = 0; - for (DownloadItem item : download_queue_[client]) { - total += item.song_.filesize(); - } - - response->set_total_size(total); - - client->SendData(&msg); -} - -void OutgoingDataCreator::OfferNextSong(RemoteClient* client) { - if (!download_queue_.contains(client)) return; - - pb::remote::Message msg; - - if (download_queue_.value(client).isEmpty()) { - // We sent all songs, tell the client the queue is empty - msg.set_type(pb::remote::DOWNLOAD_QUEUE_EMPTY); - } else { - // Get the item and send the single song - DownloadItem item = download_queue_[client].head(); - - msg.set_type(pb::remote::SONG_FILE_CHUNK); - pb::remote::ResponseSongFileChunk* chunk = - msg.mutable_response_song_file_chunk(); - - // Song offer is chunk no 0 - chunk->set_chunk_count(0); - chunk->set_chunk_number(0); - chunk->set_file_count(item.song_count_); - chunk->set_file_number(item.song_no_); - - CreateSong(item.song_, QImage(), -1, chunk->mutable_song_metadata()); - } - - client->SendData(&msg); -} - -void OutgoingDataCreator::ResponseSongOffer(RemoteClient* client, - bool accepted) { - if (!download_queue_.contains(client)) return; - - if (download_queue_.value(client).isEmpty()) return; - - // Get the item and send the single song - DownloadItem item = download_queue_[client].dequeue(); - if (accepted) - SendSingleSong(client, item.song_, item.song_no_, item.song_count_); - - // And offer the next song - OfferNextSong(client); -} - -void OutgoingDataCreator::SendSingleSong(RemoteClient* client, const Song& song, - int song_no, int song_count) { - // Only local files!!! - if (!(song.url().scheme() == "file")) return; - - // Open the file - QFile file(song.url().toLocalFile()); - - // Get sha1 for file - QByteArray sha1 = Utilities::Sha1File(file).toHex(); - qLog(Debug) << "sha1 for file" << song.url().toLocalFile() << "=" << sha1; - - file.open(QIODevice::ReadOnly); - - QByteArray data; - pb::remote::Message msg; - pb::remote::ResponseSongFileChunk* chunk = - msg.mutable_response_song_file_chunk(); - msg.set_type(pb::remote::SONG_FILE_CHUNK); - - QImage null_image; - - // Calculate the number of chunks - int chunk_count = qRound((file.size() / kFileChunkSize) + 0.5); - int chunk_number = 1; - - while (!file.atEnd()) { - // Read file chunk - data = file.read(kFileChunkSize); - - // Set chunk data - chunk->set_chunk_count(chunk_count); - chunk->set_chunk_number(chunk_number); - chunk->set_file_count(song_count); - chunk->set_file_number(song_no); - chunk->set_size(file.size()); - chunk->set_data(data.data(), data.size()); - chunk->set_file_hash(sha1.data(), sha1.size()); - - // On the first chunk send the metadata, so the client knows - // what file it receives. - if (chunk_number == 1) { - int i = app_->playlist_manager()->active()->current_row(); - CreateSong( - song, null_image, i, - msg.mutable_response_song_file_chunk()->mutable_song_metadata()); - } - - // Send data directly to the client - client->SendData(&msg); - - // Clear working data - chunk->Clear(); - data.clear(); - - chunk_number++; - } - - file.close(); -} - -void OutgoingDataCreator::SendAlbum(RemoteClient* client, const Song& song) { - // No streams! - if (song.url().scheme() != "file") return; - - SongList album = app_->library_backend()->GetSongsByAlbum(song.album()); - - for (Song s : album) { - DownloadItem item(s, album.indexOf(s) + 1, album.size()); - download_queue_[client].append(item); - } -} - -void OutgoingDataCreator::SendPlaylist(RemoteClient* client, int playlist_id) { - Playlist* playlist = app_->playlist_manager()->playlist(playlist_id); - if (!playlist) { - qLog(Info) << "Could not find playlist with id = " << playlist_id; - return; - } - SongList song_list = playlist->GetAllSongs(); - - // Count the local songs - int count = 0; - for (Song s : song_list) { - if (s.url().scheme() == "file") { - count++; - } - } - - for (Song s : song_list) { - // Only local files! - if (s.url().scheme() == "file") { - DownloadItem item(s, song_list.indexOf(s) + 1, count); - download_queue_[client].append(item); - } - } -} - -void OutgoingDataCreator::SendUrls(RemoteClient *client, - const pb::remote::RequestDownloadSongs &request) { - SongList song_list; - - // First gather all valid songs - for (auto it = request.urls().begin(); it != request.urls().end(); ++it) { - std::string s = *it; - QUrl url = QUrl(QStringFromStdString(s)); - - Song song = app_->library_backend()->GetSongByUrl(url); - - if (song.is_valid() && song.url().scheme() == "file") { - song_list.append(song); - } - } - - // Then send them to Clementine Remote - for (Song s : song_list) { - DownloadItem item(s, song_list.indexOf(s) + 1, song_list.count()); - download_queue_[client].append(item); - } -} - void OutgoingDataCreator::SendLibrary(RemoteClient* client) { // Get a temporary file name QString temp_file_name = Utilities::GetTemporaryFileName(); diff --git a/src/networkremote/outgoingdatacreator.h b/src/networkremote/outgoingdatacreator.h index d4eabe735..0c6838132 100644 --- a/src/networkremote/outgoingdatacreator.h +++ b/src/networkremote/outgoingdatacreator.h @@ -30,14 +30,6 @@ typedef QList ProviderList; -struct DownloadItem { - Song song_; - int song_no_; - int song_count_; - DownloadItem(Song s, int no, int count) - : song_(s), song_no_(no), song_count_(count) {} -}; - struct GlobalSearchRequest { int id_; QString query_; @@ -58,6 +50,9 @@ class OutgoingDataCreator : public QObject { void SetClients(QList* clients); + static void CreateSong(const Song& song, const QImage& art, const int index, + pb::remote::SongMetadata* song_metadata); + public slots: void SendClementineInfo(); void SendAllPlaylists(); @@ -82,9 +77,6 @@ class OutgoingDataCreator : public QObject { void DisconnectAllClients(); void GetLyrics(); void SendLyrics(int id, const SongInfoFetcher::Result& result); - void SendSongs(const pb::remote::RequestDownloadSongs& request, - RemoteClient* client); - void ResponseSongOffer(RemoteClient* client, bool accepted); void SendLibrary(RemoteClient* client); void EnableKittens(bool aww); void SendKitten(const QImage& kitten); @@ -103,7 +95,6 @@ class OutgoingDataCreator : public QObject { QTimer* keep_alive_timer_; QTimer* track_position_timer_; int keep_alive_timeout_; - QMap > download_queue_; int last_track_position_; bool aww_; @@ -116,17 +107,8 @@ class OutgoingDataCreator : public QObject { void SendDataToClients(pb::remote::Message* msg); void SetEngineState(pb::remote::ResponseClementineInfo* msg); - void CreateSong(const Song& song, const QImage& art, const int index, - pb::remote::SongMetadata* song_metadata); void CheckEnabledProviders(); SongInfoProvider* ProviderByName(const QString& name) const; - void SendSingleSong(RemoteClient* client, const Song& song, int song_no, - int song_count); - void SendAlbum(RemoteClient* client, const Song& song); - void SendPlaylist(RemoteClient* client, int playlist_id); - void SendUrls(RemoteClient* client, const pb::remote::RequestDownloadSongs& request); - void OfferNextSong(RemoteClient* client); - void SendTotalFileSize(RemoteClient* client); }; #endif // OUTGOINGDATACREATOR_H diff --git a/src/networkremote/remoteclient.cpp b/src/networkremote/remoteclient.cpp index cd05bdade..2f9c87947 100644 --- a/src/networkremote/remoteclient.cpp +++ b/src/networkremote/remoteclient.cpp @@ -24,7 +24,10 @@ #include RemoteClient::RemoteClient(Application* app, QTcpSocket* client) - : app_(app), downloader_(false), client_(client) { + : app_(app), + downloader_(false), + client_(client), + song_sender_(new SongSender(app, this)) { // Open the buffer buffer_.setData(QByteArray()); buffer_.open(QIODevice::ReadWrite); diff --git a/src/networkremote/remoteclient.h b/src/networkremote/remoteclient.h index b1812b571..8370b8632 100644 --- a/src/networkremote/remoteclient.h +++ b/src/networkremote/remoteclient.h @@ -5,6 +5,8 @@ #include #include +#include "songsender.h" + #include "core/application.h" #include "remotecontrolmessages.pb.h" @@ -21,6 +23,8 @@ class RemoteClient : public QObject { bool isDownloader() { return downloader_; } void DisconnectClient(pb::remote::ReasonDisconnect reason); + SongSender* song_sender() { return song_sender_; } + private slots: void IncomingData(); @@ -45,6 +49,7 @@ signals: bool reading_protobuf_; quint32 expected_length_; QBuffer buffer_; + SongSender* song_sender_; }; #endif // REMOTECLIENT_H diff --git a/src/networkremote/songsender.cpp b/src/networkremote/songsender.cpp new file mode 100644 index 000000000..3385c1c70 --- /dev/null +++ b/src/networkremote/songsender.cpp @@ -0,0 +1,336 @@ +/* This file is part of Clementine. + Copyright 2012, Andreas Muttscheller + + Clementine is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Clementine is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Clementine. If not, see . +*/ + +#include "songsender.h" + +#include "networkremote.h" + +#include + +#include "core/application.h" +#include "core/logging.h" +#include "core/utilities.h" +#include "library/librarybackend.h" +#include "playlist/playlistitem.h" + +const quint32 SongSender::kFileChunkSize = 100000; // in Bytes + +SongSender::SongSender(Application* app, RemoteClient* client) + : app_(app), + client_(client), + transcoder_(new Transcoder(this)) { + QSettings s; + s.beginGroup(NetworkRemote::kSettingsGroup); + + transcode_lossless_files_ = s.value("convert_lossless", false).toBool(); + + // Load preset + QString last_output_format = s.value("last_output_format", "audio/x-vorbis").toString(); + QList presets = transcoder_->GetAllPresets(); + for (int i = 0; iplayer()->GetCurrentItem()->Metadata(); + switch (request.download_item()) { + case pb::remote::CurrentItem: { + DownloadItem item(current_song, 1, 1); + download_queue_.append(item); + break; + } + case pb::remote::ItemAlbum: + SendAlbum(current_song); + break; + case pb::remote::APlaylist: + SendPlaylist(request.playlist_id()); + break; + case pb::remote::Urls: + SendUrls(request); + break; + default: + break; + } + + if (transcode_lossless_files_) { + TranscodeLosslessFiles(); + } else { + StartTransfer(); + } +} + +void SongSender::TranscodeLosslessFiles() { + for (DownloadItem item : download_queue_) { + // Check only lossless files + if (!item.song_.IsFileLossless()) + continue; + + // Add the file to the transcoder + QString local_file = item.song_.url().toLocalFile(); + + transcoder_->AddTemporaryJob(local_file, transcoder_preset_); + + qLog(Debug) << "transcoding" << local_file; + total_transcode_++; + } + + transcoder_->Start(); + + SendTranscoderStatus(); +} + +void SongSender::TranscodeJobComplete(const QString& input, const QString& output, bool success) { + qLog(Debug) << input << "transcoded to" << output << success; + + // If it wasn't successful send original file + if (success) { + transcoder_map_.insert(input, output); + } + + SendTranscoderStatus(); +} + +void SongSender::SendTranscoderStatus() { + // Send a message to the remote that we are converting files + pb::remote::Message msg; + msg.set_type(pb::remote::TRANSCODING_FILES); + + pb::remote::ResponseTranscoderStatus* status = + msg.mutable_response_transcoder_status(); + status->set_processed(transcoder_map_.count()); + status->set_total(total_transcode_); + + client_->SendData(&msg); +} + +void SongSender::StartTransfer() { + total_transcode_ = 0; + + // Send total file size & file count + SendTotalFileSize(); + + // Send first file + OfferNextSong(); +} + +void SongSender::SendTotalFileSize() { + pb::remote::Message msg; + msg.set_type(pb::remote::DOWNLOAD_TOTAL_SIZE); + + pb::remote::ResponseDownloadTotalSize* response = + msg.mutable_response_download_total_size(); + + response->set_file_count(download_queue_.size()); + + int total = 0; + for (DownloadItem item : download_queue_) { + total += item.song_.filesize(); + } + + response->set_total_size(total); + + client_->SendData(&msg); +} + +void SongSender::OfferNextSong() { + pb::remote::Message msg; + + if (download_queue_.isEmpty()) { + msg.set_type(pb::remote::DOWNLOAD_QUEUE_EMPTY); + } else { + // Get the item and send the single song + DownloadItem item = download_queue_.head(); + + msg.set_type(pb::remote::SONG_FILE_CHUNK); + pb::remote::ResponseSongFileChunk* chunk = + msg.mutable_response_song_file_chunk(); + + // Song offer is chunk no 0 + chunk->set_chunk_count(0); + chunk->set_chunk_number(0); + chunk->set_file_count(item.song_count_); + chunk->set_file_number(item.song_no_); + + OutgoingDataCreator::CreateSong(item.song_, QImage(), -1, chunk->mutable_song_metadata()); + } + + client_->SendData(&msg); +} + +void SongSender::ResponseSongOffer(bool accepted) { + if (download_queue_.isEmpty()) return; + + // Get the item and send the single song + DownloadItem item = download_queue_.dequeue(); + if (accepted) + SendSingleSong(item); + + // And offer the next song + OfferNextSong(); +} + +void SongSender::SendSingleSong(DownloadItem download_item) { + // Only local files!!! + if (!(download_item.song_.url().scheme() == "file")) return; + + QString local_file = download_item.song_.url().toLocalFile(); + bool is_transcoded = transcoder_map_.contains(local_file); + + if (is_transcoded) { + local_file = transcoder_map_.take(local_file); + } + + // Open the file + QFile file(local_file); + + // Get sha1 for file + QByteArray sha1 = Utilities::Sha1File(file).toHex(); + qLog(Debug) << "sha1 for file" << local_file << "=" << sha1; + + file.open(QIODevice::ReadOnly); + + QByteArray data; + pb::remote::Message msg; + pb::remote::ResponseSongFileChunk* chunk = + msg.mutable_response_song_file_chunk(); + msg.set_type(pb::remote::SONG_FILE_CHUNK); + + QImage null_image; + + // Calculate the number of chunks + int chunk_count = qRound((file.size() / kFileChunkSize) + 0.5); + int chunk_number = 1; + + while (!file.atEnd()) { + // Read file chunk + data = file.read(kFileChunkSize); + + // Set chunk data + chunk->set_chunk_count(chunk_count); + chunk->set_chunk_number(chunk_number); + chunk->set_file_count(download_item.song_count_); + chunk->set_file_number(download_item.song_no_); + chunk->set_size(file.size()); + chunk->set_data(data.data(), data.size()); + chunk->set_file_hash(sha1.data(), sha1.size()); + + // On the first chunk send the metadata, so the client knows + // what file it receives. + if (chunk_number == 1) { + int i = app_->playlist_manager()->active()->current_row(); + pb::remote::SongMetadata* song_metadata = + msg.mutable_response_song_file_chunk()->mutable_song_metadata(); + OutgoingDataCreator::CreateSong(download_item.song_, null_image, i,song_metadata); + + // if the file was transcoded, we have to change the filename and filesize + if (is_transcoded) { + song_metadata->set_file_size(file.size()); + QString basefilename = download_item.song_.basefilename(); + QFileInfo info(basefilename); + basefilename.replace("." + info.suffix(), + "." + transcoder_preset_.extension_); + song_metadata->set_filename(DataCommaSizeFromQString(basefilename)); + } + } + + // Send data directly to the client + client_->SendData(&msg); + + // Clear working data + chunk->Clear(); + data.clear(); + + chunk_number++; + } + + // If the file was transcoded, delete the temporary one + if (is_transcoded) { + file.remove(); + } else { + file.close(); + } +} + +void SongSender::SendAlbum(const Song& song) { + // No streams! + if (song.url().scheme() != "file") return; + + SongList album = app_->library_backend()->GetSongsByAlbum(song.album()); + + for (Song s : album) { + DownloadItem item(s, album.indexOf(s) + 1, album.size()); + download_queue_.append(item); + } +} + +void SongSender::SendPlaylist(int playlist_id) { + Playlist* playlist = app_->playlist_manager()->playlist(playlist_id); + if (!playlist) { + qLog(Info) << "Could not find playlist with id = " << playlist_id; + return; + } + SongList song_list = playlist->GetAllSongs(); + + // Count the local songs + int count = 0; + for (Song s : song_list) { + if (s.url().scheme() == "file") { + count++; + } + } + + for (Song s : song_list) { + // Only local files! + if (s.url().scheme() == "file") { + DownloadItem item(s, song_list.indexOf(s) + 1, count); + download_queue_.append(item); + } + } +} + +void SongSender::SendUrls(const pb::remote::RequestDownloadSongs &request) { + SongList song_list; + + // First gather all valid songs + for (auto it = request.urls().begin(); it != request.urls().end(); ++it) { + std::string s = *it; + QUrl url = QUrl(QStringFromStdString(s)); + + Song song = app_->library_backend()->GetSongByUrl(url); + + if (song.is_valid() && song.url().scheme() == "file") { + song_list.append(song); + } + } + + // Then send them to Clementine Remote + for (Song s : song_list) { + DownloadItem item(s, song_list.indexOf(s) + 1, song_list.count()); + download_queue_.append(item); + } +} diff --git a/src/networkremote/songsender.h b/src/networkremote/songsender.h new file mode 100644 index 000000000..a6d24fcad --- /dev/null +++ b/src/networkremote/songsender.h @@ -0,0 +1,62 @@ +#ifndef SONGSENDER_H +#define SONGSENDER_H + +#include +#include +#include + +#include "remotecontrolmessages.pb.h" + +#include "core/song.h" +#include "transcoder/transcoder.h" + +class Application; +class RemoteClient; +class Transcoder; + +struct DownloadItem { + Song song_; + int song_no_; + int song_count_; + DownloadItem(Song s, int no, int count) + : song_(s), song_no_(no), song_count_(count) {} +}; + +class SongSender : public QObject { + Q_OBJECT + public: + SongSender(Application* app, RemoteClient* client); + + static const quint32 kFileChunkSize; + + public slots: + void SendSongs(const pb::remote::RequestDownloadSongs& request); + void ResponseSongOffer(bool accepted); + + private slots: + void TranscodeJobComplete(const QString& input, const QString& output, bool success); + void StartTransfer(); + + private: + Application* app_; + RemoteClient* client_; + + TranscoderPreset transcoder_preset_; + Transcoder* transcoder_; + bool transcode_lossless_files_; + + QQueue download_queue_; + QMap transcoder_map_; + int total_transcode_; + + void SendSingleSong(DownloadItem download_item); + void SendAlbum(const Song& song); + void SendPlaylist(int playlist_id); + void SendUrls(const pb::remote::RequestDownloadSongs& request); + void OfferNextSong(); + void SendTotalFileSize(); + void TranscodeLosslessFiles(); + void SendTranscoderStatus(); +}; + +#endif // SONGSENDER_H diff --git a/src/transcoder/transcodedialog.cpp b/src/transcoder/transcodedialog.cpp index 69e44824b..49f2f8ef8 100644 --- a/src/transcoder/transcodedialog.cpp +++ b/src/transcoder/transcodedialog.cpp @@ -108,8 +108,8 @@ TranscodeDialog::TranscodeDialog(QWidget* parent) connect(ui_->options, SIGNAL(clicked()), SLOT(Options())); connect(ui_->select, SIGNAL(clicked()), SLOT(AddDestination())); - connect(transcoder_, SIGNAL(JobComplete(QString, bool)), - SLOT(JobComplete(QString, bool))); + connect(transcoder_, SIGNAL(JobComplete(QString, QString, bool)), + SLOT(JobComplete(QString, QString, bool))); connect(transcoder_, SIGNAL(LogLine(QString)), SLOT(LogLine(QString))); connect(transcoder_, SIGNAL(AllJobsComplete()), SLOT(AllJobsComplete())); } @@ -171,7 +171,7 @@ void TranscodeDialog::Cancel() { SetWorking(false); } -void TranscodeDialog::JobComplete(const QString& filename, bool success) { +void TranscodeDialog::JobComplete(const QString& input, const QString& output, bool success) { (*(success ? &finished_success_ : &finished_failed_))++; queued_--; diff --git a/src/transcoder/transcodedialog.h b/src/transcoder/transcodedialog.h index 77a035a05..181f1ea15 100644 --- a/src/transcoder/transcodedialog.h +++ b/src/transcoder/transcodedialog.h @@ -49,7 +49,7 @@ class TranscodeDialog : public QDialog { void Remove(); void Start(); void Cancel(); - void JobComplete(const QString& filename, bool success); + void JobComplete(const QString& input, const QString& output, bool success); void LogLine(const QString& message); void AllJobsComplete(); void Options(); diff --git a/src/transcoder/transcoder.cpp b/src/transcoder/transcoder.cpp index fcb9a75ca..5352ad54f 100644 --- a/src/transcoder/transcoder.cpp +++ b/src/transcoder/transcoder.cpp @@ -28,6 +28,7 @@ #include "core/logging.h" #include "core/signalchecker.h" +#include "core/utilities.h" using std::shared_ptr; @@ -193,14 +194,16 @@ void Transcoder::JobState::PostFinished(bool success) { new Transcoder::JobFinishedEvent(this, success)); } -Transcoder::Transcoder(QObject* parent) - : QObject(parent), max_threads_(QThread::idealThreadCount()) { +Transcoder::Transcoder(QObject* parent, const QString& settings_postfix) + : QObject(parent), + max_threads_(QThread::idealThreadCount()), + settings_postfix_(settings_postfix) { if (JobFinishedEvent::sEventType == -1) JobFinishedEvent::sEventType = QEvent::registerEventType(); // Initialise some settings for the lamemp3enc element. QSettings s; - s.beginGroup("Transcoder/lamemp3enc"); + s.beginGroup("Transcoder/lamemp3enc" + settings_postfix_); if (s.value("target").isNull()) { s.setValue("target", 1); // 1 == bitrate @@ -301,6 +304,15 @@ void Transcoder::AddJob(const QString& input, const TranscoderPreset& preset, queued_jobs_ << job; } +void Transcoder::AddTemporaryJob(const QString &input, const TranscoderPreset &preset) { + Job job; + job.input = input; + job.output = Utilities::GetTemporaryFileName(); + job.preset = preset; + + queued_jobs_ << job; +} + void Transcoder::Start() { emit LogLine(tr("Transcoding %1 files using %2 threads") .arg(queued_jobs_.count()) @@ -327,11 +339,11 @@ Transcoder::StartJobStatus Transcoder::MaybeStartNextJob() { return StartedSuccessfully; } - emit JobComplete(job.input, false); + emit JobComplete(job.input, job.output, false); return FailedToStart; } -void Transcoder::NewPadCallback(GstElement*, GstPad* pad, gboolean, +void Transcoder::NewPadCallback(GstElement*, GstPad* pad, gpointer data) { JobState* state = reinterpret_cast(data); GstPad* const audiopad = @@ -446,7 +458,7 @@ bool Transcoder::StartJob(const Job& job) { // Set callbacks state->convert_element_ = convert; - CHECKED_GCONNECT(decode, "new-decoded-pad", &NewPadCallback, state.get()); + CHECKED_GCONNECT(decode, "pad-added", &NewPadCallback, state.get()); gst_bus_set_sync_handler(gst_pipeline_get_bus(GST_PIPELINE(state->pipeline_)), BusCallbackSync, state.get(), nullptr); state->bus_callback_id_ = gst_bus_add_watch( @@ -487,7 +499,8 @@ bool Transcoder::event(QEvent* e) { return true; } - QString filename = (*it)->job_.input; + QString input = (*it)->job_.input; + QString output = (*it)->job_.output; // Remove event handlers from the gstreamer pipeline so they don't get // called after the pipeline is shutting down @@ -500,7 +513,7 @@ bool Transcoder::event(QEvent* e) { current_jobs_.erase(it); // Emit the finished signal - emit JobComplete(filename, finished_event->success_); + emit JobComplete(input, output, finished_event->success_); // Start some more jobs MaybeStartNextJob(); @@ -559,7 +572,7 @@ QMap Transcoder::GetProgress() const { void Transcoder::SetElementProperties(const QString& name, GObject* object) { QSettings s; - s.beginGroup("Transcoder/" + name); + s.beginGroup("Transcoder/" + name + settings_postfix_); guint properties_count = 0; GParamSpec** properties = g_object_class_list_properties( diff --git a/src/transcoder/transcoder.h b/src/transcoder/transcoder.h index 78e5d015b..8103c6ee9 100644 --- a/src/transcoder/transcoder.h +++ b/src/transcoder/transcoder.h @@ -47,7 +47,7 @@ class Transcoder : public QObject { Q_OBJECT public: - Transcoder(QObject* parent = nullptr); + Transcoder(QObject* parent = nullptr, const QString& settings_postfix = ""); static TranscoderPreset PresetForFileType(Song::FileType type); static QList GetAllPresets(); @@ -58,6 +58,7 @@ class Transcoder : public QObject { void AddJob(const QString& input, const TranscoderPreset& preset, const QString& output = QString()); + void AddTemporaryJob(const QString& input, const TranscoderPreset& preset); QMap GetProgress() const; int QueuedJobsCount() const { return queued_jobs_.count(); } @@ -67,7 +68,7 @@ class Transcoder : public QObject { void Cancel(); signals: - void JobComplete(const QString& filename, bool success); + void JobComplete(const QString& input, const QString& output, bool success); void LogLine(const QString& message); void AllJobsComplete(); @@ -132,7 +133,7 @@ signals: GstElement* bin = nullptr); void SetElementProperties(const QString& name, GObject* element); - static void NewPadCallback(GstElement*, GstPad* pad, gboolean, gpointer data); + static void NewPadCallback(GstElement*, GstPad* pad, gpointer data); static gboolean BusCallback(GstBus*, GstMessage* msg, gpointer data); static GstBusSyncReply BusCallbackSync(GstBus*, GstMessage* msg, gpointer data); @@ -143,6 +144,7 @@ signals: int max_threads_; QList queued_jobs_; JobStateList current_jobs_; + QString settings_postfix_; }; #endif // TRANSCODER_H diff --git a/src/transcoder/transcoderoptionsaac.cpp b/src/transcoder/transcoderoptionsaac.cpp index 1cec6b263..628bd84f8 100644 --- a/src/transcoder/transcoderoptionsaac.cpp +++ b/src/transcoder/transcoderoptionsaac.cpp @@ -31,7 +31,7 @@ TranscoderOptionsAAC::~TranscoderOptionsAAC() { delete ui_; } void TranscoderOptionsAAC::Load() { QSettings s; - s.beginGroup(kSettingsGroup); + s.beginGroup(kSettingsGroup + settings_postfix_); ui_->bitrate_slider->setValue(s.value("bitrate", 128000).toInt() / 1000); ui_->profile->setCurrentIndex(s.value("profile", 2).toInt() - 1); @@ -42,7 +42,7 @@ void TranscoderOptionsAAC::Load() { void TranscoderOptionsAAC::Save() { QSettings s; - s.beginGroup(kSettingsGroup); + s.beginGroup(kSettingsGroup + settings_postfix_); s.setValue("bitrate", ui_->bitrate_slider->value() * 1000); s.setValue("profile", ui_->profile->currentIndex() + 1); diff --git a/src/transcoder/transcoderoptionsdialog.cpp b/src/transcoder/transcoderoptionsdialog.cpp index 1fdb73a54..d57a54521 100644 --- a/src/transcoder/transcoderoptionsdialog.cpp +++ b/src/transcoder/transcoderoptionsdialog.cpp @@ -79,3 +79,9 @@ void TranscoderOptionsDialog::accept() { } QDialog::accept(); } + +void TranscoderOptionsDialog::set_settings_postfix(const QString &settings_postfix) { + if (options_) { + options_->settings_postfix_ = settings_postfix; + } +} diff --git a/src/transcoder/transcoderoptionsdialog.h b/src/transcoder/transcoderoptionsdialog.h index 5afefa7dc..5bb65ceca 100644 --- a/src/transcoder/transcoderoptionsdialog.h +++ b/src/transcoder/transcoderoptionsdialog.h @@ -36,6 +36,8 @@ class TranscoderOptionsDialog : public QDialog { void accept(); + void set_settings_postfix(const QString& settings_postfix); + protected: void showEvent(QShowEvent* e); diff --git a/src/transcoder/transcoderoptionsflac.cpp b/src/transcoder/transcoderoptionsflac.cpp index dc29471a9..38446f0e2 100644 --- a/src/transcoder/transcoderoptionsflac.cpp +++ b/src/transcoder/transcoderoptionsflac.cpp @@ -31,14 +31,14 @@ TranscoderOptionsFlac::~TranscoderOptionsFlac() { delete ui_; } void TranscoderOptionsFlac::Load() { QSettings s; - s.beginGroup(kSettingsGroup); + s.beginGroup(kSettingsGroup + settings_postfix_); ui_->quality->setValue(s.value("quality", 5).toInt()); } void TranscoderOptionsFlac::Save() { QSettings s; - s.beginGroup(kSettingsGroup); + s.beginGroup(kSettingsGroup + settings_postfix_); s.setValue("quality", ui_->quality->value()); } diff --git a/src/transcoder/transcoderoptionsinterface.h b/src/transcoder/transcoderoptionsinterface.h index db528e750..5d8aa6aa6 100644 --- a/src/transcoder/transcoderoptionsinterface.h +++ b/src/transcoder/transcoderoptionsinterface.h @@ -27,6 +27,8 @@ class TranscoderOptionsInterface : public QWidget { virtual void Load() = 0; virtual void Save() = 0; + + QString settings_postfix_; }; #endif // TRANSCODEROPTIONSINTERFACE_H diff --git a/src/transcoder/transcoderoptionsmp3.cpp b/src/transcoder/transcoderoptionsmp3.cpp index 07a44202b..0376ddacd 100644 --- a/src/transcoder/transcoderoptionsmp3.cpp +++ b/src/transcoder/transcoderoptionsmp3.cpp @@ -36,7 +36,7 @@ TranscoderOptionsMP3::~TranscoderOptionsMP3() { delete ui_; } void TranscoderOptionsMP3::Load() { QSettings s; - s.beginGroup(kSettingsGroup); + s.beginGroup(kSettingsGroup + settings_postfix_); ; if (s.value("target", 1).toInt() == 0) { @@ -55,7 +55,7 @@ void TranscoderOptionsMP3::Load() { void TranscoderOptionsMP3::Save() { QSettings s; - s.beginGroup(kSettingsGroup); + s.beginGroup(kSettingsGroup + settings_postfix_); s.setValue("target", ui_->target_quality->isChecked() ? 0 : 1); s.setValue("quality", ui_->quality_spinbox->value()); diff --git a/src/transcoder/transcoderoptionsopus.cpp b/src/transcoder/transcoderoptionsopus.cpp index 013cae7fc..7d0b6bc27 100644 --- a/src/transcoder/transcoderoptionsopus.cpp +++ b/src/transcoder/transcoderoptionsopus.cpp @@ -34,14 +34,14 @@ TranscoderOptionsOpus::~TranscoderOptionsOpus() { delete ui_; } void TranscoderOptionsOpus::Load() { QSettings s; - s.beginGroup(kSettingsGroup); + s.beginGroup(kSettingsGroup + settings_postfix_); ui_->bitrate_slider->setValue(s.value("bitrate", 128000).toInt() / 1000); } void TranscoderOptionsOpus::Save() { QSettings s; - s.beginGroup(kSettingsGroup); + s.beginGroup(kSettingsGroup + settings_postfix_); s.setValue("bitrate", ui_->bitrate_slider->value() * 1000); } diff --git a/src/transcoder/transcoderoptionsspeex.cpp b/src/transcoder/transcoderoptionsspeex.cpp index dd39af280..da956f00a 100644 --- a/src/transcoder/transcoderoptionsspeex.cpp +++ b/src/transcoder/transcoderoptionsspeex.cpp @@ -31,7 +31,7 @@ TranscoderOptionsSpeex::~TranscoderOptionsSpeex() { delete ui_; } void TranscoderOptionsSpeex::Load() { QSettings s; - s.beginGroup(kSettingsGroup); + s.beginGroup(kSettingsGroup + settings_postfix_); ui_->quality_slider->setValue(s.value("quality", 8).toInt()); ui_->bitrate_slider->setValue(s.value("bitrate", 0).toInt() / 1000); @@ -46,7 +46,7 @@ void TranscoderOptionsSpeex::Load() { void TranscoderOptionsSpeex::Save() { QSettings s; - s.beginGroup(kSettingsGroup); + s.beginGroup(kSettingsGroup + settings_postfix_); s.setValue("quality", ui_->quality_slider->value()); s.setValue("bitrate", ui_->bitrate_slider->value() * 1000); diff --git a/src/transcoder/transcoderoptionsvorbis.cpp b/src/transcoder/transcoderoptionsvorbis.cpp index 450bbfd2d..94f4faa75 100644 --- a/src/transcoder/transcoderoptionsvorbis.cpp +++ b/src/transcoder/transcoderoptionsvorbis.cpp @@ -31,7 +31,7 @@ TranscoderOptionsVorbis::~TranscoderOptionsVorbis() { delete ui_; } void TranscoderOptionsVorbis::Load() { QSettings s; - s.beginGroup(kSettingsGroup); + s.beginGroup(kSettingsGroup + settings_postfix_); #define GET_BITRATE(variable, property) \ int variable = s.value(property, -1).toInt(); \ @@ -51,7 +51,7 @@ void TranscoderOptionsVorbis::Load() { void TranscoderOptionsVorbis::Save() { QSettings s; - s.beginGroup(kSettingsGroup); + s.beginGroup(kSettingsGroup + settings_postfix_); #define GET_BITRATE(variable, ui_slider) \ int variable = ui_slider->value(); \ diff --git a/src/transcoder/transcoderoptionswma.cpp b/src/transcoder/transcoderoptionswma.cpp index db9944953..208c3d321 100644 --- a/src/transcoder/transcoderoptionswma.cpp +++ b/src/transcoder/transcoderoptionswma.cpp @@ -31,14 +31,14 @@ TranscoderOptionsWma::~TranscoderOptionsWma() { delete ui_; } void TranscoderOptionsWma::Load() { QSettings s; - s.beginGroup(kSettingsGroup); + s.beginGroup(kSettingsGroup + settings_postfix_); ui_->bitrate_slider->setValue(s.value("bitrate", 128000).toInt() / 1000); } void TranscoderOptionsWma::Save() { QSettings s; - s.beginGroup(kSettingsGroup); + s.beginGroup(kSettingsGroup + settings_postfix_); s.setValue("bitrate", ui_->bitrate_slider->value() * 1000); } diff --git a/src/ui/networkremotesettingspage.cpp b/src/ui/networkremotesettingspage.cpp index 01c59b8db..01277869c 100644 --- a/src/ui/networkremotesettingspage.cpp +++ b/src/ui/networkremotesettingspage.cpp @@ -20,6 +20,8 @@ #include "ui_networkremotesettingspage.h" #include "networkremote/networkremote.h" #include "networkremote/networkremotehelper.h" +#include "transcoder/transcoder.h" +#include "transcoder/transcoderoptionsdialog.h" #include #include @@ -29,12 +31,28 @@ const char* NetworkRemoteSettingsPage::kPlayStoreUrl = "https://play.google.com/store/apps/details?id=de.qspool.clementineremote"; +static bool ComparePresetsByName(const TranscoderPreset& left, + const TranscoderPreset& right) { + return left.name_ < right.name_; +} + NetworkRemoteSettingsPage::NetworkRemoteSettingsPage(SettingsDialog* dialog) : SettingsPage(dialog), ui_(new Ui_NetworkRemoteSettingsPage) { ui_->setupUi(this); setWindowIcon(IconLoader::Load("ipodtouchicon")); + connect(ui_->options, SIGNAL(clicked()), SLOT(Options())); + ui_->play_store->installEventFilter(this); + + // Get presets + QList presets = Transcoder::GetAllPresets(); + qSort(presets.begin(), presets.end(), ComparePresetsByName); + for (const TranscoderPreset& preset : presets) { + ui_->format->addItem( + QString("%1 (.%2)").arg(preset.name_, preset.extension_), + QVariant::fromValue(preset)); + } } NetworkRemoteSettingsPage::~NetworkRemoteSettingsPage() { delete ui_; } @@ -65,6 +83,17 @@ void NetworkRemoteSettingsPage::Load() { ui_->auth_code->setValue(s.value("auth_code", qrand() % 100000).toInt()); ui_->allow_downloads->setChecked(s.value("allow_downloads", false).toBool()); + ui_->convert_lossless->setChecked(s.value("convert_lossless", false).toBool()); + + // Load settings + QString last_output_format = s.value("last_output_format", "audio/x-vorbis").toString(); + for (int i = 0; i < ui_->format->count(); ++i) { + if (last_output_format == + ui_->format->itemData(i).value().codec_mimetype_) { + ui_->format->setCurrentIndex(i); + break; + } + } s.endGroup(); @@ -104,6 +133,11 @@ void NetworkRemoteSettingsPage::Save() { s.setValue("use_auth_code", ui_->use_auth_code->isChecked()); s.setValue("auth_code", ui_->auth_code->value()); s.setValue("allow_downloads", ui_->allow_downloads->isChecked()); + s.setValue("convert_lossless", ui_->convert_lossless->isChecked()); + + TranscoderPreset preset = ui_->format->itemData(ui_->format->currentIndex()) + .value(); + s.setValue("last_output_format", preset.codec_mimetype_); s.endGroup(); @@ -111,3 +145,14 @@ void NetworkRemoteSettingsPage::Save() { NetworkRemoteHelper::Instance()->ReloadSettings(); } } + +void NetworkRemoteSettingsPage::Options() { + TranscoderPreset preset = ui_->format->itemData(ui_->format->currentIndex()) + .value(); + + TranscoderOptionsDialog dialog(preset.type_, this); + dialog.set_settings_postfix(NetworkRemote::kTranscoderSettingPostfix); + if (dialog.is_valid()) { + dialog.exec(); + } +} diff --git a/src/ui/networkremotesettingspage.h b/src/ui/networkremotesettingspage.h index 3a8a06d8d..1ac7af4c0 100644 --- a/src/ui/networkremotesettingspage.h +++ b/src/ui/networkremotesettingspage.h @@ -35,6 +35,9 @@ class NetworkRemoteSettingsPage : public SettingsPage { protected: bool eventFilter(QObject* object, QEvent* event); + private slots: + void Options(); + private: static const char* kPlayStoreUrl; diff --git a/src/ui/networkremotesettingspage.ui b/src/ui/networkremotesettingspage.ui index bcf0487f6..2f9e1cada 100644 --- a/src/ui/networkremotesettingspage.ui +++ b/src/ui/networkremotesettingspage.ui @@ -6,8 +6,8 @@ 0 0 - 385 - 528 + 421 + 664 @@ -30,6 +30,9 @@ Settings + + QFormLayout::AllNonFixedFieldsGrow + @@ -98,17 +101,7 @@ - - - - Allow a client to download music from this computer. - - - Allow downloads - - - - + Enter this IP in the App to connect to Clementine. @@ -118,13 +111,67 @@ - + 127.0.0.1 + + + + Allow a client to download music from this computer. + + + Allow downloads + + + + + + + false + + + Download settings + + + + + + Convert lossless audiofiles before sending them to the remote. + + + Convert lossless files + + + + + + + false + + + Audio format + + + + + + Options... + + + + + + + + + + + + @@ -248,5 +295,37 @@ + + allow_downloads + toggled(bool) + download_settings_container + setEnabled(bool) + + + 196 + 160 + + + 117 + 205 + + + + + convert_lossless + toggled(bool) + format_container + setEnabled(bool) + + + 218 + 212 + + + 218 + 262 + + + diff --git a/src/ui/ripcd.cpp b/src/ui/ripcd.cpp index e5922f8a4..bf8a329a8 100644 --- a/src/ui/ripcd.cpp +++ b/src/ui/ripcd.cpp @@ -100,8 +100,8 @@ RipCD::RipCD(QWidget* parent) connect(cancel_button_, SIGNAL(clicked()), SLOT(Cancel())); connect(close_button_, SIGNAL(clicked()), SLOT(hide())); - connect(transcoder_, SIGNAL(JobComplete(QString, bool)), - SLOT(TranscodingJobComplete(QString, bool))); + connect(transcoder_, SIGNAL(JobComplete(QString, QString, bool)), + SLOT(TranscodingJobComplete(QString, QString, bool))); connect(transcoder_, SIGNAL(AllJobsComplete()), SLOT(AllTranscodingJobsComplete())); connect(transcoder_, SIGNAL(LogLine(QString)), SLOT(LogLine(QString))); @@ -334,7 +334,7 @@ void RipCD::AddTrack(int track_number, const QString& title, tracks_.append(track); } -void RipCD::TranscodingJobComplete(const QString& filename, bool success) { +void RipCD::TranscodingJobComplete(const QString& input, const QString& output, bool success) { (*(success ? &finished_success_ : &finished_failed_))++; emit(SignalUpdateProgress()); } diff --git a/src/ui/ripcd.h b/src/ui/ripcd.h index d919c54a9..71bb40746 100644 --- a/src/ui/ripcd.h +++ b/src/ui/ripcd.h @@ -124,7 +124,7 @@ signals: void UpdateProgress(); void ThreadedTranscoding(); void ClickedRipButton(); - void TranscodingJobComplete(const QString& filename, bool success); + void TranscodingJobComplete(const QString& input, const QString& output, bool success); void AllTranscodingJobsComplete(); void FileTagged(TagReaderReply* reply); void Options();