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")
This commit is contained in:
Andreas 2014-11-13 22:31:49 +01:00
parent 8aa78af8f8
commit ff172f6ed4
36 changed files with 647 additions and 311 deletions

View File

@ -55,6 +55,7 @@ enum MsgType {
LIBRARY_CHUNK = 52; LIBRARY_CHUNK = 52;
DOWNLOAD_TOTAL_SIZE = 53; DOWNLOAD_TOTAL_SIZE = 53;
GLOBAL_SEARCH_RESULT = 54; GLOBAL_SEARCH_RESULT = 54;
TRANSCODING_FILES = 55;
} }
// Valid Engine states // Valid Engine states
@ -297,9 +298,14 @@ message ResponseGlobalSearch {
repeated SongMetadata song_metadata = 4; repeated SongMetadata song_metadata = 4;
} }
message ResponseTranscoderStatus {
optional int32 processed = 1;
optional int32 total = 2;
}
// The message itself // The message itself
message Message { 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 MsgType type = 2 [default=UNKNOWN]; // What data is in the message?
optional RequestConnect request_connect = 21; optional RequestConnect request_connect = 21;
@ -333,4 +339,5 @@ message Message {
optional ResponseLibraryChunk response_library_chunk = 34; optional ResponseLibraryChunk response_library_chunk = 34;
optional ResponseDownloadTotalSize response_download_total_size = 36; optional ResponseDownloadTotalSize response_download_total_size = 36;
optional ResponseGlobalSearch response_global_search = 38; optional ResponseGlobalSearch response_global_search = 38;
optional ResponseTranscoderStatus response_transcoder_status = 39;
} }

View File

@ -233,6 +233,7 @@ set(SOURCES
networkremote/networkremotehelper.cpp networkremote/networkremotehelper.cpp
networkremote/outgoingdatacreator.cpp networkremote/outgoingdatacreator.cpp
networkremote/remoteclient.cpp networkremote/remoteclient.cpp
networkremote/songsender.cpp
networkremote/zeroconf.cpp networkremote/zeroconf.cpp
playlist/dynamicplaylistcontrols.cpp playlist/dynamicplaylistcontrols.cpp
@ -534,6 +535,7 @@ set(HEADERS
networkremote/incomingdataparser.h networkremote/incomingdataparser.h
networkremote/outgoingdatacreator.h networkremote/outgoingdatacreator.h
networkremote/remoteclient.h networkremote/remoteclient.h
networkremote/songsender.h
playlist/dynamicplaylistcontrols.h playlist/dynamicplaylistcontrols.h
playlist/playlist.h playlist/playlist.h

View File

@ -71,8 +71,8 @@ void Organise::Start() {
thread_ = new QThread; thread_ = new QThread;
connect(thread_, SIGNAL(started()), SLOT(ProcessSomeFiles())); connect(thread_, SIGNAL(started()), SLOT(ProcessSomeFiles()));
connect(transcoder_, SIGNAL(JobComplete(QString, bool)), connect(transcoder_, SIGNAL(JobComplete(QString, QString, bool)),
SLOT(FileTranscoded(QString, bool))); SLOT(FileTranscoded(QString, QString, bool)));
moveToThread(thread_); moveToThread(thread_);
thread_->start(); thread_->start();
@ -272,13 +272,13 @@ void Organise::UpdateProgress() {
task_manager_->SetTaskProgress(task_id_, progress, total); task_manager_->SetTaskProgress(task_id_, progress, total);
} }
void Organise::FileTranscoded(const QString& filename, bool success) { void Organise::FileTranscoded(const QString& input, const QString& output, bool success) {
qLog(Info) << "File finished" << filename << success; qLog(Info) << "File finished" << input << success;
transcode_progress_timer_.stop(); transcode_progress_timer_.stop();
Task task = tasks_transcoding_.take(filename); Task task = tasks_transcoding_.take(input);
if (!success) { if (!success) {
files_with_errors_ << filename; files_with_errors_ << input;
} else { } else {
tasks_pending_ << task; tasks_pending_ << task;
} }

View File

@ -61,7 +61,7 @@ signals:
private slots: private slots:
void ProcessSomeFiles(); void ProcessSomeFiles();
void FileTranscoded(const QString& filename, bool success); void FileTranscoded(const QString& input, const QString& output, bool success);
private: private:
void SetSongProgress(float progress, bool transcoded = false); void SetSongProgress(float progress, bool transcoded = false);

View File

@ -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) { int CompareSongsName(const Song& song1, const Song& song2) {
return song1.PrettyTitleWithArtist().localeAwareCompare( return song1.PrettyTitleWithArtist().localeAwareCompare(
song2.PrettyTitleWithArtist()) < 0; song2.PrettyTitleWithArtist()) < 0;

View File

@ -75,6 +75,7 @@ class Song {
// Don't change these values - they're stored in the database, and defined // Don't change these values - they're stored in the database, and defined
// in the tag reader protobuf. // in the tag reader protobuf.
// If a new lossless file is added, also add it to IsFileLossless().
enum FileType { enum FileType {
Type_Unknown = 0, Type_Unknown = 0,
Type_Asf = 1, Type_Asf = 1,
@ -94,6 +95,7 @@ class Song {
}; };
static QString TextForFiletype(FileType type); static QString TextForFiletype(FileType type);
QString TextForFiletype() const { return TextForFiletype(filetype()); } QString TextForFiletype() const { return TextForFiletype(filetype()); }
bool IsFileLossless() const;
// Sort songs alphabetically using their pretty title // Sort songs alphabetically using their pretty title
static void SortSongsListAlphabetically(QList<Song>* songs); static void SortSongsListAlphabetically(QList<Song>* songs);

View File

@ -213,6 +213,8 @@ QString GetTemporaryFileName() {
QString file; QString file;
{ {
QTemporaryFile tempfile; QTemporaryFile tempfile;
// Do not delete the file, we want to do something with it
tempfile.setAutoRemove(false);
tempfile.open(); tempfile.open();
file = tempfile.fileName(); file = tempfile.fileName();
} }

View File

@ -158,10 +158,10 @@ void IncomingDataParser::Parse(const pb::remote::Message& msg) {
emit GetLyrics(); emit GetLyrics();
break; break;
case pb::remote::DOWNLOAD_SONGS: case pb::remote::DOWNLOAD_SONGS:
emit SendSongs(msg.request_download_songs(), client); client->song_sender()->SendSongs(msg.request_download_songs());
break; break;
case pb::remote::SONG_OFFER_RESPONSE: case pb::remote::SONG_OFFER_RESPONSE:
emit ResponseSongOffer(client, msg.response_song_offer().accepted()); client->song_sender()->ResponseSongOffer(msg.response_song_offer().accepted());
break; break;
case pb::remote::GET_LIBRARY: case pb::remote::GET_LIBRARY:
emit SendLibrary(client); emit SendLibrary(client);

View File

@ -46,9 +46,6 @@ signals:
bool enqueue); bool enqueue);
void RemoveSongs(int id, const QList<int>& indices); void RemoveSongs(int id, const QList<int>& indices);
void SeekTo(int seconds); void SeekTo(int seconds);
void SendSongs(const pb::remote::RequestDownloadSongs& request,
RemoteClient* client);
void ResponseSongOffer(RemoteClient* client, bool accepted);
void SendLibrary(RemoteClient* client); void SendLibrary(RemoteClient* client);
void RateCurrentSong(double); void RateCurrentSong(double);

View File

@ -29,6 +29,7 @@
const char* NetworkRemote::kSettingsGroup = "NetworkRemote"; const char* NetworkRemote::kSettingsGroup = "NetworkRemote";
const quint16 NetworkRemote::kDefaultServerPort = 5500; const quint16 NetworkRemote::kDefaultServerPort = 5500;
const char* NetworkRemote::kTranscoderSettingPostfix = "/NetworkRemote";
NetworkRemote::NetworkRemote(Application* app, QObject* parent) NetworkRemote::NetworkRemote(Application* app, QObject* parent)
: QObject(parent), signals_connected_(false), app_(app) {} : QObject(parent), signals_connected_(false), app_(app) {}
@ -158,15 +159,6 @@ void NetworkRemote::AcceptConnection() {
connect(incoming_data_parser_.get(), SIGNAL(GetLyrics()), connect(incoming_data_parser_.get(), SIGNAL(GetLyrics()),
outgoing_data_creator_.get(), SLOT(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*)), connect(incoming_data_parser_.get(), SIGNAL(SendLibrary(RemoteClient*)),
outgoing_data_creator_.get(), SLOT(SendLibrary(RemoteClient*))); outgoing_data_creator_.get(), SLOT(SendLibrary(RemoteClient*)));

View File

@ -17,6 +17,7 @@ class NetworkRemote : public QObject {
public: public:
static const char* kSettingsGroup; static const char* kSettingsGroup;
static const quint16 kDefaultServerPort; static const quint16 kDefaultServerPort;
static const char* kTranscoderSettingPostfix;
explicit NetworkRemote(Application* app, QObject* parent = nullptr); explicit NetworkRemote(Application* app, QObject* parent = nullptr);
~NetworkRemote(); ~NetworkRemote();

View File

@ -152,7 +152,6 @@ void OutgoingDataCreator::SendDataToClients(pb::remote::Message* msg) {
if (client->isDownloader()) { if (client->isDownloader()) {
if (client->State() != QTcpSocket::ConnectedState) { if (client->State() != QTcpSocket::ConnectedState) {
clients_->removeAt(clients_->indexOf(client)); clients_->removeAt(clients_->indexOf(client));
download_queue_.remove(client);
delete client; delete client;
} }
continue; continue;
@ -579,224 +578,6 @@ void OutgoingDataCreator::SendLyrics(int id,
results_.take(id); results_.take(id);
} }
void OutgoingDataCreator::SendSongs(
const pb::remote::RequestDownloadSongs& request, RemoteClient* client) {
if (!download_queue_.contains(client)) {
download_queue_.insert(client, QQueue<DownloadItem>());
}
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) { void OutgoingDataCreator::SendLibrary(RemoteClient* client) {
// Get a temporary file name // Get a temporary file name
QString temp_file_name = Utilities::GetTemporaryFileName(); QString temp_file_name = Utilities::GetTemporaryFileName();

View File

@ -30,14 +30,6 @@
typedef QList<SongInfoProvider*> ProviderList; typedef QList<SongInfoProvider*> 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 { struct GlobalSearchRequest {
int id_; int id_;
QString query_; QString query_;
@ -58,6 +50,9 @@ class OutgoingDataCreator : public QObject {
void SetClients(QList<RemoteClient*>* clients); void SetClients(QList<RemoteClient*>* clients);
static void CreateSong(const Song& song, const QImage& art, const int index,
pb::remote::SongMetadata* song_metadata);
public slots: public slots:
void SendClementineInfo(); void SendClementineInfo();
void SendAllPlaylists(); void SendAllPlaylists();
@ -82,9 +77,6 @@ class OutgoingDataCreator : public QObject {
void DisconnectAllClients(); void DisconnectAllClients();
void GetLyrics(); void GetLyrics();
void SendLyrics(int id, const SongInfoFetcher::Result& result); 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 SendLibrary(RemoteClient* client);
void EnableKittens(bool aww); void EnableKittens(bool aww);
void SendKitten(const QImage& kitten); void SendKitten(const QImage& kitten);
@ -103,7 +95,6 @@ class OutgoingDataCreator : public QObject {
QTimer* keep_alive_timer_; QTimer* keep_alive_timer_;
QTimer* track_position_timer_; QTimer* track_position_timer_;
int keep_alive_timeout_; int keep_alive_timeout_;
QMap<RemoteClient*, QQueue<DownloadItem> > download_queue_;
int last_track_position_; int last_track_position_;
bool aww_; bool aww_;
@ -116,17 +107,8 @@ class OutgoingDataCreator : public QObject {
void SendDataToClients(pb::remote::Message* msg); void SendDataToClients(pb::remote::Message* msg);
void SetEngineState(pb::remote::ResponseClementineInfo* 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(); void CheckEnabledProviders();
SongInfoProvider* ProviderByName(const QString& name) const; 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 #endif // OUTGOINGDATACREATOR_H

View File

@ -24,7 +24,10 @@
#include <QSettings> #include <QSettings>
RemoteClient::RemoteClient(Application* app, QTcpSocket* client) 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 // Open the buffer
buffer_.setData(QByteArray()); buffer_.setData(QByteArray());
buffer_.open(QIODevice::ReadWrite); buffer_.open(QIODevice::ReadWrite);

View File

@ -5,6 +5,8 @@
#include <QTcpSocket> #include <QTcpSocket>
#include <QBuffer> #include <QBuffer>
#include "songsender.h"
#include "core/application.h" #include "core/application.h"
#include "remotecontrolmessages.pb.h" #include "remotecontrolmessages.pb.h"
@ -21,6 +23,8 @@ class RemoteClient : public QObject {
bool isDownloader() { return downloader_; } bool isDownloader() { return downloader_; }
void DisconnectClient(pb::remote::ReasonDisconnect reason); void DisconnectClient(pb::remote::ReasonDisconnect reason);
SongSender* song_sender() { return song_sender_; }
private slots: private slots:
void IncomingData(); void IncomingData();
@ -45,6 +49,7 @@ signals:
bool reading_protobuf_; bool reading_protobuf_;
quint32 expected_length_; quint32 expected_length_;
QBuffer buffer_; QBuffer buffer_;
SongSender* song_sender_;
}; };
#endif // REMOTECLIENT_H #endif // REMOTECLIENT_H

View File

@ -0,0 +1,336 @@
/* This file is part of Clementine.
Copyright 2012, Andreas Muttscheller <asfa194@gmail.com>
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 <http://www.gnu.org/licenses/>.
*/
#include "songsender.h"
#include "networkremote.h"
#include <QFileInfo>
#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<TranscoderPreset> presets = transcoder_->GetAllPresets();
for (int i = 0; i<presets.count(); ++i) {
if (last_output_format == presets.at(i).codec_mimetype_) {
transcoder_preset_ = presets.at(i);
break;
}
}
qLog(Debug) << "Transcoder preset" << transcoder_preset_.codec_mimetype_;
connect(transcoder_, SIGNAL(JobComplete(QString, QString, bool)),
SLOT(TranscodeJobComplete(QString, QString, bool)));
connect(transcoder_, SIGNAL(AllJobsComplete()), SLOT(StartTransfer()));
total_transcode_ = 0;
}
void SongSender::SendSongs(const pb::remote::RequestDownloadSongs& request) {
Song current_song = app_->player()->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);
}
}

View File

@ -0,0 +1,62 @@
#ifndef SONGSENDER_H
#define SONGSENDER_H
#include <QMap>
#include <QQueue>
#include <QUrl>
#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<DownloadItem> download_queue_;
QMap<QString, QString> 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

View File

@ -108,8 +108,8 @@ TranscodeDialog::TranscodeDialog(QWidget* parent)
connect(ui_->options, SIGNAL(clicked()), SLOT(Options())); connect(ui_->options, SIGNAL(clicked()), SLOT(Options()));
connect(ui_->select, SIGNAL(clicked()), SLOT(AddDestination())); connect(ui_->select, SIGNAL(clicked()), SLOT(AddDestination()));
connect(transcoder_, SIGNAL(JobComplete(QString, bool)), connect(transcoder_, SIGNAL(JobComplete(QString, QString, bool)),
SLOT(JobComplete(QString, bool))); SLOT(JobComplete(QString, QString, bool)));
connect(transcoder_, SIGNAL(LogLine(QString)), SLOT(LogLine(QString))); connect(transcoder_, SIGNAL(LogLine(QString)), SLOT(LogLine(QString)));
connect(transcoder_, SIGNAL(AllJobsComplete()), SLOT(AllJobsComplete())); connect(transcoder_, SIGNAL(AllJobsComplete()), SLOT(AllJobsComplete()));
} }
@ -171,7 +171,7 @@ void TranscodeDialog::Cancel() {
SetWorking(false); 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_))++; (*(success ? &finished_success_ : &finished_failed_))++;
queued_--; queued_--;

View File

@ -49,7 +49,7 @@ class TranscodeDialog : public QDialog {
void Remove(); void Remove();
void Start(); void Start();
void Cancel(); 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 LogLine(const QString& message);
void AllJobsComplete(); void AllJobsComplete();
void Options(); void Options();

View File

@ -28,6 +28,7 @@
#include "core/logging.h" #include "core/logging.h"
#include "core/signalchecker.h" #include "core/signalchecker.h"
#include "core/utilities.h"
using std::shared_ptr; using std::shared_ptr;
@ -193,14 +194,16 @@ void Transcoder::JobState::PostFinished(bool success) {
new Transcoder::JobFinishedEvent(this, success)); new Transcoder::JobFinishedEvent(this, success));
} }
Transcoder::Transcoder(QObject* parent) Transcoder::Transcoder(QObject* parent, const QString& settings_postfix)
: QObject(parent), max_threads_(QThread::idealThreadCount()) { : QObject(parent),
max_threads_(QThread::idealThreadCount()),
settings_postfix_(settings_postfix) {
if (JobFinishedEvent::sEventType == -1) if (JobFinishedEvent::sEventType == -1)
JobFinishedEvent::sEventType = QEvent::registerEventType(); JobFinishedEvent::sEventType = QEvent::registerEventType();
// Initialise some settings for the lamemp3enc element. // Initialise some settings for the lamemp3enc element.
QSettings s; QSettings s;
s.beginGroup("Transcoder/lamemp3enc"); s.beginGroup("Transcoder/lamemp3enc" + settings_postfix_);
if (s.value("target").isNull()) { if (s.value("target").isNull()) {
s.setValue("target", 1); // 1 == bitrate s.setValue("target", 1); // 1 == bitrate
@ -301,6 +304,15 @@ void Transcoder::AddJob(const QString& input, const TranscoderPreset& preset,
queued_jobs_ << job; 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() { void Transcoder::Start() {
emit LogLine(tr("Transcoding %1 files using %2 threads") emit LogLine(tr("Transcoding %1 files using %2 threads")
.arg(queued_jobs_.count()) .arg(queued_jobs_.count())
@ -327,11 +339,11 @@ Transcoder::StartJobStatus Transcoder::MaybeStartNextJob() {
return StartedSuccessfully; return StartedSuccessfully;
} }
emit JobComplete(job.input, false); emit JobComplete(job.input, job.output, false);
return FailedToStart; return FailedToStart;
} }
void Transcoder::NewPadCallback(GstElement*, GstPad* pad, gboolean, void Transcoder::NewPadCallback(GstElement*, GstPad* pad,
gpointer data) { gpointer data) {
JobState* state = reinterpret_cast<JobState*>(data); JobState* state = reinterpret_cast<JobState*>(data);
GstPad* const audiopad = GstPad* const audiopad =
@ -446,7 +458,7 @@ bool Transcoder::StartJob(const Job& job) {
// Set callbacks // Set callbacks
state->convert_element_ = convert; 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_)), gst_bus_set_sync_handler(gst_pipeline_get_bus(GST_PIPELINE(state->pipeline_)),
BusCallbackSync, state.get(), nullptr); BusCallbackSync, state.get(), nullptr);
state->bus_callback_id_ = gst_bus_add_watch( state->bus_callback_id_ = gst_bus_add_watch(
@ -487,7 +499,8 @@ bool Transcoder::event(QEvent* e) {
return true; 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 // Remove event handlers from the gstreamer pipeline so they don't get
// called after the pipeline is shutting down // called after the pipeline is shutting down
@ -500,7 +513,7 @@ bool Transcoder::event(QEvent* e) {
current_jobs_.erase(it); current_jobs_.erase(it);
// Emit the finished signal // Emit the finished signal
emit JobComplete(filename, finished_event->success_); emit JobComplete(input, output, finished_event->success_);
// Start some more jobs // Start some more jobs
MaybeStartNextJob(); MaybeStartNextJob();
@ -559,7 +572,7 @@ QMap<QString, float> Transcoder::GetProgress() const {
void Transcoder::SetElementProperties(const QString& name, GObject* object) { void Transcoder::SetElementProperties(const QString& name, GObject* object) {
QSettings s; QSettings s;
s.beginGroup("Transcoder/" + name); s.beginGroup("Transcoder/" + name + settings_postfix_);
guint properties_count = 0; guint properties_count = 0;
GParamSpec** properties = g_object_class_list_properties( GParamSpec** properties = g_object_class_list_properties(

View File

@ -47,7 +47,7 @@ class Transcoder : public QObject {
Q_OBJECT Q_OBJECT
public: public:
Transcoder(QObject* parent = nullptr); Transcoder(QObject* parent = nullptr, const QString& settings_postfix = "");
static TranscoderPreset PresetForFileType(Song::FileType type); static TranscoderPreset PresetForFileType(Song::FileType type);
static QList<TranscoderPreset> GetAllPresets(); static QList<TranscoderPreset> GetAllPresets();
@ -58,6 +58,7 @@ class Transcoder : public QObject {
void AddJob(const QString& input, const TranscoderPreset& preset, void AddJob(const QString& input, const TranscoderPreset& preset,
const QString& output = QString()); const QString& output = QString());
void AddTemporaryJob(const QString& input, const TranscoderPreset& preset);
QMap<QString, float> GetProgress() const; QMap<QString, float> GetProgress() const;
int QueuedJobsCount() const { return queued_jobs_.count(); } int QueuedJobsCount() const { return queued_jobs_.count(); }
@ -67,7 +68,7 @@ class Transcoder : public QObject {
void Cancel(); void Cancel();
signals: signals:
void JobComplete(const QString& filename, bool success); void JobComplete(const QString& input, const QString& output, bool success);
void LogLine(const QString& message); void LogLine(const QString& message);
void AllJobsComplete(); void AllJobsComplete();
@ -132,7 +133,7 @@ signals:
GstElement* bin = nullptr); GstElement* bin = nullptr);
void SetElementProperties(const QString& name, GObject* element); 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 gboolean BusCallback(GstBus*, GstMessage* msg, gpointer data);
static GstBusSyncReply BusCallbackSync(GstBus*, GstMessage* msg, static GstBusSyncReply BusCallbackSync(GstBus*, GstMessage* msg,
gpointer data); gpointer data);
@ -143,6 +144,7 @@ signals:
int max_threads_; int max_threads_;
QList<Job> queued_jobs_; QList<Job> queued_jobs_;
JobStateList current_jobs_; JobStateList current_jobs_;
QString settings_postfix_;
}; };
#endif // TRANSCODER_H #endif // TRANSCODER_H

View File

@ -31,7 +31,7 @@ TranscoderOptionsAAC::~TranscoderOptionsAAC() { delete ui_; }
void TranscoderOptionsAAC::Load() { void TranscoderOptionsAAC::Load() {
QSettings s; QSettings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup + settings_postfix_);
ui_->bitrate_slider->setValue(s.value("bitrate", 128000).toInt() / 1000); ui_->bitrate_slider->setValue(s.value("bitrate", 128000).toInt() / 1000);
ui_->profile->setCurrentIndex(s.value("profile", 2).toInt() - 1); ui_->profile->setCurrentIndex(s.value("profile", 2).toInt() - 1);
@ -42,7 +42,7 @@ void TranscoderOptionsAAC::Load() {
void TranscoderOptionsAAC::Save() { void TranscoderOptionsAAC::Save() {
QSettings s; QSettings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup + settings_postfix_);
s.setValue("bitrate", ui_->bitrate_slider->value() * 1000); s.setValue("bitrate", ui_->bitrate_slider->value() * 1000);
s.setValue("profile", ui_->profile->currentIndex() + 1); s.setValue("profile", ui_->profile->currentIndex() + 1);

View File

@ -79,3 +79,9 @@ void TranscoderOptionsDialog::accept() {
} }
QDialog::accept(); QDialog::accept();
} }
void TranscoderOptionsDialog::set_settings_postfix(const QString &settings_postfix) {
if (options_) {
options_->settings_postfix_ = settings_postfix;
}
}

View File

@ -36,6 +36,8 @@ class TranscoderOptionsDialog : public QDialog {
void accept(); void accept();
void set_settings_postfix(const QString& settings_postfix);
protected: protected:
void showEvent(QShowEvent* e); void showEvent(QShowEvent* e);

View File

@ -31,14 +31,14 @@ TranscoderOptionsFlac::~TranscoderOptionsFlac() { delete ui_; }
void TranscoderOptionsFlac::Load() { void TranscoderOptionsFlac::Load() {
QSettings s; QSettings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup + settings_postfix_);
ui_->quality->setValue(s.value("quality", 5).toInt()); ui_->quality->setValue(s.value("quality", 5).toInt());
} }
void TranscoderOptionsFlac::Save() { void TranscoderOptionsFlac::Save() {
QSettings s; QSettings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup + settings_postfix_);
s.setValue("quality", ui_->quality->value()); s.setValue("quality", ui_->quality->value());
} }

View File

@ -27,6 +27,8 @@ class TranscoderOptionsInterface : public QWidget {
virtual void Load() = 0; virtual void Load() = 0;
virtual void Save() = 0; virtual void Save() = 0;
QString settings_postfix_;
}; };
#endif // TRANSCODEROPTIONSINTERFACE_H #endif // TRANSCODEROPTIONSINTERFACE_H

View File

@ -36,7 +36,7 @@ TranscoderOptionsMP3::~TranscoderOptionsMP3() { delete ui_; }
void TranscoderOptionsMP3::Load() { void TranscoderOptionsMP3::Load() {
QSettings s; QSettings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup + settings_postfix_);
; ;
if (s.value("target", 1).toInt() == 0) { if (s.value("target", 1).toInt() == 0) {
@ -55,7 +55,7 @@ void TranscoderOptionsMP3::Load() {
void TranscoderOptionsMP3::Save() { void TranscoderOptionsMP3::Save() {
QSettings s; QSettings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup + settings_postfix_);
s.setValue("target", ui_->target_quality->isChecked() ? 0 : 1); s.setValue("target", ui_->target_quality->isChecked() ? 0 : 1);
s.setValue("quality", ui_->quality_spinbox->value()); s.setValue("quality", ui_->quality_spinbox->value());

View File

@ -34,14 +34,14 @@ TranscoderOptionsOpus::~TranscoderOptionsOpus() { delete ui_; }
void TranscoderOptionsOpus::Load() { void TranscoderOptionsOpus::Load() {
QSettings s; QSettings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup + settings_postfix_);
ui_->bitrate_slider->setValue(s.value("bitrate", 128000).toInt() / 1000); ui_->bitrate_slider->setValue(s.value("bitrate", 128000).toInt() / 1000);
} }
void TranscoderOptionsOpus::Save() { void TranscoderOptionsOpus::Save() {
QSettings s; QSettings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup + settings_postfix_);
s.setValue("bitrate", ui_->bitrate_slider->value() * 1000); s.setValue("bitrate", ui_->bitrate_slider->value() * 1000);
} }

View File

@ -31,7 +31,7 @@ TranscoderOptionsSpeex::~TranscoderOptionsSpeex() { delete ui_; }
void TranscoderOptionsSpeex::Load() { void TranscoderOptionsSpeex::Load() {
QSettings s; QSettings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup + settings_postfix_);
ui_->quality_slider->setValue(s.value("quality", 8).toInt()); ui_->quality_slider->setValue(s.value("quality", 8).toInt());
ui_->bitrate_slider->setValue(s.value("bitrate", 0).toInt() / 1000); ui_->bitrate_slider->setValue(s.value("bitrate", 0).toInt() / 1000);
@ -46,7 +46,7 @@ void TranscoderOptionsSpeex::Load() {
void TranscoderOptionsSpeex::Save() { void TranscoderOptionsSpeex::Save() {
QSettings s; QSettings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup + settings_postfix_);
s.setValue("quality", ui_->quality_slider->value()); s.setValue("quality", ui_->quality_slider->value());
s.setValue("bitrate", ui_->bitrate_slider->value() * 1000); s.setValue("bitrate", ui_->bitrate_slider->value() * 1000);

View File

@ -31,7 +31,7 @@ TranscoderOptionsVorbis::~TranscoderOptionsVorbis() { delete ui_; }
void TranscoderOptionsVorbis::Load() { void TranscoderOptionsVorbis::Load() {
QSettings s; QSettings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup + settings_postfix_);
#define GET_BITRATE(variable, property) \ #define GET_BITRATE(variable, property) \
int variable = s.value(property, -1).toInt(); \ int variable = s.value(property, -1).toInt(); \
@ -51,7 +51,7 @@ void TranscoderOptionsVorbis::Load() {
void TranscoderOptionsVorbis::Save() { void TranscoderOptionsVorbis::Save() {
QSettings s; QSettings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup + settings_postfix_);
#define GET_BITRATE(variable, ui_slider) \ #define GET_BITRATE(variable, ui_slider) \
int variable = ui_slider->value(); \ int variable = ui_slider->value(); \

View File

@ -31,14 +31,14 @@ TranscoderOptionsWma::~TranscoderOptionsWma() { delete ui_; }
void TranscoderOptionsWma::Load() { void TranscoderOptionsWma::Load() {
QSettings s; QSettings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup + settings_postfix_);
ui_->bitrate_slider->setValue(s.value("bitrate", 128000).toInt() / 1000); ui_->bitrate_slider->setValue(s.value("bitrate", 128000).toInt() / 1000);
} }
void TranscoderOptionsWma::Save() { void TranscoderOptionsWma::Save() {
QSettings s; QSettings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup + settings_postfix_);
s.setValue("bitrate", ui_->bitrate_slider->value() * 1000); s.setValue("bitrate", ui_->bitrate_slider->value() * 1000);
} }

View File

@ -20,6 +20,8 @@
#include "ui_networkremotesettingspage.h" #include "ui_networkremotesettingspage.h"
#include "networkremote/networkremote.h" #include "networkremote/networkremote.h"
#include "networkremote/networkremotehelper.h" #include "networkremote/networkremotehelper.h"
#include "transcoder/transcoder.h"
#include "transcoder/transcoderoptionsdialog.h"
#include <QDesktopServices> #include <QDesktopServices>
#include <QSettings> #include <QSettings>
@ -29,12 +31,28 @@
const char* NetworkRemoteSettingsPage::kPlayStoreUrl = const char* NetworkRemoteSettingsPage::kPlayStoreUrl =
"https://play.google.com/store/apps/details?id=de.qspool.clementineremote"; "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) NetworkRemoteSettingsPage::NetworkRemoteSettingsPage(SettingsDialog* dialog)
: SettingsPage(dialog), ui_(new Ui_NetworkRemoteSettingsPage) { : SettingsPage(dialog), ui_(new Ui_NetworkRemoteSettingsPage) {
ui_->setupUi(this); ui_->setupUi(this);
setWindowIcon(IconLoader::Load("ipodtouchicon")); setWindowIcon(IconLoader::Load("ipodtouchicon"));
connect(ui_->options, SIGNAL(clicked()), SLOT(Options()));
ui_->play_store->installEventFilter(this); ui_->play_store->installEventFilter(this);
// Get presets
QList<TranscoderPreset> 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_; } NetworkRemoteSettingsPage::~NetworkRemoteSettingsPage() { delete ui_; }
@ -65,6 +83,17 @@ void NetworkRemoteSettingsPage::Load() {
ui_->auth_code->setValue(s.value("auth_code", qrand() % 100000).toInt()); ui_->auth_code->setValue(s.value("auth_code", qrand() % 100000).toInt());
ui_->allow_downloads->setChecked(s.value("allow_downloads", false).toBool()); 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<TranscoderPreset>().codec_mimetype_) {
ui_->format->setCurrentIndex(i);
break;
}
}
s.endGroup(); s.endGroup();
@ -104,6 +133,11 @@ void NetworkRemoteSettingsPage::Save() {
s.setValue("use_auth_code", ui_->use_auth_code->isChecked()); s.setValue("use_auth_code", ui_->use_auth_code->isChecked());
s.setValue("auth_code", ui_->auth_code->value()); s.setValue("auth_code", ui_->auth_code->value());
s.setValue("allow_downloads", ui_->allow_downloads->isChecked()); 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<TranscoderPreset>();
s.setValue("last_output_format", preset.codec_mimetype_);
s.endGroup(); s.endGroup();
@ -111,3 +145,14 @@ void NetworkRemoteSettingsPage::Save() {
NetworkRemoteHelper::Instance()->ReloadSettings(); NetworkRemoteHelper::Instance()->ReloadSettings();
} }
} }
void NetworkRemoteSettingsPage::Options() {
TranscoderPreset preset = ui_->format->itemData(ui_->format->currentIndex())
.value<TranscoderPreset>();
TranscoderOptionsDialog dialog(preset.type_, this);
dialog.set_settings_postfix(NetworkRemote::kTranscoderSettingPostfix);
if (dialog.is_valid()) {
dialog.exec();
}
}

View File

@ -35,6 +35,9 @@ class NetworkRemoteSettingsPage : public SettingsPage {
protected: protected:
bool eventFilter(QObject* object, QEvent* event); bool eventFilter(QObject* object, QEvent* event);
private slots:
void Options();
private: private:
static const char* kPlayStoreUrl; static const char* kPlayStoreUrl;

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>385</width> <width>421</width>
<height>528</height> <height>664</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -30,6 +30,9 @@
<string>Settings</string> <string>Settings</string>
</property> </property>
<layout class="QFormLayout" name="formLayout"> <layout class="QFormLayout" name="formLayout">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label_remote_port"> <widget class="QLabel" name="label_remote_port">
<property name="minimumSize"> <property name="minimumSize">
@ -98,17 +101,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0" colspan="2"> <item row="6" column="0">
<widget class="QCheckBox" name="allow_downloads">
<property name="toolTip">
<string>Allow a client to download music from this computer.</string>
</property>
<property name="text">
<string>Allow downloads</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_2"> <widget class="QLabel" name="label_2">
<property name="toolTip"> <property name="toolTip">
<string>Enter this IP in the App to connect to Clementine.</string> <string>Enter this IP in the App to connect to Clementine.</string>
@ -118,13 +111,67 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="1"> <item row="6" column="1">
<widget class="QLabel" name="ip_address"> <widget class="QLabel" name="ip_address">
<property name="text"> <property name="text">
<string notr="true">127.0.0.1</string> <string notr="true">127.0.0.1</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0">
<widget class="QCheckBox" name="allow_downloads">
<property name="toolTip">
<string>Allow a client to download music from this computer.</string>
</property>
<property name="text">
<string>Allow downloads</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QGroupBox" name="download_settings_container">
<property name="enabled">
<bool>false</bool>
</property>
<property name="title">
<string>Download settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QCheckBox" name="convert_lossless">
<property name="toolTip">
<string>Convert lossless audiofiles before sending them to the remote.</string>
</property>
<property name="text">
<string>Convert lossless files</string>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="format_container">
<property name="enabled">
<bool>false</bool>
</property>
<property name="title">
<string>Audio format</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QPushButton" name="options">
<property name="text">
<string>Options...</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QComboBox" name="format"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -248,5 +295,37 @@
</hint> </hint>
</hints> </hints>
</connection> </connection>
<connection>
<sender>allow_downloads</sender>
<signal>toggled(bool)</signal>
<receiver>download_settings_container</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>196</x>
<y>160</y>
</hint>
<hint type="destinationlabel">
<x>117</x>
<y>205</y>
</hint>
</hints>
</connection>
<connection>
<sender>convert_lossless</sender>
<signal>toggled(bool)</signal>
<receiver>format_container</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>218</x>
<y>212</y>
</hint>
<hint type="destinationlabel">
<x>218</x>
<y>262</y>
</hint>
</hints>
</connection>
</connections> </connections>
</ui> </ui>

View File

@ -100,8 +100,8 @@ RipCD::RipCD(QWidget* parent)
connect(cancel_button_, SIGNAL(clicked()), SLOT(Cancel())); connect(cancel_button_, SIGNAL(clicked()), SLOT(Cancel()));
connect(close_button_, SIGNAL(clicked()), SLOT(hide())); connect(close_button_, SIGNAL(clicked()), SLOT(hide()));
connect(transcoder_, SIGNAL(JobComplete(QString, bool)), connect(transcoder_, SIGNAL(JobComplete(QString, QString, bool)),
SLOT(TranscodingJobComplete(QString, bool))); SLOT(TranscodingJobComplete(QString, QString, bool)));
connect(transcoder_, SIGNAL(AllJobsComplete()), connect(transcoder_, SIGNAL(AllJobsComplete()),
SLOT(AllTranscodingJobsComplete())); SLOT(AllTranscodingJobsComplete()));
connect(transcoder_, SIGNAL(LogLine(QString)), SLOT(LogLine(QString))); connect(transcoder_, SIGNAL(LogLine(QString)), SLOT(LogLine(QString)));
@ -334,7 +334,7 @@ void RipCD::AddTrack(int track_number, const QString& title,
tracks_.append(track); 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_))++; (*(success ? &finished_success_ : &finished_failed_))++;
emit(SignalUpdateProgress()); emit(SignalUpdateProgress());
} }

View File

@ -124,7 +124,7 @@ signals:
void UpdateProgress(); void UpdateProgress();
void ThreadedTranscoding(); void ThreadedTranscoding();
void ClickedRipButton(); void ClickedRipButton();
void TranscodingJobComplete(const QString& filename, bool success); void TranscodingJobComplete(const QString& input, const QString& output, bool success);
void AllTranscodingJobsComplete(); void AllTranscodingJobsComplete();
void FileTagged(TagReaderReply* reply); void FileTagged(TagReaderReply* reply);
void Options(); void Options();