testProfile(xmlProducer.profile());
if (Settings.playerGPU() && MLT.profile().is_explicit()) {
if (testProfile->width() != MLT.profile().width()
|| testProfile->height() != MLT.profile().height()
|| Util::isFpsDifferent(MLT.profile().fps(), testProfile->fps())) {
return Mlt::Producer();
}
}
if (xmlProducer.is_valid()) {
Mlt::Chain chain(MLT.profile());
chain.set_source(xmlProducer);
chain.attach_normalizers();
chain.get_length_time(mlt_time_clock);
chain.set(kShotcutVirtualClip, 1);
chain.set("resource", path.toUtf8().constData());
return chain;
}
return Mlt::Producer();
}
bool Util::hasiPhoneAmbisonic(Mlt::Producer *producer)
{
// iPhone 16 Pro has a 4 channel (spatial) audio stream with codec "apac" that causes failure.
// This is not limited to only iPhone 16 Pro, but I think most iPhones only record one usable audio track.
return producer && producer->is_valid()
&& !::qstrcmp(producer->get("meta.media.1.stream.type"), "audio")
&& QString(producer->get("meta.attr.com.apple.quicktime.model.markup")).contains("iPhone");
}
bool Util::installFlatpakWrappers(QWidget *parent)
{
if (!Settings.askFlatpakWrappers())
return false;
QMessageBox dialog(QMessageBox::Question,
qApp->applicationName(),
parent->tr(
"Do you want use a Flatpak?
"
"Click Yes to install/update the Flatpak wrapper scripts "
"in\nFiles > Home > Flatpaks.
"
"Tip: Add ~/Flatpaks to your $PATH "
"to make them more "
"convenient on the command line.
"),
QMessageBox::No | QMessageBox::Yes,
parent);
dialog.setCheckBox(new QCheckBox(parent->tr("Do not show this anymore.")));
dialog.setWindowModality(QmlApplication::dialogModality());
dialog.setDefaultButton(QMessageBox::Yes);
dialog.setEscapeButton(QMessageBox::No);
int r = dialog.exec();
if (dialog.checkBox()->isChecked())
Settings.setAskFlatpakWrappers(false);
if (r == QMessageBox::Yes) {
FlatpakWrapperGenerator flatpaks;
const auto ls = QStandardPaths::standardLocations(QStandardPaths::HomeLocation);
auto home = QDir(ls.first());
const auto subdir = QStringLiteral("Flatpaks");
if (!home.cd(subdir)) {
if (home.mkdir(subdir))
home.cd(subdir);
else
return false;
}
flatpaks.setOutputDir(home.absolutePath());
flatpaks.setForce(true);
flatpaks.generateAllInstalled();
return true;
}
return false;
}
QString Util::getExecutable(QWidget *parent)
{
QString dir;
QString filter;
#if defined(Q_OS_WIN)
dir = QStringLiteral("C:/ProgramData/Microsoft/Windows/Start Menu/Programs");
filter = parent->tr("Executable Files (*.exe);;All Files (*)");
#elif defined(Q_OS_MAC)
const auto ls = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation);
LOG_DEBUG() << ls;
dir = ls.last();
#elif defined(Q_OS_LINUX)
if (Util::installFlatpakWrappers(parent)) {
const auto ls = QStandardPaths::standardLocations(QStandardPaths::HomeLocation);
auto home = QDir(ls.first());
home.cd("Flatpaks");
dir = home.absolutePath();
} else {
dir = QStringLiteral("/usr/bin");
}
#endif
return QFileDialog::getOpenFileName(MAIN.window(),
parent->tr("Choose Executable"),
dir,
filter,
nullptr,
Util::getFileDialogOptions());
}
QPair Util::dockerStatus(const QString &imageName)
{
// Check if docker executable is available by running 'docker --version'.
QProcess proc;
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
auto env = QProcessEnvironment::systemEnvironment();
env.remove("LD_LIBRARY_PATH");
proc.setProcessEnvironment(env);
#endif
proc.start(Settings.dockerPath(), {"--version"});
if (!proc.waitForStarted(1000)) {
return qMakePair(false, false);
}
// Keep the timeout short to avoid UI stall.
if (!proc.waitForFinished(2000)) {
proc.kill();
return qMakePair(false, false);
}
bool dockerOk = proc.exitStatus() == QProcess::NormalExit && proc.exitCode() == 0;
if (!dockerOk) {
return qMakePair(false, false);
}
if (imageName.isEmpty()) {
return qMakePair(true, false);
}
// Query local images for the given name (may include tag). We avoid pulling.
// Use 'docker image inspect ' which returns 0 if present.
QProcess procImage;
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
env = QProcessEnvironment::systemEnvironment();
env.remove("LD_LIBRARY_PATH");
procImage.setProcessEnvironment(env);
#endif
procImage.start(Settings.dockerPath(), {"image", "inspect", imageName});
if (!procImage.waitForStarted(1000)) {
return qMakePair(true, false);
}
if (!procImage.waitForFinished(5000)) { // allow a bit more time here
procImage.kill();
return qMakePair(true, false);
}
bool imageOk = procImage.exitStatus() == QProcess::NormalExit && procImage.exitCode() == 0;
return qMakePair(true, imageOk);
}
// Helper to extract digest from 'docker manifest inspect' output.
static QString digestFromManifestInspect(const QByteArray &json)
{
auto s = QString::fromUtf8(json);
auto idx = s.indexOf("sha256:");
if (idx >= 0 && idx + 71 <= s.size()) {
return s.mid(idx, 71);
}
return QString();
}
static QSet dockerCurrentState;
bool Util::isDockerImageCurrent(const QString &imageRef)
{
if (imageRef.isEmpty()) {
return false;
}
// TODO: This comparison is not working outside of the machine on which I built the image.
// In the meantime, use check once per session per image ref.
if (dockerCurrentState.contains(imageRef))
return true;
dockerCurrentState << imageRef;
return false;
// Inspect (local/remote combined behavior) - this is a simplistic approach.
QProcess localProc;
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
auto env = QProcessEnvironment::systemEnvironment();
env.remove("LD_LIBRARY_PATH");
localProc.setProcessEnvironment(env);
#endif
localProc.start(Settings.dockerPath(), {"image", "inspect", imageRef});
if (!localProc.waitForStarted(1000) || !localProc.waitForFinished(4000)) {
if (localProc.state() != QProcess::NotRunning)
localProc.kill();
}
QString localDigest;
if (localProc.exitStatus() == QProcess::NormalExit && localProc.exitCode() == 0) {
localDigest = digestFromManifestInspect(localProc.readAllStandardOutput());
}
// Run again expecting remote freshness (will hit registry) without pulling.
QProcess remoteProc;
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
env = QProcessEnvironment::systemEnvironment();
env.remove("LD_LIBRARY_PATH");
remoteProc.setProcessEnvironment(env);
#endif
remoteProc.start(Settings.dockerPath(), {"manifest", "inspect", imageRef});
if (!remoteProc.waitForStarted(1000) || !remoteProc.waitForFinished(15000)) {
if (remoteProc.state() != QProcess::NotRunning)
remoteProc.kill();
return false;
}
if (remoteProc.exitStatus() != QProcess::NormalExit || remoteProc.exitCode() != 0) {
return false;
}
auto remoteDigest = digestFromManifestInspect(remoteProc.readAllStandardOutput());
return !remoteDigest.isEmpty() && !localDigest.isEmpty() && localDigest == remoteDigest;
}
void Util::isDockerImageCurrentAsync(const QString &imageRef,
QObject *receiver,
std::function callback)
{
if (!callback) {
return; // nothing to do
}
if (imageRef.isEmpty()) {
callback(false);
return;
}
// TODO: This comparison is not working outside of the machine on which I built the image.
// In the meantime, use check once per session per image ref.
if (dockerCurrentState.contains(imageRef)) {
callback(true);
return;
}
dockerCurrentState << imageRef;
callback(false);
return;
auto emitResult = [callback](bool result) {
QMetaObject::invokeMethod(
qApp, [callback, result]() { callback(result); }, Qt::QueuedConnection);
};
// Start with local inspect.
auto *localProc = new QProcess(receiver);
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
auto env = QProcessEnvironment::systemEnvironment();
env.remove("LD_LIBRARY_PATH");
localProc->setProcessEnvironment(env);
#endif
QObject::connect(localProc,
&QProcess::errorOccurred,
localProc,
[localProc, emitResult](QProcess::ProcessError) {
localProc->deleteLater();
emitResult(false);
});
QObject::connect(
localProc,
QOverload::of(&QProcess::finished),
localProc,
[imageRef, receiver, emitResult, localProc](int, QProcess::ExitStatus) {
QString localDigest;
if (localProc->exitStatus() == QProcess::NormalExit && localProc->exitCode() == 0) {
localDigest = digestFromManifestInspect(localProc->readAllStandardOutput());
}
localProc->deleteLater();
// Remote manifest inspect regardless; if docker missing it will fail.
auto *remoteProc = new QProcess(receiver);
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
auto env = QProcessEnvironment::systemEnvironment();
env.remove("LD_LIBRARY_PATH");
remoteProc->setProcessEnvironment(env);
#endif
QObject::connect(remoteProc,
&QProcess::errorOccurred,
remoteProc,
[remoteProc, emitResult](QProcess::ProcessError) {
remoteProc->deleteLater();
emitResult(false);
});
QObject::connect(remoteProc,
QOverload::of(&QProcess::finished),
remoteProc,
[emitResult, remoteProc, localDigest](int, QProcess::ExitStatus) {
QString remoteDigest;
if (remoteProc->exitStatus() == QProcess::NormalExit
&& remoteProc->exitCode() == 0) {
remoteDigest = digestFromManifestInspect(
remoteProc->readAllStandardOutput());
}
remoteProc->deleteLater();
bool current = !remoteDigest.isEmpty() && !localDigest.isEmpty()
&& localDigest == remoteDigest;
emitResult(current);
});
LOG_DEBUG() << "docker manifest inspect" << imageRef;
remoteProc->start(Settings.dockerPath(), {"manifest", "inspect", imageRef});
});
LOG_DEBUG() << "docker image inspect" << imageRef;
localProc->start(Settings.dockerPath(), {"image", "inspect", imageRef});
}
bool Util::isChromiumAvailable()
{
// Check if Chromium executable file exists and is executable
QFileInfo fileInfo(Settings.chromiumPath());
return fileInfo.exists() && fileInfo.isExecutable();
}
bool Util::startDetached(const QString &program, const QStringList &arguments)
{
QProcess process;
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
// Remove parts of environment variables that our launch script has set
auto env = QProcessEnvironment::systemEnvironment();
// Get the parent of bin/shotcut
QString appDir = QFileInfo(QCoreApplication::applicationDirPath()).dir().absolutePath();
auto filterEnvVar = [&env, &appDir](const QString &varName) {
QString value = env.value(varName);
if (!value.isEmpty()) {
QStringList paths = value.split(':');
QStringList filtered;
for (QString &path : paths) {
if (!path.contains(appDir)) {
filtered << path;
} else {
LOG_DEBUG() << "removing" << path << "from env var" << varName;
}
}
env.insert(varName, filtered.join(':'));
}
};
filterEnvVar("FREI0R_PATH");
filterEnvVar("LADSPA_PATH");
filterEnvVar("LD_LIBRARY_PATH");
filterEnvVar("MANPATH");
filterEnvVar("PKG_CONFIG_PATH");
filterEnvVar("PYTHONHOME");
// These are relative paths and not cleanable with filterEnvVar()
env.remove("QML2_IMPORT_PATH");
env.remove("QT_PLUGIN_PATH");
process.setProcessEnvironment(env);
#endif
return process.startDetached(program, arguments);
}
bool Util::openUrl(const QUrl &url)
{
auto success = QDesktopServices::openUrl(url);
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
if (!success)
success = startDetached("xdg-open", {url.toString()});
#endif
return success;
}