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();