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;
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;
}

View File

@ -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

View File

@ -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;
}

View File

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

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) {
return song1.PrettyTitleWithArtist().localeAwareCompare(
song2.PrettyTitleWithArtist()) < 0;

View File

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

View File

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

View File

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

View File

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

View File

@ -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*)));

View File

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

View File

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

View File

@ -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

View File

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

View File

@ -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

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_->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_--;

View File

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

View File

@ -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(

View File

@ -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

View File

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

View File

@ -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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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);
}

View File

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

View File

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

View File

@ -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);
}

View File

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

View File

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

View File

@ -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>

View File

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

View File

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