Systemd changes | root script | URL support for gif effects (#1319)

* Systemd changes and URL option for Gif Effects
* Add grayscale to gif effect
* WebUI adjustments
* Rename version to .version
* Copy runHyperionAsRoot.sh to rpi packages
* Pack script into all unix packages
* Start hyperion only after network is available
* Snap builds removed due to poor server connection
* Flexible updateHyperionUser.sh
* updateHyperionUser script entered in the package
* Print help on none sudo execute
* Corrected embedded Python location
* Replacement for the QWindowsScreen grabWindow function
* Updated to latest 2.x mbedtls version 2.27

Co-authored-by: LordGrey <lordgrey.emmel@gmail.com>
This commit is contained in:
Markus
2021-10-02 18:02:52 +02:00
committed by GitHub
parent f269268def
commit eb96553975
37 changed files with 776 additions and 573 deletions

View File

@@ -70,9 +70,9 @@ QString EffectFileHandler::deleteEffect(const QString& effectName)
{
if (effectConfigurationFile.exists())
{
if ((it->script == ":/effects/gif.py") && !it->args.value("image").toString("").isEmpty())
if ((it->script == ":/effects/gif.py") && !it->args.value("file").toString("").isEmpty())
{
QFileInfo effectImageFile(it->args.value("image").toString());
QFileInfo effectImageFile(it->args.value("file").toString());
if (effectImageFile.exists())
{
QFile::remove(effectImageFile.absoluteFilePath());
@@ -159,19 +159,26 @@ QString EffectFileHandler::saveEffect(const QJsonObject& message)
newFileName.setFile(f);
}
if (!message["imageData"].toString("").isEmpty() && !message["args"].toObject().value("image").toString("").isEmpty())
if (!message["imageData"].toString("").isEmpty() && !message["args"].toObject().value("file").toString("").isEmpty())
{
QJsonObject args = message["args"].toObject();
QString imageFilePath = effectArray[0].toString().replace("$ROOT", _rootPath) + '/' + args.value("image").toString();
QString imageFilePath = effectArray[0].toString().replace("$ROOT", _rootPath) + '/' + args.value("file").toString();
QFileInfo imageFileName(imageFilePath);
if (!FileUtils::writeFile(imageFileName.absoluteFilePath(), QByteArray::fromBase64(message["imageData"].toString("").toUtf8()), _log))
{
return "Error while saving image file '" + message["args"].toObject().value("image").toString() + ", please check the Hyperion Log";
return "Error while saving image file '" + message["args"].toObject().value("file").toString() + ", please check the Hyperion Log";
}
//Update json with image file location
args["image"] = imageFilePath;
args["file"] = imageFilePath;
effectJson["args"] = args;
}
if (message["args"].toObject().value("imageSource").toString("") == "url" || message["args"].toObject().value("imageSource").toString("") == "file")
{
QJsonObject args = message["args"].toObject();
args.remove(args.value("imageSource").toString("") == "url" ? "file" : "url");
effectJson["args"] = args;
}

View File

@@ -12,6 +12,10 @@
#include <QDateTime>
#include <QImageReader>
#include <QBuffer>
#include <QUrl>
#include <QNetworkReply>
#include <QNetworkAccessManager>
#include <QEventLoop>
// Get the effect from the capsule
#define getEffect() static_cast<Effect*>((Effect*)PyCapsule_Import("hyperion.__effectObj", 0))
@@ -219,31 +223,57 @@ PyObject* EffectModule::wrapSetImage(PyObject *self, PyObject *args)
PyObject* EffectModule::wrapGetImage(PyObject *self, PyObject *args)
{
QString file;
QBuffer buffer;
QImageReader reader;
char *source;
int cropLeft = 0, cropTop = 0, cropRight = 0, cropBottom = 0;
bool grayscale = false;
if (getEffect()->_imageData.isEmpty())
{
Q_INIT_RESOURCE(EffectEngine);
char *source;
if(!PyArg_ParseTuple(args, "s", &source))
if(!PyArg_ParseTuple(args, "s|iiiii", &source, &cropLeft, &cropTop, &cropRight, &cropBottom, &grayscale))
{
PyErr_SetString(PyExc_TypeError, "String required");
return nullptr;
}
file = QString::fromUtf8(source);
const QUrl url = QUrl(source);
if (url.isValid())
{
QNetworkAccessManager *networkManager = new QNetworkAccessManager();
QNetworkReply * networkReply = networkManager->get(QNetworkRequest(url));
if (file.mid(0, 1) == ":")
file = ":/effects/"+file.mid(1);
QEventLoop eventLoop;
connect(networkReply, &QNetworkReply::finished, &eventLoop, &QEventLoop::quit);
eventLoop.exec();
reader.setDecideFormatFromContent(true);
reader.setFileName(file);
if (networkReply->error() == QNetworkReply::NoError)
{
buffer.setData(networkReply->readAll());
buffer.open(QBuffer::ReadOnly);
reader.setDecideFormatFromContent(true);
reader.setDevice(&buffer);
}
delete networkReply;
delete networkManager;
}
else
{
QString file = QString::fromUtf8(source);
if (file.mid(0, 1) == ":")
file = ":/effects/"+file.mid(1);
reader.setDecideFormatFromContent(true);
reader.setFileName(file);
}
}
else
{
PyArg_ParseTuple(args, "|siiiii", &source, &cropLeft, &cropTop, &cropRight, &cropBottom, &grayscale);
buffer.setData(QByteArray::fromBase64(getEffect()->_imageData.toUtf8()));
buffer.open(QBuffer::ReadOnly);
reader.setDecideFormatFromContent(true);
@@ -260,19 +290,33 @@ PyObject* EffectModule::wrapGetImage(PyObject *self, PyObject *args)
if (reader.canRead())
{
QImage qimage = reader.read();
int width = qimage.width();
int height = qimage.height();
if (cropLeft > 0 || cropTop > 0 || cropRight > 0 || cropBottom > 0)
{
if (cropLeft + cropRight >= width || cropTop + cropBottom >= height)
{
QString errorStr = QString("Rejecting invalid crop values: left: %1, right: %2, top: %3, bottom: %4, higher than height/width %5/%6").arg(cropLeft).arg(cropRight).arg(cropTop).arg(cropBottom).arg(height).arg(width);
PyErr_SetString(PyExc_RuntimeError, qPrintable(errorStr));
return nullptr;
}
qimage = qimage.copy(cropLeft, cropTop, width - cropLeft - cropRight, height - cropTop - cropBottom);
width = qimage.width();
height = qimage.height();
}
QByteArray binaryImage;
for (int i = 0; i<height; ++i)
for (int i = 0; i<height; i++)
{
const QRgb *scanline = reinterpret_cast<const QRgb *>(qimage.scanLine(i));
for (int j = 0; j< width; ++j)
const QRgb *end = scanline + qimage.width();
for (; scanline != end; scanline++)
{
binaryImage.append((char) qRed(scanline[j]));
binaryImage.append((char) qGreen(scanline[j]));
binaryImage.append((char) qBlue(scanline[j]));
binaryImage.append(!grayscale ? (char) qRed(scanline[0]) : (char) qGray(scanline[0]));
binaryImage.append(!grayscale ? (char) qGreen(scanline[1]) : (char) qGray(scanline[1]));
binaryImage.append(!grayscale ? (char) qBlue(scanline[2]) : (char) qGray(scanline[2]));
}
}
PyList_SET_ITEM(result, i, Py_BuildValue("{s:i,s:i,s:O}", "imageWidth", width, "imageHeight", height, "imageData", PyByteArray_FromStringAndSize(binaryImage.constData(),binaryImage.size())));
@@ -283,6 +327,7 @@ PyObject* EffectModule::wrapGetImage(PyObject *self, PyObject *args)
return nullptr;
}
}
return result;
}
else

View File

@@ -11,6 +11,10 @@
#include <QJsonArray>
#include <QJsonDocument>
#ifdef _WIN32
#include <Windows.h>
#endif
// Constants
namespace {
const bool verbose = false;
@@ -156,6 +160,58 @@ void QtGrabber::geometryChanged(const QRect &geo)
updateScreenDimensions(true);
}
#ifdef _WIN32
extern QPixmap qt_pixmapFromWinHBITMAP(HBITMAP bitmap, int format = 0);
QPixmap QtGrabber::grabWindow(quintptr window, int xIn, int yIn, int width, int height) const
{
QSize windowSize;
int x = xIn;
int y = yIn;
HWND hwnd = reinterpret_cast<HWND>(window);
if (hwnd)
{
RECT r;
GetClientRect(hwnd, &r);
windowSize = QSize(r.right - r.left, r.bottom - r.top);
}
else
{
hwnd = GetDesktopWindow();
const QRect screenGeometry = _screen->geometry();
windowSize = screenGeometry.size();
x += screenGeometry.x();
y += screenGeometry.y();
}
if (width < 0)
width = windowSize.width() - x;
if (height < 0)
height = windowSize.height() - y;
// Create and setup bitmap
HDC display_dc = GetDC(nullptr);
HDC bitmap_dc = CreateCompatibleDC(display_dc);
HBITMAP bitmap = CreateCompatibleBitmap(display_dc, width, height);
HGDIOBJ null_bitmap = SelectObject(bitmap_dc, bitmap);
// copy data
HDC window_dc = GetDC(hwnd);
BitBlt(bitmap_dc, 0, 0, width, height, window_dc, x, y, SRCCOPY);
// clean up all but bitmap
ReleaseDC(hwnd, window_dc);
SelectObject(bitmap_dc, null_bitmap);
DeleteDC(bitmap_dc);
const QPixmap pixmap = qt_pixmapFromWinHBITMAP(bitmap);
DeleteObject(bitmap);
ReleaseDC(nullptr, display_dc);
return pixmap;
}
#endif
int QtGrabber::grabFrame(Image<ColorRgb> & image)
{
int rc = 0;
@@ -170,7 +226,11 @@ int QtGrabber::grabFrame(Image<ColorRgb> & image)
if (_isEnabled)
{
#ifdef _WIN32
QPixmap originalPixmap = grabWindow(0, _src_x, _src_y, _src_x_max, _src_y_max);
#else
QPixmap originalPixmap = _screen->grabWindow(0, _src_x, _src_y, _src_x_max, _src_y_max);
#endif
if (originalPixmap.isNull())
{
rc = -1;

View File

@@ -23,17 +23,26 @@ PythonInit::PythonInit()
// register modules
EffectModule::registerHyperionExtensionModule();
// Set Program name
Py_SetProgramName(L"Hyperion");
// set Python module path when exists
QString py_patch = QDir::cleanPath(qApp->applicationDirPath() + "/../lib/python");
QString py_patch = QDir::cleanPath(qApp->applicationDirPath() + "/../lib/python" + STRINGIFY(PYTHON_VERSION_MAJOR_MINOR));
QString py_file = QDir::cleanPath(qApp->applicationDirPath() + "/python" + STRINGIFY(PYTHON_VERSION_MAJOR_MINOR) + ".zip");
if (QFile(py_file).exists() || QDir(py_patch).exists())
{
Py_NoSiteFlag++;
if (QFile(py_file).exists())
{
Py_SetPythonHome(Py_DecodeLocale(py_file.toLatin1().data(), nullptr));
Py_SetPath(Py_DecodeLocale(py_file.toLatin1().data(), nullptr));
}
else if (QDir(py_patch).exists())
{
Py_SetPythonHome(Py_DecodeLocale(py_file.toLatin1().data(), nullptr));
Py_SetPath(Py_DecodeLocale(py_patch.toLatin1().data(), nullptr));
}
}
// init Python