mpv: Add screenshot action

When activated, it gets from MPV the currently playing frame as raw image data held in memory, which is then used to construct a TQImage, which is passed via DCOP to a newly launched instance of KSnapshot so that the user can choose what to do next like they would do with an ordinary screenshot.

If KSnapshot is not available, a save dialog is shown to choose a local or remote location for writing the image, in all the formats that KImageIO supports writing to.

Signed-off-by: Mavridis Philippe <mavridisf@gmail.com>
feat/libmpv-backend
Mavridis Philippe 6 months ago
parent b69aea1fa0
commit 37550ddc6a
No known key found for this signature in database
GPG Key ID: 93F66F98F906147D

@ -35,6 +35,7 @@
#include <tdeparts/genericfactory.h>
#include <tdeglobalsettings.h>
#include <tdeio/netaccess.h>
#include <tdeapplication.h>
#include <kstandarddirs.h>
#include <tdemessagebox.h>
#include <tdefiledialog.h>
@ -45,6 +46,9 @@
#include <tdetoolbar.h>
#include <tdeglobal.h>
#include <kiconloader.h>
#include <dcopclient.h>
#include <tdetempfile.h>
#include <kimageio.h>
#include <kdebug.h>
// Kaffeine
@ -180,6 +184,8 @@ void MpvPart::initActions()
new KWidgetAction(m_volume, i18n("Volume"), 0, 0, 0, actionCollection(), "player_volume");
/*** Stream recording toolbar ***/
new TDEAction(i18n("Take Screenshot"), "ksnapshot", Key_S, this, SLOT(slotShot()), actionCollection(), "record_shot");
m_recordAction = new TDEToggleAction(i18n("&Record stream"), "player_record", Key_R, this, SLOT(slotToggleRecording()), actionCollection(), "record_toggle");
new TDEAction(i18n("Set recording file"), "document-open", 0, this, SLOT(slotSetRecordingFile()), actionCollection(), "record_open");
m_recordFile = new TQLabel(0);
@ -753,4 +759,159 @@ void MpvPart::hideContextMenu() {
}
}
#define THROW_ERROR(msg, details) \
kdError() << "mpv: " << msg << " (" << details << ")" << endl; \
KMessageBox::detailedError(nullptr, i18n(msg), details); \
#define THROW_ERROR_FREE(msg, details) \
THROW_ERROR(msg, details) \
mpv_free_node_contents(&result);
void MpvPart::slotShot() {
mpv_node result;
int w, h, stride;
char *format;
mpv_byte_array *data;
// Get screenshot as raw data in memory
const char *args[] = {"screenshot-raw", nullptr};
if (!mpv_command_ret(m_mpv, args, &result) == MPV_ERROR_SUCCESS) {
THROW_ERROR("Internal error", "screenshot-raw command failed")
return;
}
if (result.format != MPV_FORMAT_NODE_MAP) {
THROW_ERROR("Internal error", "screenshot-raw command returned wrong data format")
return;
}
// Validate the result returned by `screenshot-raw`
#define CHECK_ARG(index, key) \
if (qstrcmp(result.u.list->keys[index], key) != 0) { \
TQString error = TQString("screenshot-raw returned wrong argument at #%1: %2") \
.arg(index).arg(key); \
THROW_ERROR_FREE("Internal error", error) \
return; \
}
CHECK_ARG(0, "w")
w = result.u.list->values[0].u.int64;
CHECK_ARG(1, "h")
h = result.u.list->values[1].u.int64;
CHECK_ARG(2, "stride")
stride = result.u.list->values[2].u.int64;
CHECK_ARG(3, "format")
format = result.u.list->values[3].u.string;
CHECK_ARG(4, "data")
data = result.u.list->values[4].u.ba;
#undef CHECK_ARG
// Validate image format (we can handle only bgr0, which is the default anyway)
kdDebug() << "mpv: screenshot-raw image format: " << format << endl;
if (qstrcmp(format, "bgr0") != 0) {
THROW_ERROR_FREE("Internal error", "mpv: screenshot-raw returned image format other than bgr0")
return;
}
// An empty image is useless but not a fatal error
if (w <= 0 || h <= 0) {
kdWarning() << "mpv: screenshot-raw returned empty image" << endl;
}
// Create a TQDataStream to easily read the raw image data
TQByteArray ba;
ba.setRawData(static_cast<char *>(data->data), data->size);
TQDataStream s(ba, IO_ReadOnly);
s.setByteOrder(TQDataStream::BigEndian);
// Create image from raw B8G8R8X8 data
TQImage img(w, h, 32);
for (uint y = 0; y < h; ++y) {
TQRgb *scanline = (TQRgb *)img.scanLine(y);
for (uint x = 0; x < w; x++) {
uchar r, g, b, z;
s >> b >> g >> r >> z;
scanline[x] = tqRgb(r, g, b);
}
}
// Try to launch ksnapshot
TQString ksError;
TQCString ksDcop;
int ksSuccess = kapp->startServiceByDesktopName("ksnapshot", TQString::null,
&ksError, &ksDcop);
if (ksSuccess == 0) {
// Serialize the image to pass via dcop
TQByteArray pix;
TQDataStream ds(pix, IO_WriteOnly);
ds << img;
// Send the serialized image to the newly initialized ksnapshot instance
if (!kapp->dcopClient()->send(ksDcop, "interface", "setPixmap(TQPixmap)", pix)) {
THROW_ERROR("Unable to launch KSnapshot!", i18n("You seem to be using an incompatible version of KSnapshot."));
return;
}
}
else {
TQStringList mimeTypes = KImageIO::mimeTypes(KImageIO::Writing);
if (mimeTypes.isEmpty()) {
THROW_ERROR("No image formats supported!", "KImageIO does not support any image format for export.");
return;
}
KFileDialog *saveDlg = new KFileDialog(TQString::null, TQString::null,
widget(), "screenshotSaver", false);
saveDlg->setOperationMode(KFileDialog::Saving);
saveDlg->setMimeFilter(mimeTypes);
if (!saveDlg->exec()) {
return;
}
KURL saveURL = saveDlg->selectedURL();
if (saveURL.isEmpty()) return;
TQString type = KImageIO::type(saveURL.url());
if (!KImageIO::canWrite(type)) {
THROW_ERROR("Cannot write image", "Format " + type + " is unsupported for export.")
return;
}
if (saveURL.isLocalFile()) {
if (!img.save(saveURL.path(), type.latin1())) {
THROW_ERROR("I/O Error", "Could not save file!")
return;
}
}
else {
KTempFile tempFile;
tempFile.setAutoDelete(true);
TQString tempFileName = tempFile.name();
if (tempFileName.isEmpty()) {
THROW_ERROR("I/O Error", "Could not create temporary file!")
return;
}
if (!img.save(tempFileName, type.latin1())) {
THROW_ERROR("I/O Error", "Could not write to temporary file!")
return;
}
if (!TDEIO::NetAccess::upload(tempFileName, saveURL, widget())) {
THROW_ERROR("Upload error", "Could not upload image.")
return;
}
}
}
}
#undef THROW_ERROR
#undef THROW_ERROR_FREE
#include "libmpv_part.moc"

@ -102,6 +102,8 @@ class MpvPart : public KaffeinePart
void slotNext();
void slotSetPosition(uint); /* percent */
void slotShot();
void slotToggleRecording();
void startRecording();
void stopRecording();

@ -12,7 +12,9 @@
<Action name="player_previous"/>
<Action name="player_next"/>
<Separator/>
<Action name="record_shot"/>
<Action name="record_toggle"/>
<Action name="record_open"/>
</Menu>
</MenuBar>
@ -34,6 +36,8 @@
<Separator/>
</ToolBar>
<ToolBar name="record" hidden="true"><text>Stream Recorder Toolbar</text>
<Action name="record_shot"/>
<Separator/>
<Action name="record_toggle"/>
<Action name="record_open"/>
<Action name="record_file"/>
@ -72,6 +76,7 @@
<Action name="player_mute"/>
<Action name="player_volume"/>
<Action name="record_shot"/>
<Action name="record_toggle"/>
<Action name="record_open"/>
<Action name="record_file"/>
@ -99,6 +104,7 @@
<Action name="player_mute"/>
<Action name="player_volume"/>
<Action name="record_shot"/>
<Action name="record_toggle"/>
<Action name="record_open"/>
<Action name="record_file"/>
@ -124,6 +130,8 @@
<Action name="player_mute"/>
<Action name="player_volume"/>
<Action name="record_shot"/>
</enable>
</State>
@ -143,6 +151,7 @@
<Action name="player_mute"/>
<Action name="player_volume"/>
<Action name="record_shot"/>
<Action name="record_toggle"/>
<Action name="record_open"/>
<Action name="record_file"/>

Loading…
Cancel
Save