strawberry-audio-player-win.../src/transcoder/transcoder.cpp

636 lines
22 KiB
C++
Raw Normal View History

2018-02-27 18:06:05 +01:00
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
2021-03-20 21:14:47 +01:00
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
2018-02-27 18:06:05 +01:00
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
2018-08-09 18:10:03 +02:00
*
2018-02-27 18:06:05 +01:00
*/
#include "config.h"
2023-07-21 05:25:57 +02:00
#include <QtGlobal>
2018-10-19 20:18:46 +02:00
#include <algorithm>
2023-07-21 05:25:57 +02:00
#include <memory>
2023-01-12 23:37:12 +01:00
#include <glib.h>
#include <glib/gtypes.h>
#include <gst/gst.h>
2018-02-27 18:06:05 +01:00
#include <QThread>
2018-02-27 18:06:05 +01:00
#include <QCoreApplication>
#include <QStandardPaths>
#include <QByteArray>
2018-02-27 18:06:05 +01:00
#include <QDir>
#include <QFileInfo>
#include <QList>
#include <QMap>
#include <QVariant>
#include <QString>
2018-02-27 18:06:05 +01:00
#include <QSettings>
#include "core/logging.h"
#include "core/shared_ptr.h"
2018-02-27 18:06:05 +01:00
#include "core/signalchecker.h"
#include "core/settings.h"
#include "transcoder.h"
2018-02-27 18:06:05 +01:00
using namespace Qt::StringLiterals;
using std::make_shared;
2018-02-27 18:06:05 +01:00
int Transcoder::JobFinishedEvent::sEventType = -1;
2021-06-20 19:04:08 +02:00
TranscoderPreset::TranscoderPreset(const Song::FileType filetype, const QString &name, const QString &extension, const QString &codec_mimetype, const QString &muxer_mimetype)
: filetype_(filetype),
2018-02-27 18:06:05 +01:00
name_(name),
extension_(extension),
codec_mimetype_(codec_mimetype),
muxer_mimetype_(muxer_mimetype) {}
GstElement *Transcoder::CreateElement(const QString &factory_name, GstElement *bin, const QString &name) {
GstElement *ret = gst_element_factory_make(factory_name.toLatin1().constData(), name.isNull() ? factory_name.toLatin1().constData() : name.toLatin1().constData());
if (ret && bin) gst_bin_add(GST_BIN(bin), ret);
2023-01-12 23:37:12 +01:00
if (ret) {
SetElementProperties(factory_name, G_OBJECT(ret));
2018-02-27 18:06:05 +01:00
}
else {
2024-08-25 01:06:30 +02:00
Q_EMIT LogLine(tr("Could not create the GStreamer element \"%1\" - make sure you have all the required GStreamer plugins installed").arg(factory_name));
2018-02-27 18:06:05 +01:00
}
return ret;
}
struct SuitableElement {
2020-06-26 22:41:38 +02:00
explicit SuitableElement(const QString &name = QString(), int rank = 0) : name_(name), rank_(rank) {}
2018-02-27 18:06:05 +01:00
bool operator<(const SuitableElement &other) const {
return rank_ < other.rank_;
}
QString name_;
int rank_;
};
GstElement *Transcoder::CreateElementForMimeType(GstElementFactoryListType element_type, const QString &mime_type, GstElement *bin) {
2018-02-27 18:06:05 +01:00
if (mime_type.isEmpty()) return nullptr;
2019-01-24 19:16:39 +01:00
// HACK: Force mp4mux because it doesn't set any useful src caps
if (mime_type == "audio/mp4"_L1) {
Q_EMIT LogLine(QStringLiteral("Using '%1' (rank %2)").arg("mp4mux"_L1).arg(-1));
2024-04-09 23:20:26 +02:00
return CreateElement(QStringLiteral("mp4mux"), bin);
2018-02-27 18:06:05 +01:00
}
// Keep track of all the suitable elements we find and figure out which is the best at the end.
2018-02-27 18:06:05 +01:00
QList<SuitableElement> suitable_elements_;
// The caps we're trying to find
GstCaps *target_caps = gst_caps_from_string(mime_type.toUtf8().constData());
GstRegistry *registry = gst_registry_get();
GList *const features = gst_registry_get_feature_list(registry, GST_TYPE_ELEMENT_FACTORY);
2021-08-23 21:21:08 +02:00
for (GList *f = features; f; f = g_list_next(f)) {
2020-04-23 21:08:28 +02:00
GstElementFactory *factory = GST_ELEMENT_FACTORY(f->data);
2018-02-27 18:06:05 +01:00
// Is this the right type of plugin?
if (gst_element_factory_list_is_type(factory, element_type)) {
// check if the element factory supports the target caps
if (gst_element_factory_can_src_any_caps(factory, target_caps)) {
const QString name = QString::fromUtf8(GST_OBJECT_NAME(factory));
int rank = static_cast<int>(gst_plugin_feature_get_rank(GST_PLUGIN_FEATURE(factory)));
if (name.startsWith("avmux"_L1) || name.startsWith("avenc"_L1)) {
rank = -1; // ffmpeg usually sucks
2018-02-27 18:06:05 +01:00
}
suitable_elements_ << SuitableElement(name, rank);
2018-02-27 18:06:05 +01:00
}
}
}
gst_plugin_feature_list_free(features);
gst_caps_unref(target_caps);
if (suitable_elements_.isEmpty()) return nullptr;
// Sort by rank
2018-10-19 20:18:46 +02:00
std::sort(suitable_elements_.begin(), suitable_elements_.end());
2018-02-27 18:06:05 +01:00
const SuitableElement &best = suitable_elements_.last();
2024-08-25 01:06:30 +02:00
Q_EMIT LogLine(QStringLiteral("Using '%1' (rank %2)").arg(best.name_).arg(best.rank_));
2018-02-27 18:06:05 +01:00
if (best.name_ == "lamemp3enc"_L1) {
// Special case: we need to add xingmux and id3v2mux to the pipeline when using lamemp3enc because it doesn't write the VBR or ID3v2 headers itself.
2018-02-27 18:06:05 +01:00
2024-08-25 01:06:30 +02:00
Q_EMIT LogLine(QStringLiteral("Adding xingmux and id3v2mux to the pipeline"));
2018-02-27 18:06:05 +01:00
// Create the bin
GstElement *mp3bin = gst_bin_new("mp3bin");
gst_bin_add(GST_BIN(bin), mp3bin);
// Create the elements
2024-04-09 23:20:26 +02:00
GstElement *lame = CreateElement(QStringLiteral("lamemp3enc"), mp3bin);
GstElement *xing = CreateElement(QStringLiteral("xingmux"), mp3bin);
GstElement *id3v2 = CreateElement(QStringLiteral("id3v2mux"), mp3bin);
2018-02-27 18:06:05 +01:00
if (!lame || !xing || !id3v2) {
return nullptr;
}
// Link the elements together
gst_element_link_many(lame, xing, id3v2, nullptr);
// Link the bin's ghost pads to the elements on each end
GstPad *pad = gst_element_get_static_pad(lame, "sink");
gst_element_add_pad(mp3bin, gst_ghost_pad_new("sink", pad));
gst_object_unref(GST_OBJECT(pad));
pad = gst_element_get_static_pad(id3v2, "src");
gst_element_add_pad(mp3bin, gst_ghost_pad_new("src", pad));
gst_object_unref(GST_OBJECT(pad));
return mp3bin;
}
else {
return CreateElement(best.name_, bin);
}
2022-07-16 16:31:04 +02:00
2018-02-27 18:06:05 +01:00
}
Transcoder::JobFinishedEvent::JobFinishedEvent(JobState *state, bool success)
2022-06-13 00:23:42 +02:00
: QEvent(static_cast<QEvent::Type>(sEventType)), state_(state), success_(success) {}
2018-02-27 18:06:05 +01:00
2021-06-20 19:04:08 +02:00
void Transcoder::JobState::PostFinished(const bool success) {
2018-02-27 18:06:05 +01:00
if (success) {
2024-08-25 01:06:30 +02:00
Q_EMIT parent_->LogLine(tr("Successfully written %1").arg(QDir::toNativeSeparators(job_.output)));
2018-02-27 18:06:05 +01:00
}
QCoreApplication::postEvent(parent_, new Transcoder::JobFinishedEvent(this, success));
}
Transcoder::Transcoder(QObject *parent, const QString &settings_postfix)
: QObject(parent),
2021-07-11 09:49:38 +02:00
max_threads_(QThread::idealThreadCount()),
settings_postfix_(settings_postfix) {
2018-02-27 18:06:05 +01:00
if (JobFinishedEvent::sEventType == -1)
JobFinishedEvent::sEventType = QEvent::registerEventType();
2020-10-17 17:29:09 +02:00
// Initialize some settings for the lamemp3enc element.
Settings s;
s.beginGroup("Transcoder/lamemp3enc"_L1 + settings_postfix_);
2018-02-27 18:06:05 +01:00
if (s.value("target").isNull()) {
s.setValue("target", 1); // 1 == bitrate
}
if (s.value("cbr").isNull()) {
2019-01-24 19:16:39 +01:00
s.setValue("cbr", false);
2018-02-27 18:06:05 +01:00
}
2022-07-16 16:31:04 +02:00
s.endGroup();
2018-02-27 18:06:05 +01:00
}
QList<TranscoderPreset> Transcoder::GetAllPresets() {
QList<TranscoderPreset> ret;
2023-02-18 14:09:27 +01:00
ret << PresetForFileType(Song::FileType::WAV);
ret << PresetForFileType(Song::FileType::FLAC);
ret << PresetForFileType(Song::FileType::WavPack);
ret << PresetForFileType(Song::FileType::OggFlac);
ret << PresetForFileType(Song::FileType::OggVorbis);
ret << PresetForFileType(Song::FileType::OggOpus);
ret << PresetForFileType(Song::FileType::OggSpeex);
ret << PresetForFileType(Song::FileType::MPEG);
ret << PresetForFileType(Song::FileType::MP4);
ret << PresetForFileType(Song::FileType::ASF);
2022-07-16 16:31:04 +02:00
2018-02-27 18:06:05 +01:00
return ret;
}
2021-06-20 19:04:08 +02:00
TranscoderPreset Transcoder::PresetForFileType(const Song::FileType filetype) {
2018-02-27 18:06:05 +01:00
2021-06-20 19:04:08 +02:00
switch (filetype) {
2023-02-18 14:09:27 +01:00
case Song::FileType::WAV:
2024-04-09 23:20:26 +02:00
return TranscoderPreset(filetype, QStringLiteral("Wav"), QStringLiteral("wav"), QString(), QStringLiteral("audio/x-wav"));
2023-02-18 14:09:27 +01:00
case Song::FileType::FLAC:
2024-04-09 23:20:26 +02:00
return TranscoderPreset(filetype, QStringLiteral("FLAC"), QStringLiteral("flac"), QStringLiteral("audio/x-flac"));
2023-02-18 14:09:27 +01:00
case Song::FileType::WavPack:
2024-04-09 23:20:26 +02:00
return TranscoderPreset(filetype, QStringLiteral("WavPack"), QStringLiteral("wv"), QStringLiteral("audio/x-wavpack"));
2023-02-18 14:09:27 +01:00
case Song::FileType::OggFlac:
2024-04-09 23:20:26 +02:00
return TranscoderPreset(filetype, QStringLiteral("Ogg FLAC"), QStringLiteral("ogg"), QStringLiteral("audio/x-flac"), QStringLiteral("application/ogg"));
2023-02-18 14:09:27 +01:00
case Song::FileType::OggVorbis:
2024-04-09 23:20:26 +02:00
return TranscoderPreset(filetype, QStringLiteral("Ogg Vorbis"), QStringLiteral("ogg"), QStringLiteral("audio/x-vorbis"), QStringLiteral("application/ogg"));
2023-02-18 14:09:27 +01:00
case Song::FileType::OggOpus:
2024-04-09 23:20:26 +02:00
return TranscoderPreset(filetype, QStringLiteral("Ogg Opus"), QStringLiteral("opus"), QStringLiteral("audio/x-opus"), QStringLiteral("application/ogg"));
2023-02-18 14:09:27 +01:00
case Song::FileType::OggSpeex:
2024-04-09 23:20:26 +02:00
return TranscoderPreset(filetype, QStringLiteral("Ogg Speex"), QStringLiteral("spx"), QStringLiteral("audio/x-speex"), QStringLiteral("application/ogg"));
2023-02-18 14:09:27 +01:00
case Song::FileType::MPEG:
2024-04-09 23:20:26 +02:00
return TranscoderPreset(filetype, QStringLiteral("MP3"), QStringLiteral("mp3"), QStringLiteral("audio/mpeg, mpegversion=(int)1, layer=(int)3"));
2023-02-18 14:09:27 +01:00
case Song::FileType::MP4:
2024-04-09 23:20:26 +02:00
return TranscoderPreset(filetype, QStringLiteral("M4A AAC"), QStringLiteral("mp4"), QStringLiteral("audio/mpeg, mpegversion=(int)4"), QStringLiteral("audio/mp4"));
2023-02-18 14:09:27 +01:00
case Song::FileType::ASF:
2024-04-09 23:20:26 +02:00
return TranscoderPreset(filetype, QStringLiteral("Windows Media audio"), QStringLiteral("wma"), QStringLiteral("audio/x-wma"), QStringLiteral("video/x-ms-asf"));
2018-02-27 18:06:05 +01:00
default:
2023-02-18 14:09:27 +01:00
qLog(Warning) << "Unsupported format in PresetForFileType:" << static_cast<int>(filetype);
2018-02-27 18:06:05 +01:00
return TranscoderPreset();
}
}
2021-06-20 19:04:08 +02:00
Song::FileType Transcoder::PickBestFormat(const QList<Song::FileType> &supported) {
2018-02-27 18:06:05 +01:00
2023-02-18 14:09:27 +01:00
if (supported.isEmpty()) return Song::FileType::Unknown;
2018-02-27 18:06:05 +01:00
2024-04-23 17:15:42 +02:00
const QList<Song::FileType> best_formats = QList<Song::FileType>() << Song::FileType::FLAC << Song::FileType::OggFlac << Song::FileType::WavPack;
2018-02-27 18:06:05 +01:00
for (Song::FileType type : best_formats) {
2023-07-21 07:16:08 +02:00
if (supported.contains(type)) return type;
2018-02-27 18:06:05 +01:00
}
return supported[0];
}
2021-06-20 19:04:08 +02:00
QString Transcoder::GetFile(const QString &input, const TranscoderPreset &preset, const QString &output) {
2018-02-27 18:06:05 +01:00
QFileInfo fileinfo_output;
if (!output.isEmpty()) {
fileinfo_output.setFile(output);
}
2018-02-27 18:06:05 +01:00
if (!fileinfo_output.isFile() || fileinfo_output.filePath().isEmpty() || fileinfo_output.path().isEmpty() || fileinfo_output.fileName().isEmpty() || fileinfo_output.suffix().isEmpty()) {
QFileInfo fileinfo_input(input);
QString temp_dir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/transcoder"_L1;
2021-03-20 18:58:38 +01:00
if (!QDir(temp_dir).exists()) QDir().mkpath(temp_dir);
QString filename = fileinfo_input.completeBaseName() + QLatin1Char('.') + preset.extension_;
fileinfo_output.setFile(temp_dir + QLatin1Char('/') + filename);
}
2018-02-27 18:06:05 +01:00
// Never overwrite existing files
if (fileinfo_output.exists()) {
QString path = fileinfo_output.path();
QString filename = fileinfo_output.completeBaseName();
QString suffix = fileinfo_output.suffix();
2018-02-27 18:06:05 +01:00
for (int i = 0;; ++i) {
2024-04-09 23:20:26 +02:00
QString new_filename = QStringLiteral("%1/%2-%3.%4").arg(path, filename).arg(i).arg(suffix);
fileinfo_output.setFile(new_filename);
if (!fileinfo_output.exists()) {
2018-02-27 18:06:05 +01:00
break;
}
2018-02-27 18:06:05 +01:00
}
}
return fileinfo_output.filePath();
2022-07-16 16:31:04 +02:00
2018-02-27 18:06:05 +01:00
}
void Transcoder::AddJob(const QString &input, const TranscoderPreset &preset, const QString &output) {
2018-02-27 18:06:05 +01:00
Job job;
job.input = input;
job.preset = preset;
job.output = output;
2018-02-27 18:06:05 +01:00
queued_jobs_ << job;
}
void Transcoder::Start() {
2024-08-25 01:06:30 +02:00
Q_EMIT LogLine(tr("Transcoding %1 files using %2 threads").arg(queued_jobs_.count()).arg(max_threads()));
2018-02-27 18:06:05 +01:00
2024-08-25 06:24:02 +02:00
Q_FOREVER {
2018-02-27 18:06:05 +01:00
StartJobStatus status = MaybeStartNextJob();
2023-02-18 14:09:27 +01:00
if (status == StartJobStatus::AllThreadsBusy || status == StartJobStatus::NoMoreJobs) break;
2018-02-27 18:06:05 +01:00
}
}
Transcoder::StartJobStatus Transcoder::MaybeStartNextJob() {
2023-02-18 14:09:27 +01:00
if (current_jobs_.count() >= max_threads()) return StartJobStatus::AllThreadsBusy;
2018-02-27 18:06:05 +01:00
if (queued_jobs_.isEmpty()) {
if (current_jobs_.isEmpty()) {
2024-08-25 01:06:30 +02:00
Q_EMIT AllJobsComplete();
2018-02-27 18:06:05 +01:00
}
2023-02-18 14:09:27 +01:00
return StartJobStatus::NoMoreJobs;
2018-02-27 18:06:05 +01:00
}
Job job = queued_jobs_.takeFirst();
if (StartJob(job)) {
2023-02-18 14:09:27 +01:00
return StartJobStatus::StartedSuccessfully;
2018-02-27 18:06:05 +01:00
}
2024-08-25 01:06:30 +02:00
Q_EMIT JobComplete(job.input, job.output, false);
2023-02-18 14:09:27 +01:00
return StartJobStatus::FailedToStart;
2018-02-27 18:06:05 +01:00
}
void Transcoder::NewPadCallback(GstElement*, GstPad *pad, gpointer data) {
JobState *state = reinterpret_cast<JobState*>(data);
GstPad *const audiopad = gst_element_get_static_pad(state->convert_element_, "sink");
if (GST_PAD_IS_LINKED(audiopad)) {
2023-01-12 23:37:12 +01:00
qLog(Debug) << "Audiopad is already linked, unlinking old pad";
2018-02-27 18:06:05 +01:00
gst_pad_unlink(audiopad, GST_PAD_PEER(audiopad));
}
gst_pad_link(pad, audiopad);
gst_object_unref(audiopad);
}
GstBusSyncReply Transcoder::BusCallbackSync(GstBus*, GstMessage *msg, gpointer data) {
JobState *state = reinterpret_cast<JobState*>(data);
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_EOS:
state->PostFinished(true);
break;
case GST_MESSAGE_ERROR:
state->ReportError(msg);
state->PostFinished(false);
break;
default:
break;
}
2019-01-24 19:16:39 +01:00
2018-02-27 18:06:05 +01:00
return GST_BUS_PASS;
}
2021-06-22 13:45:29 +02:00
void Transcoder::JobState::ReportError(GstMessage *msg) const {
2018-02-27 18:06:05 +01:00
2021-03-26 21:30:13 +01:00
GError *error = nullptr;
gchar *debugs = nullptr;
2018-02-27 18:06:05 +01:00
gst_message_parse_error(msg, &error, &debugs);
QString message = QString::fromLocal8Bit(error->message);
g_error_free(error);
2022-02-06 18:47:23 +01:00
g_free(debugs);
2018-02-27 18:06:05 +01:00
2024-08-25 01:06:30 +02:00
Q_EMIT parent_->LogLine(tr("Error processing %1: %2").arg(QDir::toNativeSeparators(job_.input), message));
2018-02-27 18:06:05 +01:00
}
bool Transcoder::StartJob(const Job &job) {
SharedPtr<JobState> state = make_shared<JobState>(job, this);
2018-02-27 18:06:05 +01:00
2024-08-25 01:06:30 +02:00
Q_EMIT LogLine(tr("Starting %1").arg(QDir::toNativeSeparators(job.input)));
2018-02-27 18:06:05 +01:00
// Create the pipeline.
// This should be a scoped_ptr, but scoped_ptr doesn't support custom destructors.
2018-02-27 18:06:05 +01:00
state->pipeline_ = gst_pipeline_new("pipeline");
if (!state->pipeline_) return false;
// Create all the elements
2024-04-09 23:20:26 +02:00
GstElement *src = CreateElement(QStringLiteral("filesrc"), state->pipeline_);
GstElement *decode = CreateElement(QStringLiteral("decodebin"), state->pipeline_);
GstElement *convert = CreateElement(QStringLiteral("audioconvert"), state->pipeline_);
GstElement *resample = CreateElement(QStringLiteral("audioresample"), state->pipeline_);
GstElement *codec = CreateElementForMimeType(GST_ELEMENT_FACTORY_TYPE_AUDIO_ENCODER, job.preset.codec_mimetype_, state->pipeline_);
GstElement *muxer = CreateElementForMimeType(GST_ELEMENT_FACTORY_TYPE_MUXER, job.preset.muxer_mimetype_, state->pipeline_);
2024-04-09 23:20:26 +02:00
GstElement *sink = CreateElement(QStringLiteral("filesink"), state->pipeline_);
2018-02-27 18:06:05 +01:00
if (!src || !decode || !convert || !sink) return false;
if (!codec && !job.preset.codec_mimetype_.isEmpty()) {
2024-08-25 01:06:30 +02:00
Q_EMIT LogLine(tr("Couldn't find an encoder for %1, check you have the correct GStreamer plugins installed").arg(job.preset.codec_mimetype_));
2018-02-27 18:06:05 +01:00
return false;
}
if (!muxer && !job.preset.muxer_mimetype_.isEmpty()) {
2024-08-25 01:06:30 +02:00
Q_EMIT LogLine(tr("Couldn't find a muxer for %1, check you have the correct GStreamer plugins installed").arg(job.preset.muxer_mimetype_));
2018-02-27 18:06:05 +01:00
return false;
}
// Join them together
gst_element_link(src, decode);
if (codec && muxer) gst_element_link_many(convert, resample, codec, muxer, sink, nullptr);
else if (codec) gst_element_link_many(convert, resample, codec, sink, nullptr);
else if (muxer) gst_element_link_many(convert, resample, muxer, sink, nullptr);
// Set properties
g_object_set(src, "location", job.input.toUtf8().constData(), nullptr);
g_object_set(sink, "location", job.output.toUtf8().constData(), nullptr);
// Set callbacks
state->convert_element_ = convert;
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);
// Start the pipeline
gst_element_set_state(state->pipeline_, GST_STATE_PLAYING);
// GStreamer now transcodes in another thread, so we can return now and do something else.
// Keep the JobState object around. It'll post an event to our event loop when it finishes.
2018-02-27 18:06:05 +01:00
current_jobs_ << state;
return true;
}
Transcoder::JobState::~JobState() {
if (pipeline_) {
gst_element_set_state(pipeline_, GST_STATE_NULL);
gst_object_unref(pipeline_);
}
}
bool Transcoder::event(QEvent *e) {
if (e->type() == JobFinishedEvent::sEventType) {
JobFinishedEvent *finished_event = static_cast<JobFinishedEvent*>(e);
// Find this job in the list
JobStateList::iterator it = current_jobs_.begin();
2021-08-23 21:21:08 +02:00
for (; it != current_jobs_.end(); ++it) {
2018-02-27 18:06:05 +01:00
if (it->get() == finished_event->state_) break;
}
if (it == current_jobs_.end()) {
// Couldn't find it, maybe GStreamer gave us an event after we'd destroyed the pipeline?
2018-02-27 18:06:05 +01:00
return true;
}
QString input = (*it)->job_.input;
QString output = (*it)->job_.output;
2022-08-28 02:44:37 +02:00
// Remove event handlers from the gstreamer pipeline, so they don't get called after the pipeline is shutting down
2018-02-27 18:06:05 +01:00
gst_bus_set_sync_handler(gst_pipeline_get_bus(GST_PIPELINE(finished_event->state_->pipeline_)), nullptr, nullptr, nullptr);
// Remove it from the list - this will also destroy the GStreamer pipeline
2024-08-23 20:30:59 +02:00
current_jobs_.erase(it);
2018-02-27 18:06:05 +01:00
// Emit the finished signal
2024-08-25 01:06:30 +02:00
Q_EMIT JobComplete(input, output, finished_event->success_);
2018-02-27 18:06:05 +01:00
// Start some more jobs
MaybeStartNextJob();
return true;
}
return QObject::event(e);
}
void Transcoder::Cancel() {
// Remove all pending jobs
queued_jobs_.clear();
// Stop the running ones
JobStateList::iterator it = current_jobs_.begin();
while (it != current_jobs_.end()) {
SharedPtr<JobState> state(*it);
2018-02-27 18:06:05 +01:00
2022-08-28 02:44:37 +02:00
// Remove event handlers from the gstreamer pipeline, so they don't get called after the pipeline is shutting down
2018-02-27 18:06:05 +01:00
gst_bus_set_sync_handler(gst_pipeline_get_bus(GST_PIPELINE(state->pipeline_)), nullptr, nullptr, nullptr);
// Stop the pipeline
if (gst_element_set_state(state->pipeline_, GST_STATE_NULL) == GST_STATE_CHANGE_ASYNC) {
// Wait for it to finish stopping...
gst_element_get_state(state->pipeline_, nullptr, nullptr, GST_CLOCK_TIME_NONE);
}
// Remove the job, this destroys the GStreamer pipeline too
2024-08-23 20:30:59 +02:00
it = current_jobs_.erase(it);
2018-02-27 18:06:05 +01:00
}
}
QMap<QString, float> Transcoder::GetProgress() const {
QMap<QString, float> ret;
for (const auto &state : current_jobs_) {
if (!state->pipeline_) continue;
gint64 position = 0;
gint64 duration = 0;
gst_element_query_position(state->pipeline_, GST_FORMAT_TIME, &position);
gst_element_query_duration(state->pipeline_, GST_FORMAT_TIME, &duration);
2021-10-30 02:21:29 +02:00
ret[state->job_.input] = static_cast<float>(position) / static_cast<float>(duration);
2018-02-27 18:06:05 +01:00
}
return ret;
}
void Transcoder::SetElementProperties(const QString &name, GObject *object) {
Settings s;
s.beginGroup("Transcoder/"_L1 + name + settings_postfix_);
2018-02-27 18:06:05 +01:00
guint properties_count = 0;
GParamSpec **properties = g_object_class_list_properties(G_OBJECT_GET_CLASS(object), &properties_count);
2018-02-27 18:06:05 +01:00
2019-11-15 00:23:06 +01:00
for (uint i = 0; i < properties_count; ++i) {
2018-02-27 18:06:05 +01:00
GParamSpec *property = properties[i];
if (!s.contains(QString::fromUtf8(property->name))) {
2023-01-12 23:37:12 +01:00
continue;
}
const QVariant value = s.value(QString::fromUtf8(property->name));
2023-01-12 23:37:12 +01:00
if (value.isNull()) {
continue;
}
2018-02-27 18:06:05 +01:00
2024-08-25 01:06:30 +02:00
Q_EMIT LogLine(QStringLiteral("Setting %1 property: %2 = %3").arg(name, QString::fromUtf8(property->name), value.toString()));
2018-02-27 18:06:05 +01:00
switch (property->value_type) {
2023-01-12 23:37:12 +01:00
case G_TYPE_FLOAT:{
const double g_value = static_cast<gfloat>(value.toFloat());
qLog(Debug) << "Setting" << property->name << "float" << "to" << g_value;
g_object_set(object, property->name, g_value, nullptr);
2018-02-27 18:06:05 +01:00
break;
2023-01-12 23:37:12 +01:00
}
case G_TYPE_DOUBLE:{
const double g_value = static_cast<gdouble>(value.toDouble());
qLog(Debug) << "Setting" << property->name << "(double)" << "to" << g_value;
g_object_set(object, property->name, g_value, nullptr);
2018-02-27 18:06:05 +01:00
break;
2023-01-12 23:37:12 +01:00
}
case G_TYPE_BOOLEAN:{
const bool g_value = value.toBool();
qLog(Debug) << "Setting" << property->name << "(bool)" << "to" << g_value;
g_object_set(object, property->name, g_value, nullptr);
2018-02-27 18:06:05 +01:00
break;
2023-01-12 23:37:12 +01:00
}
2018-02-27 18:06:05 +01:00
case G_TYPE_INT:
2023-01-12 23:37:12 +01:00
case G_TYPE_ENUM:{
const gint g_value = static_cast<gint>(value.toInt());
qLog(Debug) << "Setting" << property->name << "(enum)" << "to" << g_value;
g_object_set(object, property->name, g_value, nullptr);
break;
}
case G_TYPE_UINT:{
const guint g_value = static_cast<gint>(value.toUInt());
qLog(Debug) << "Setting" << property->name << "(uint)" << "to" << g_value;
g_object_set(object, property->name, g_value, nullptr);
break;
}
case G_TYPE_LONG:
case G_TYPE_INT64:{
const glong g_value = static_cast<glong>(value.toLongLong());
qLog(Debug) << "Setting" << property->name << "(long)" << "to" << g_value;
g_object_set(object, property->name, g_value, nullptr);
2018-02-27 18:06:05 +01:00
break;
2023-01-12 23:37:12 +01:00
}
case G_TYPE_ULONG:
case G_TYPE_UINT64:{
const gulong g_value = static_cast<gulong>(value.toULongLong());
qLog(Debug) << "Setting" << property->name << "(ulong)" << "to" << g_value;
g_object_set(object, property->name, g_value, nullptr);
break;
}
default:{
const gint g_value = static_cast<gint>(value.toInt());
qLog(Debug) << "Setting" << property->name << "(int)" << "to" << g_value;
g_object_set(object, property->name, g_value, nullptr);
break;
}
2018-02-27 18:06:05 +01:00
}
}
g_free(properties);
2022-07-16 16:31:04 +02:00
s.endGroup();
2018-02-27 18:06:05 +01:00
}