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:
parent
8aa78af8f8
commit
ff172f6ed4
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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<Song>* songs);
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -46,9 +46,6 @@ signals:
|
||||
bool enqueue);
|
||||
void RemoveSongs(int id, const QList<int>& 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);
|
||||
|
||||
|
@ -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*)));
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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<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) {
|
||||
// Get a temporary file name
|
||||
QString temp_file_name = Utilities::GetTemporaryFileName();
|
||||
|
@ -30,14 +30,6 @@
|
||||
|
||||
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 {
|
||||
int id_;
|
||||
QString query_;
|
||||
@ -58,6 +50,9 @@ class OutgoingDataCreator : public QObject {
|
||||
|
||||
void SetClients(QList<RemoteClient*>* 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<RemoteClient*, QQueue<DownloadItem> > 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
|
||||
|
@ -24,7 +24,10 @@
|
||||
#include <QSettings>
|
||||
|
||||
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);
|
||||
|
@ -5,6 +5,8 @@
|
||||
#include <QTcpSocket>
|
||||
#include <QBuffer>
|
||||
|
||||
#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
|
||||
|
336
src/networkremote/songsender.cpp
Normal file
336
src/networkremote/songsender.cpp
Normal 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);
|
||||
}
|
||||
}
|
62
src/networkremote/songsender.h
Normal file
62
src/networkremote/songsender.h
Normal 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
|
@ -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_--;
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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<JobState*>(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<QString, float> 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(
|
||||
|
@ -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<TranscoderPreset> 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<QString, float> 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<Job> queued_jobs_;
|
||||
JobStateList current_jobs_;
|
||||
QString settings_postfix_;
|
||||
};
|
||||
|
||||
#endif // TRANSCODER_H
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,8 @@ class TranscoderOptionsDialog : public QDialog {
|
||||
|
||||
void accept();
|
||||
|
||||
void set_settings_postfix(const QString& settings_postfix);
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent* e);
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -27,6 +27,8 @@ class TranscoderOptionsInterface : public QWidget {
|
||||
|
||||
virtual void Load() = 0;
|
||||
virtual void Save() = 0;
|
||||
|
||||
QString settings_postfix_;
|
||||
};
|
||||
|
||||
#endif // TRANSCODEROPTIONSINTERFACE_H
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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(); \
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 <QDesktopServices>
|
||||
#include <QSettings>
|
||||
@ -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<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_; }
|
||||
@ -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<TranscoderPreset>().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<TranscoderPreset>();
|
||||
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<TranscoderPreset>();
|
||||
|
||||
TranscoderOptionsDialog dialog(preset.type_, this);
|
||||
dialog.set_settings_postfix(NetworkRemote::kTranscoderSettingPostfix);
|
||||
if (dialog.is_valid()) {
|
||||
dialog.exec();
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,9 @@ class NetworkRemoteSettingsPage : public SettingsPage {
|
||||
protected:
|
||||
bool eventFilter(QObject* object, QEvent* event);
|
||||
|
||||
private slots:
|
||||
void Options();
|
||||
|
||||
private:
|
||||
static const char* kPlayStoreUrl;
|
||||
|
||||
|
@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>385</width>
|
||||
<height>528</height>
|
||||
<width>421</width>
|
||||
<height>664</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@ -30,6 +30,9 @@
|
||||
<string>Settings</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_remote_port">
|
||||
<property name="minimumSize">
|
||||
@ -98,17 +101,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<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">
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="toolTip">
|
||||
<string>Enter this IP in the App to connect to Clementine.</string>
|
||||
@ -118,13 +111,67 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<item row="6" column="1">
|
||||
<widget class="QLabel" name="ip_address">
|
||||
<property name="text">
|
||||
<string notr="true">127.0.0.1</string>
|
||||
</property>
|
||||
</widget>
|
||||
</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>
|
||||
</widget>
|
||||
</item>
|
||||
@ -248,5 +295,37 @@
|
||||
</hint>
|
||||
</hints>
|
||||
</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>
|
||||
</ui>
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user