Rewrite in order to support user-selected formats

This commit is contained in:
ArsenArsen 2017-06-06 17:05:34 +02:00
parent 1d059cc08a
commit 0b09000729
22 changed files with 211 additions and 123 deletions

View File

@ -51,7 +51,8 @@ SOURCES += main.cpp\
screenareaselector/screenareaselector.cpp \
recording/recordingpreview.cpp \
recording/recordingcontroller.cpp \
recording/recordingformats.cpp
recording/recordingformats.cpp \
formats.cpp
HEADERS += mainwindow.hpp \
cropeditor/cropeditor.hpp \
@ -84,7 +85,8 @@ HEADERS += mainwindow.hpp \
screenareaselector/screenareaselector.hpp \
recording/recordingpreview.hpp \
recording/recordingcontroller.hpp \
recording/recordingformats.hpp
recording/recordingformats.hpp \
formats.hpp
mac {
SOURCES += $$PWD/platformspecifics/mac/macbackend.cpp

63
formats.cpp Normal file
View File

@ -0,0 +1,63 @@
#include <formats.hpp>
QString formats::normalFormatName(formats::Normal format) {
switch (format) {
case Normal::JPG:
return "JPG";
break;
case Normal::PNG:
return "PNG";
break;
default:
return QString();
break;
}
}
formats::Normal formats::normalFormatFromName(QString format) {
if (format.toLower() == "jpg") return Normal::JPG;
if (format.toLower() == "jpeg") return Normal::JPG;
if (format.toLower() == "png") return Normal::PNG;
return Normal::None;
}
QString formats::normalFormatMIME(formats::Normal format) {
switch (format) {
case Normal::JPG:
return "image/jpeg";
break;
case Normal::PNG:
return "image/png";
break;
default:
return QString();
break;
}
}
QString formats::recordingFormatName(formats::Recording format) {
switch (format) {
case Recording::GIF:
return "GIF";
break;
default:
return QString();
break;
}
}
formats::Recording formats::recordingFormatFromName(QString format) {
if (format.toLower() == "gif") return Recording::GIF;
return Recording::None;
}
QString formats::recordingFormatMIME(formats::Recording format) {
switch (format) {
case Recording::GIF:
return "image/gif";
break;
default:
return QString();
break;
}
}

17
formats.hpp Normal file
View File

@ -0,0 +1,17 @@
#ifndef FORMATS_HPP
#define FORMATS_HPP
#include <QString>
namespace formats {
enum class Normal { PNG, JPG, None };
QString normalFormatName(Normal format);
Normal normalFormatFromName(QString format);
QString normalFormatMIME(Normal format);
enum class Recording { GIF, None };
QString recordingFormatName(Recording format);
Recording recordingFormatFromName(QString format);
QString recordingFormatMIME(Recording format);
}
#endif // FORMATS_HPP

View File

@ -3,7 +3,7 @@
#include <QDateTime>
#include <QStringList>
QString formatter::format(QString toFormat) {
QString formatter::format(QString toFormat, QString ext) {
QRegExp dateRegex("%\\((.+)\\)date");
dateRegex.indexIn(toFormat);
QStringList capturedTexts(dateRegex.capturedTexts());
@ -12,5 +12,6 @@ QString formatter::format(QString toFormat) {
for (int i = 0; i < capturedTexts.length(); i += 2) {
formatted = formatted.replace(capturedTexts.at(i), date.toString(capturedTexts.at(i + 1)));
}
formatted = formatted.replace(QRegExp("%(?!%)ext"), ext);
return formatted;
}

View File

@ -5,7 +5,7 @@
#include <QString>
namespace formatter {
QString format(QString toFormat);
QString format(QString toFormat, QString ext);
}
#endif // FORMATTER_HPP

View File

@ -2,7 +2,9 @@
#include "screenshotutil.hpp"
#include <QApplication>
#include <QCommandLineParser>
#include <QDebug>
#include <QtGlobal>
#include <formatter.hpp>
#include <iostream>
#include <notifications.hpp>
#include <worker/worker.hpp>
@ -56,7 +58,6 @@ int main(int argc, char *argv[]) {
}
verbose = parser.isSet(v);
MainWindow w;
Worker::init();
a.connect(&a, &QApplication::aboutToQuit, Worker::end);

View File

@ -91,15 +91,16 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
addHotkeyItem("Color picker", "picker", [] { ColorPickerScene::showPicker(); });
addHotkeyItem("Stop Recording", "recordingstop", [&] { controller->end(); });
addHotkeyItem("Start Recording", "recordingstart", [&] {
auto f = static_cast<RecordingFormats::Format>(
settings::settings().value("recording/format", (int)RecordingFormats::None).toInt());
if (f == RecordingFormats::None) return;
auto f
= static_cast<formats::Recording>(settings::settings().value("recording/format", (int)formats::Recording::None).toInt());
if (f >= formats::Recording::None) return;
RecordingContext *ctx = new RecordingContext;
RecordingFormats *format = new RecordingFormats(f);
ctx->consumer = format->getConsumer();
ctx->finalizer = format->getFinalizer();
ctx->validator = format->getValidator();
ctx->format = format->getFormat();
ctx->anotherFormat = format->getAnotherFormat();
controller->start(ctx);
});
@ -107,11 +108,16 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
ui->hideToTray->setChecked(settings::settings().value("hideOnClose", true).toBool());
ui->captureCursor->setChecked(settings::settings().value("captureCursor", true).toBool());
for (int i = 0; i < RecordingFormats::None; i++) {
ui->formatBox->addItem(RecordingFormats::getExt(static_cast<RecordingFormats::Format>(i)));
for (int i = 0; i < (int)formats::Recording::None; i++) {
ui->formatBox->addItem(formats::recordingFormatName(static_cast<formats::Recording>(i)));
}
for (int i = 0; i < (int)formats::Normal::None; i++) {
ui->imageFormatBox->addItem(formats::normalFormatName(static_cast<formats::Normal>(i)));
}
ui->formatBox->addItem("None");
ui->formatBox->setCurrentIndex(settings::settings().value("recording/format", (int)RecordingFormats::None).toInt());
ui->formatBox->setCurrentIndex(settings::settings().value("recording/format", (int)formats::Recording::None).toInt());
}
MainWindow::~MainWindow() {
@ -219,3 +225,7 @@ void MainWindow::on_captureCursor_clicked(bool checked) {
void MainWindow::on_formatBox_currentIndexChanged(int index) {
settings::settings().setValue("recording/format", index);
}
void MainWindow::on_imageFormatBox_currentIndexChanged(const QString &arg1) {
settings::settings().setValue("imageformat", arg1);
}

View File

@ -33,8 +33,8 @@ private slots:
void on_hideToTray_clicked(bool checked);
void on_actionColor_Picker_triggered();
void on_captureCursor_clicked(bool checked);
void on_formatBox_currentIndexChanged(int index);
void on_imageFormatBox_currentIndexChanged(const QString &arg1);
public:
explicit MainWindow(QWidget *parent = 0);

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>512</width>
<height>412</height>
<width>483</width>
<height>478</height>
</rect>
</property>
<property name="windowTitle">
@ -25,7 +25,10 @@
</sizepolicy>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="9" column="1">
<item row="15" column="1">
<widget class="QComboBox" name="imageFormatBox"/>
</item>
<item row="12" column="1" rowspan="2">
<widget class="QComboBox" name="formatBox">
<property name="currentIndex">
<number>-1</number>
@ -70,7 +73,7 @@
</property>
</widget>
</item>
<item row="11" column="0" colspan="2">
<item row="16" column="0" colspan="2">
<widget class="QLabel" name="label_6">
<property name="text">
<string>&lt;a href=&quot;https://github.com/ArsenArsen/KShare&quot;&gt;Source code available free for everyone. Forever.&lt;/a&gt;
@ -112,24 +115,31 @@
<item row="1" column="0">
<widget class="QListWidget" name="uploaderList"/>
</item>
<item row="9" column="0">
<widget class="QCheckBox" name="captureCursor">
<item row="13" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Capture cursor</string>
<string>Recording format</string>
</property>
</widget>
</item>
<item row="7" column="0">
<item row="15" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Still image format</string>
</property>
</widget>
</item>
<item row="8" column="0" colspan="2">
<widget class="QCheckBox" name="hideToTray">
<property name="text">
<string>Pressing &lt;X&gt; hides to tray</string>
</property>
</widget>
</item>
<item row="7" column="1" rowspan="2">
<widget class="QLabel" name="label_4">
<item row="7" column="0" colspan="2">
<widget class="QCheckBox" name="captureCursor">
<property name="text">
<string>Recording format</string>
<string>Capture cursor</string>
</property>
</widget>
</item>
@ -140,7 +150,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>512</width>
<width>483</width>
<height>25</height>
</rect>
</property>

View File

@ -38,7 +38,12 @@ bool RecordingController::end() {
preview = 0;
WorkerContext *c = new WorkerContext;
c->consumer = [&](QImage) { queue(_context->finalizer()); };
c->consumer = [&](QImage) {
_QueueContext contx;
contx.arr = _context->finalizer();
contx.format = _context->anotherFormat;
queue(contx);
};
c->targetFormat = QImage::Format_Alpha8;
c->pixmap = QPixmap(0, 0);
Worker::queue(c);
@ -48,7 +53,7 @@ bool RecordingController::end() {
return true;
}
void RecordingController::queue(QByteArray arr) {
void RecordingController::queue(_QueueContext arr) {
QMutexLocker l(&lock);
uploadQueue.enqueue(arr);
}
@ -86,7 +91,10 @@ void RecordingController::timeout() {
preview->setTime(QString("%1:%2").arg(QString::number(minute)).arg(QString::number(second)), frame);
} else {
QMutexLocker l(&lock);
if (!uploadQueue.isEmpty()) UploaderSingleton::inst().upload(uploadQueue.dequeue());
if (!uploadQueue.isEmpty()) {
auto a = uploadQueue.dequeue();
UploaderSingleton::inst().upload(a.arr, a.format);
}
}
}

View File

@ -12,12 +12,17 @@
#include <functional>
#include <memory>
class RecordingContext {
public:
struct RecordingContext {
QImage::Format format;
std::function<void(QImage)> consumer;
std::function<bool()> validator;
std::function<QByteArray()> finalizer;
QString anotherFormat;
};
struct _QueueContext {
QByteArray arr;
QString format;
};
class RecordingController : public QObject {
@ -30,14 +35,14 @@ public slots:
bool start(RecordingContext *context);
// Returns false if not running
bool end();
void queue(QByteArray arr);
void queue(_QueueContext arr);
private slots:
void timeout();
void startWithArea(QRect newArea);
private:
QMutex lock;
QQueue<QByteArray> uploadQueue;
QQueue<_QueueContext> uploadQueue;
QRect area;
RecordingContext *_context = 0;
QTimer timer;

View File

@ -7,13 +7,14 @@
#include <QFile>
#include <QStandardPaths>
#include <QTimer>
#include <formats.hpp>
#include <gif-h/gif.h>
#include <platformbackend.hpp>
#include <settings.hpp>
#include <time.h>
#include <unistd.h>
RecordingFormats::RecordingFormats(RecordingFormats::Format f) {
RecordingFormats::RecordingFormats(formats::Recording f) {
QString path = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
if (path.isEmpty()) {
@ -26,7 +27,7 @@ RecordingFormats::RecordingFormats(RecordingFormats::Format f) {
tmpDir.mkdir(name);
tmpDir.cd(name);
switch (f) {
case GIF: {
case formats::Recording::GIF: {
iFormat = QImage::Format_RGBA8888;
validator = [] { return true; };
consumer = [&](QImage img) { frames.push_back(img); };
@ -49,6 +50,7 @@ RecordingFormats::RecordingFormats(RecordingFormats::Format f) {
QByteArray data = res.readAll();
return data;
};
anotherFormat = formats::recordingFormatName(f);
break;
}
default:
@ -72,16 +74,6 @@ QImage::Format RecordingFormats::getFormat() {
return iFormat;
}
QString RecordingFormats::getExt(RecordingFormats::Format f) {
switch (f) {
case None:
return "None";
break;
case GIF:
return "gif";
break;
default:
return QString();
break;
}
QString RecordingFormats::getAnotherFormat() {
return anotherFormat;
}

View File

@ -5,18 +5,17 @@
#include <QFile>
#include <QImage>
#include <QString>
#include <formats.hpp>
#include <functional>
class RecordingFormats {
public:
enum Format { GIF, None };
RecordingFormats(Format f);
RecordingFormats(formats::Recording f);
std::function<void(QImage)> getConsumer();
std::function<QByteArray()> getFinalizer();
std::function<bool()> getValidator();
QImage::Format getFormat();
static QString getExt(Format f);
QString getAnotherFormat();
private:
std::function<void(QImage)> consumer;
@ -25,7 +24,7 @@ private:
std::vector<QImage> frames;
QImage::Format iFormat;
QDir tmpDir;
int frame = 0;
QString anotherFormat;
};
#endif // RECORDINGFORMATS_HPP

View File

@ -7,9 +7,12 @@
#include <QFile>
#include <QJsonDocument>
#include <QNetworkReply>
#include <formats.hpp>
#include <io/ioutils.hpp>
#include <notifications.hpp>
using formats::normalFormatFromName;
using formats::normalFormatMIME;
using std::runtime_error;
void error(QString absFilePath, QString err) {
@ -17,12 +20,6 @@ void error(QString absFilePath, QString err) {
}
CustomUploader::CustomUploader(QString absFilePath) {
types.insert("PNG", "image/png"); // This is a list of supported formats, too
types.insert("GIF", "image/gif");
types.insert("JPG", "image/jpeg");
types.insert("JPEG", "image/jpeg");
types.insert("WEBM", "video/webm");
types.insert("MP4", "video/mp4");
// Let's go
QFile file(absFilePath);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) error(absFilePath, file.errorString());
@ -74,16 +71,6 @@ CustomUploader::CustomUploader(QString absFilePath) {
}
} else
error(absFilePath, "format provided but not string");
QJsonValue imageValue = obj["imageformat"];
if (!imageValue.isString()) {
error(absFilePath, "imageformat not string");
}
QString imageFormat = imageValue.toString();
if (imageFormat == "base64" || QRegExp("base64\\([^+]+\\+[^+]+)").exactMatch(imageFormat)
|| QRegExp("[^+]+\\+[^+]+").exactMatch(imageFormat)) {
this->iFormat = imageFormat;
} else
error(absFilePath, "imageformat invalid");
QJsonValue bodyValue = obj["body"];
if (rFormat != RequestFormat::PLAIN) {
if (bodyValue.isUndefined()) error(absFilePath, "body not set");
@ -113,6 +100,11 @@ CustomUploader::CustomUploader(QString absFilePath) {
if (!fileLimit.isDouble()) error(absFilePath, "fileLimit not double");
limit = fileLimit.toDouble();
}
QJsonValue bool64 = obj["base64"];
if (!bool64.isNull() && !bool64.isUndefined()) {
if (!bool64.isBool()) error(absFilePath, "base64 must be boolean");
base64 = bool64.toBool();
}
}
QString CustomUploader::name() {
@ -123,10 +115,6 @@ QString CustomUploader::description() {
return desc;
}
std::tuple<QString, QString> CustomUploader::format() {
return std::tuple<QString, QString>(getFormatString(false), getFormatString(true));
}
QString getCType(RequestFormat format, QString plainType) {
switch (format) {
case RequestFormat::X_WWW_FORM_URLENCODED:
@ -139,7 +127,7 @@ QString getCType(RequestFormat format, QString plainType) {
return plainType;
}
QList<QPair<QString, QString>> getHeaders(QJsonObject h, QString imageFormat, QMap<QString, QString> types, RequestFormat format) {
QList<QPair<QString, QString>> getHeaders(QJsonObject h, QString imageFormat, RequestFormat format) {
QList<QPair<QString, QString>> headers;
for (QString s : h.keys()) {
if (s.toLower() == "content-type") continue;
@ -149,20 +137,10 @@ QList<QPair<QString, QString>> getHeaders(QJsonObject h, QString imageFormat, QM
else
headers << QPair<QString, QString>(s, v.toString());
}
headers << QPair<QString, QString>("Content-Type", getCType(format, types.value(imageFormat)));
headers << QPair<QString, QString>("Content-Type", getCType(format, normalFormatMIME(normalFormatFromName(imageFormat))));
return headers;
}
QString CustomUploader::getFormatString(bool animated) {
if (iFormat == "base64")
return animated ? "GIF" : "PNG";
else if (QRegExp("[^+]+\\+[^+]+").exactMatch(iFormat))
return iFormat.split('+')[(int)animated];
else if (QRegExp("base64\\([^+]+\\+[^+]+)").exactMatch(iFormat))
return iFormat.mid(7, iFormat.length() - 8).split('+')[(int)animated];
return "";
}
QJsonObject recurseAndReplace(QJsonObject &body, QByteArray &data, QString contentType) {
QJsonObject o;
for (QString s : body.keys()) {
@ -234,25 +212,26 @@ void parseResult(QJsonDocument result, QByteArray data, QString returnPathspec,
}
}
void CustomUploader::doUpload(QByteArray imgData) {
auto h = getHeaders(headers, getFormatString(false), types, this->rFormat);
QString format = getFormatString(false); // Soon:tm:
void CustomUploader::doUpload(QByteArray imgData, QString format) {
auto h = getHeaders(headers, format, this->rFormat);
QByteArray data;
if (iFormat == "base64" || QRegExp("base64\\([^+]\\+[^+]\\)").exactMatch(iFormat)) imgData = imgData.toBase64();
if (base64) imgData = imgData.toBase64();
switch (this->rFormat) {
case RequestFormat::PLAIN: {
data = imgData;
} break;
case RequestFormat::JSON: {
if (body.isString()) {
QStringList split = body.toString().replace("%contenttype", types.value(format)).split("%imagedata");
QStringList split = body.toString().replace("%contenttype", normalFormatMIME(normalFormatFromName(format))).split("%imagedata");
for (int i = 0; i < split.size(); i++) {
data.append(split[i]);
if (i < split.size() - 1) data.append(imgData);
}
} else {
QJsonObject vo = body.toObject();
data = QJsonDocument::fromVariant(recurseAndReplace(vo, imgData, types.value(format)).toVariantMap()).toJson();
data = QJsonDocument::fromVariant(
recurseAndReplace(vo, imgData, normalFormatMIME(normalFormatFromName(format))).toVariantMap())
.toJson();
}
} break;
case RequestFormat::X_WWW_FORM_URLENCODED: {
@ -264,7 +243,8 @@ void CustomUploader::doUpload(QByteArray imgData) {
QByteArray strB;
if (str.startsWith("/") && str.endsWith("/")) {
str = str.mid(1, str.length() - 2);
QStringList split = str.replace("%contenttype", types.value(format)).split("%imagedata");
QStringList split
= str.replace("%contenttype", normalFormatMIME(normalFormatFromName(format))).split("%imagedata");
for (int i = 0; i < split.size(); i++) {
strB.append(split[i]);
if (i < split.size() - 1) strB.append(imgData);

View File

@ -15,10 +15,7 @@ public:
CustomUploader(QString absFilePath);
QString name();
QString description();
std::tuple<QString, QString> format();
void doUpload(QByteArray imgData);
QString getFormatString(bool animated);
QMap<QString, QString> types;
void doUpload(QByteArray imgData, QString format);
private:
double limit = -1;
@ -29,8 +26,8 @@ private:
QUrl target;
QJsonValue body;
QJsonObject headers;
bool base64 = false;
QString returnPathspec;
QString iFormat;
};
#endif // CUSTOMUPLOADER_HPP

View File

@ -2,9 +2,17 @@
#include <QApplication>
#include <QClipboard>
#include <QMimeData>
#include <formats.hpp>
#include <notifications.hpp>
void ClipboardUploader::doUpload(QByteArray imgData) {
QApplication::clipboard()->setImage(QImage::fromData(imgData, std::get<0>(format()).toLocal8Bit().constData()));
void ClipboardUploader::doUpload(QByteArray imgData, QString format) {
auto f = formats::recordingFormatFromName(format);
if (f != formats::Recording::None) {
auto data = new QMimeData();
data->setData(formats::recordingFormatMIME(f), imgData);
QApplication::clipboard()->setMimeData(data);
}
QApplication::clipboard()->setImage(QImage::fromData(imgData, format.toLocal8Bit().constData()));
notifications::notify("KShare", "Copied to clipboard!");
}

View File

@ -12,11 +12,8 @@ public:
QString description() {
return "Copies the image to clipboard";
}
std::tuple<QString, QString> format() {
return std::tuple<QString, QString>("PNG", "MP4");
}
void doUpload(QByteArray imgData);
void doUpload(QByteArray imgData, QString format);
};
#endif // CLIPBOARDUPLOADER_HPP

View File

@ -7,7 +7,7 @@
#include <notifications.hpp>
#include <screenshotutil.hpp>
void ImgurUploader::doUpload(QByteArray byteArray) {
void ImgurUploader::doUpload(QByteArray byteArray, QString) {
if (byteArray.size() > 1e+7) {
notifications::notify("KShare imgur Uploader ", "Failed upload! Image too big");
return;

View File

@ -11,10 +11,7 @@ public:
QString description() {
return "imgur.com uploader";
}
std::tuple<QString, QString> format() {
return std::tuple<QString, QString>("PNG", "MP4");
}
void doUpload(QByteArray byteArray);
void doUpload(QByteArray byteArray, QString);
};
#endif // IMGURUPLOADER_HPP

View File

@ -6,10 +6,9 @@
class Uploader {
public:
virtual void doUpload(QByteArray imgData) = 0;
virtual void doUpload(QByteArray imgData, QString format) = 0;
virtual QString name() = 0;
virtual QString description() = 0;
virtual std::tuple<QString, QString> format() = 0;
};
#endif // UPLOADER_HPP

View File

@ -5,8 +5,11 @@
#include <QBuffer>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QStandardPaths>
#include <formats.hpp>
#include <formatter.hpp>
#include <notifications.hpp>
#include <settings.hpp>
UploaderSingleton::UploaderSingleton()
@ -57,29 +60,29 @@ void UploaderSingleton::registerUploader(Uploader *uploader) {
void UploaderSingleton::upload(QPixmap *pixmap) {
auto u = uploaders.value(uploader);
QByteArray arr;
QBuffer data(&arr);
pixmap->save(&data, std::get<0>(u->format()).toLocal8Bit().constData());
u->doUpload(arr);
data.close();
QString format = settings::settings().value("captureformat", "PNG").toString();
QFile file(saveDir.absoluteFilePath(formatter::format(settings::settings().value("fileFormat").toString(), format.toLower())));
if (file.open(QFile::ReadWrite)) {
pixmap->save(&file, format.toLocal8Bit().constData());
file.seek(0);
u->doUpload(file.readAll(), format);
} else
notifications::notify("KShare - Failed to save picture", file.errorString(), QSystemTrayIcon::Warning);
delete pixmap;
}
void UploaderSingleton::upload(QByteArray img) {
uploaders.value(uploader)->doUpload(img);
void UploaderSingleton::upload(QByteArray img, QString format) {
uploaders.value(uploader)->doUpload(img, format);
}
void UploaderSingleton::upload(QFile img) {
void UploaderSingleton::upload(QFile img, QString format) {
if (img.open(QIODevice::ReadOnly)) {
uploaders.value(uploader)->doUpload(img.readAll());
uploaders.value(uploader)->doUpload(img.readAll(), format);
img.close();
}
}
std::tuple<QString, QString> UploaderSingleton::format() {
return uploaders.value(uploader)->format();
}
QList<Uploader *> UploaderSingleton::uploaderList() {
return uploaders.values();
}

View File

@ -14,9 +14,8 @@ public:
}
void registerUploader(Uploader *uploader);
void upload(QPixmap *pixmap);
void upload(QByteArray img);
void upload(QFile img);
virtual std::tuple<QString, QString> format();
void upload(QByteArray img, QString format);
void upload(QFile img, QString format);
QList<Uploader *> uploaderList();
void set(QString uploader);
QString selectedUploader();