12#include <QApplication>
13#include <QCoreApplication>
18#include <QTemporaryFile>
23#include "libmythbase/mythconfig.h"
39#define LOC QString("Preview: ")
76 m_programInfo(*pginfo), m_mode(mode),
77 m_pathname(pginfo->GetPathname()),
78 m_token(
std::move(token))
83 moveToThread(QApplication::instance()->thread());
94 if (fileName.isEmpty())
96 QFileInfo fileinfo = QFileInfo(fileName);
111 QObject::deleteLater();
126 QTime tm = QTime::currentTime();
127 QElapsedTimer te; te.start();
133 LOG(VB_GENERAL, LOG_ERR,
LOC +
134 QString(
"RunReal() file not local: '%1'")
139 LOG(VB_GENERAL, LOG_ERR,
LOC +
140 QString(
"RunReal() Preview of '%1' failed "
141 "because mode was invalid 0x%2")
147 msg = QString(
"Generated on %1 in %2 seconds, starting at %3")
149 .arg(te.elapsed()*0.001)
156 LOG(VB_GENERAL, LOG_WARNING,
LOC +
"Failed to save preview."
157 "\n\t\t\tYou may need to check user and group ownership on"
158 "\n\t\t\tyour frontend and backend for quicker previews.\n"
159 "\n\t\t\tAttempting to regenerate preview on backend.\n");
164 msg = QString(
"Generated remotely in %1 seconds, starting at %2")
165 .arg(te.elapsed()*0.001)
170 msg =
"Remote preview failed";
175 msg =
"Could not access recording";
189 QFileInfo fi(output_fn);
191 dt = fi.lastModified();
194 QString message = (ok) ?
"PREVIEW_SUCCESS" :
"PREVIEW_FAILED";
197 list.push_back(output_fn);
199 list.push_back(dt.isValid()?dt.toUTC().toString(
Qt::ISODate):
"");
210 QTime tm = QTime::currentTime();
211 QElapsedTimer te; te.start();
216 QFileInfo(command).isExecutable());
225 QString(
"Generated remotely in %1 seconds, starting at %2")
226 .arg(te.elapsed()*0.001)
232 LOG(VB_GENERAL, LOG_ERR,
LOC +
233 QString(
"Run() cannot generate preview locally for: '%1'")
235 msg =
"Failed, local preview requested for remote file.";
246 cmdargs <<
"--seconds" << QString::number(
m_captureTime.count());
249 cmdargs <<
"--chanid"
268 uint ret = ms->Wait();
273 LOG(VB_GENERAL, LOG_ERR,
LOC +
274 QString(
"Encountered problems running '%1 %2' - (%3)")
275 .arg(command, cmdargs.join(
" "), QString::number(ret)));
279 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"Preview process returned 0.");
283 QString lpath = QFileInfo(outname).fileName();
284 if (lpath == outname)
287 QString tmpFile = sgroup.
FindFile(lpath);
288 outname = (tmpFile.isEmpty()) ? outname : tmpFile;
291 QFileInfo fi(outname);
292 ok = (fi.exists() && fi.isReadable() && (fi.size() != 0));
295 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"Preview process ran ok.");
296 msg = QString(
"Generated on %1 in %2 seconds, starting at %3")
298 .arg(te.elapsed()*0.001)
303 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Preview process not ok." +
304 QString(
"\n\t\t\tfileinfo(%1)").arg(outname) +
305 QString(
" exists: %1").arg(fi.exists()) +
306 QString(
" readable: %1").arg(fi.isReadable()) +
307 QString(
" size: %1").arg(fi.size()));
308 LOG(VB_GENERAL, LOG_ERR,
LOC +
309 QString(
"Despite command '%1' returning success")
311 msg = QString(
"Failed to read preview image despite "
312 "preview process returning success.");
327 QFileInfo fi(output_fn);
329 dt = fi.lastModified();
332 QString message = (ok) ?
"PREVIEW_SUCCESS" :
"PREVIEW_FAILED";
337 list.push_back(output_fn);
339 list.push_back(dt.isValid()?dt.toUTC().toString(
Qt::ISODate):
"");
356 QStringList strlist(
"QUERY_GENPIXMAP2" );
366 strlist.push_back(
"s");
371 strlist.push_back(
"f");
376 strlist.push_back(
"<EMPTY>");
381 strlist.push_back(fi.fileName());
383 strlist.push_back(QString::number(
m_outSize.width()));
384 strlist.push_back(QString::number(
m_outSize.height()));
390 if (!ok || strlist.empty() || (strlist[0] !=
"OK"))
394 LOG(VB_GENERAL, LOG_ERR,
LOC +
395 "Remote Preview failed due to communications error.");
397 else if (strlist.size() > 1)
399 LOG(VB_GENERAL, LOG_ERR,
LOC +
400 "Remote Preview failed, reason given: " + strlist[1]);
416 LOG(VB_GENERAL, LOG_NOTICE,
LOC +
"RemotePreviewRun() -- no reply..");
426 return QObject::event(e);
430 return QObject::event(e);
431 if (me->Message() !=
"GENERATED_PIXMAP" || me->ExtraDataCount() < 3)
432 return QObject::event(e);
434 bool ok = me->ExtraData(0) ==
"OK";
437 for (; i < (
uint) me->ExtraDataCount() && !ours; i++)
438 ours |= me->ExtraData(i) ==
m_token;
440 return QObject::event(e);
442 const QString& pginfokey = me->ExtraData(1);
449 LOG(VB_GENERAL, LOG_ERR,
LOC + pginfokey +
": " + me->ExtraData(2));
454 if (me->ExtraDataCount() < 5)
462 if (!datetime.isValid())
465 LOG(VB_GENERAL, LOG_ERR,
LOC + pginfokey +
"Got invalid date");
467 return QObject::event(e);
470 size_t length = me->ExtraData(4).toULongLong();
471 quint16 checksum16 = me->ExtraData(5).toUInt();
472 QByteArray data = QByteArray::fromBase64(me->ExtraData(6).toLatin1());
473 if ((
size_t) data.size() < length)
476 LOG(VB_GENERAL, LOG_ERR,
LOC +
477 QString(
"Preview size check failed %1 < %2")
478 .arg(data.size()).arg(length));
483#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
484 quint16 calculated = qChecksum(data.constData(), data.size());
486 quint16 calculated = qChecksum(data);
488 if (checksum16 != calculated)
490 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Preview checksum failed");
505 QString remotecachedirname =
506 QString(
"%1/cache/remotecache").arg(
GetConfDir());
513 LOG(VB_GENERAL, LOG_ERR,
LOC +
514 "Remote Preview failed because we could not create a "
515 "remote cache directory");
525 bool ok =
file.open(QIODevice::Unbuffered|QIODevice::WriteOnly);
528 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Failed to open: '%1'")
533 size_t remaining = data.size();
534 uint failure_cnt = 0;
535 while ((remaining > 0) && (failure_cnt < 5))
537 ssize_t written =
file.write(data.data() + offset, remaining);
547 remaining -= written;
550 if (ok && !remaining)
553 struct utimbuf times {};
554 times.actime = times.modtime = dt.toSecsSinceEpoch();
567 const unsigned char *data,
568 uint width,
uint height,
float aspect,
569 int desired_width,
int desired_height,
570 const QString &format)
572 if (!data || !width || !height)
575 const QImage img(data, width, height, QImage::Format_RGB32);
577 float ppw = std::max(desired_width, 0);
578 float pph = std::max(desired_height, 0);
579 bool desired_size_exactly_specified =
true;
580 if ((ppw < 1.0F) && (pph < 1.0F))
584 desired_size_exactly_specified =
false;
587 aspect = (aspect <= 0.0F) ? ((
float) width) / height : aspect;
588 pph = (pph < 1.0F) ? (ppw / aspect) : pph;
589 ppw = (ppw < 1.0F) ? (pph * aspect) : ppw;
591 if (!desired_size_exactly_specified)
593 if (aspect > ppw / pph)
594 pph = (ppw / aspect);
596 ppw = (pph * aspect);
599 ppw = std::max(1.0F, ppw);
600 pph = std::max(1.0F, pph);;
602 QImage small_img = img.scaled((
int) ppw, (
int) pph,
603 Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
605 QTemporaryFile f(QFileInfo(
filename).absoluteFilePath()+
".XXXXXX");
606 f.setAutoRemove(
false);
607 if (f.open() && small_img.save(&f, format.toLocal8Bit().constData()))
613 LOG(VB_GENERAL, LOG_ERR,
"Unable to change permissions on "
614 "preview image. Backends and frontends "
615 "running under different users will be "
616 "unable to access it");
622 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"Saved preview '%0' %1x%2")
623 .arg(
filename).arg((
int) ppw).arg((
int) pph));
639 long long capframe = -1;
644 LOG(VB_GENERAL, LOG_INFO,
"Preview from time spec");
650 LOG(VB_GENERAL, LOG_INFO,
651 QString(
"Preview from bookmark (frame %1)").arg(capframe));
655 if ((captime <= 0s) && (capframe <= 0))
657 std::chrono::seconds startEarly = 0s;
658 std::chrono::seconds programDuration = 0s;
676 if (programDuration > 0s)
678 captime = programDuration / 3;
681 captime += startEarly;
686 LOG(VB_GENERAL, LOG_INFO,
687 QString(
"Preview at calculated offset (%1 seconds)").arg(captime.count()));
694 sz, width, height, aspect);
703 bool ok =
SavePreview(outname, data, width, height, aspect, dw, dh,
710 struct utimbuf times {};
711 times.actime = times.modtime = dt.toSecsSinceEpoch();
712 utime(outname.toLocal8Bit().constData(), ×);
723 const QString &pathname,
const QString &outFileName)
725 QString outname = pathname +
".png";
727 if (outFileName.isEmpty())
730 outname = outFileName;
731 QFileInfo fi(outname);
732 if (outname == fi.fileName())
735 if (pathname.contains(
':'))
737 QUrl uinfo(pathname);
739 dir = uinfo.toString();
743 dir = QFileInfo(pathname).path();
745 outname = dir +
"/" + fi.fileName();
746 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"outfile '%1' -> '%2'")
747 .arg(outFileName, outname));
757 if (tmppathname.startsWith(
"dvd:"))
758 tmppathname = tmppathname.section(
":", 1, 1);
760 if (!QFileInfo(tmppathname).isReadable())
764 QString pathdir = QFileInfo(tmppathname).path();
766 if (!QFileInfo(pathdir).isWritable())
768 LOG(VB_GENERAL, LOG_WARNING,
LOC +
769 QString(
"Output path '%1' is not writeable") .arg(pathdir));
794 std::chrono::seconds seektime,
long long seekframe,
796 int &video_width,
int &video_height,
float &video_aspect)
798 uint8_t *retbuf =
nullptr;
803 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Previewer could not connect to DB.");
811 bool invalid = (!
info.exists() || !
info.isReadable() || (
info.isFile() && (
info.size() < 8LL*1024)));
814 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Previewer file '%1' is not valid.")
821 if (!buffer || !buffer->
IsOpen())
823 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Previewer could not open file: " +
831 ctx->SetRingBuffer(buffer);
832 ctx->SetPlayingInfo(&pginfo);
833 ctx->SetPlayer(player);
837 retbuf = player->GetScreenGrab(seektime, bufferlen,
838 video_width, video_height, video_aspect);
842 retbuf = player->GetScreenGrabAtFrame(
static_cast<uint64_t
>(seekframe),
true,
843 bufferlen, video_width, video_height, video_aspect);
847 auto pos_text = (seektime != std::chrono::seconds::max())
848 ? QString::number(seektime.count()) +
"s"
849 : QString::number(seekframe)+
"f";
852 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"Grabbed preview '%0' %1x%2@%3")
853 .arg(
filename).arg(video_width).arg(video_height).arg(pos_text));
857 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Failed to grab preview '%0' %1x%2@%3")
858 .arg(
filename).arg(video_width).arg(video_height).arg(pos_text));
static bool testDBConnection()
Checks DB connection + login (login info via Mythcontext)
This is a wrapper around QThread that does several additional things.
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
static void usleep(std::chrono::microseconds time)
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
QString GetHostName(void)
bool SendReceiveStringList(QStringList &strlist, bool quickTimeout=false, bool block=true)
Send a message to the backend and wait for a response.
std::enable_if_t< std::chrono::__is_duration< T >::value, T > GetDurSetting(const QString &key, T defaultval=T::zero())
This class is used as a container for messages.
static const Type kMythEventMessage
void addListener(QObject *listener)
Add a listener to the observable.
void removeListener(QObject *listener)
Remove a listener to the observable.
static uint8_t * GetScreenGrab(const ProgramInfo &pginfo, const QString &filename, std::chrono::seconds seektime, long long seekframe, int &bufferlen, int &video_width, int &video_height, float &video_aspect)
Returns a AV_PIX_FMT_RGBA32 buffer containg a frame from the video.
bool RemotePreviewRun(void)
PreviewGenerator(const ProgramInfo *pginfo, QString token, Mode mode=kLocal)
Constructor.
std::chrono::seconds m_captureTime
snapshot time in seconds or frame number (seconds has priority)
bool LocalPreviewRun(void)
static QString CreateAccessibleFilename(const QString &pathname, const QString &outFileName)
static bool SavePreview(const QString &filename, const unsigned char *data, uint width, uint height, float aspect, int desired_width, int desired_height, const QString &format)
bool SaveOutFile(const QByteArray &data, const QDateTime &dt)
void AttachSignals(QObject *obj)
~PreviewGenerator() override
QWaitCondition m_previewWaitCondition
void SetOutputFilename(const QString &fileName)
bool RunReal(void)
This call creates a preview without starting a new thread.
bool event(QEvent *e) override
ProgramInfo m_programInfo
void run(void) override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
Holds information on recordings and videos.
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
uint64_t QueryStartMark(void) const
QString GetBasename(void) const
void SetIgnoreProgStart(bool ignore)
If "ignore" is true QueryProgStart() will return 0, otherwise QueryProgStart() will return the progst...
uint GetRecordingID(void) const
QDateTime GetScheduledEndTime(void) const
The scheduled end time of the program.
void MarkAsInUse(bool inuse, const QString &usedFor="")
Tracks a recording's in use status, to prevent deletion and to allow the storage scheduler to perform...
QDateTime GetRecordingStartTime(void) const
Approximate time the recording started.
QDateTime GetScheduledStartTime(void) const
The scheduled start time of program.
QString MakeUniqueKey(void) const
Creates a unique string that can be used to identify an existing recording.
QString GetPathname(void) const
void ToStringList(QStringList &list) const
Serializes ProgramInfo into a QStringList which can be passed over a socket.
QString FindFile(const QString &filename)
@ GENERIC_EXIT_OK
Exited with no error.
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
QString GetAppBinDir(void)
static QString remotecachedir
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
bool makeFileAccessible(const QString &filename)
Convenience inline random number generator functions.
@ kMSDontBlockInputDevs
avoid blocking LIRC & Joystick Menu
@ kMSProcessEvents
process events while waiting
@ kMSPropagateLogs
add arguments for MythTV log propagation
@ kMSDontDisableDrawing
avoid disabling UI drawing
@ kMSAutoCleanup
automatically delete if backgrounded
@ kFilename
Default UTC, "yyyyMMddhhmmss".
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
uint32_t MythRandom()
generate 32 random bits
const QString kPreviewGeneratorInUseID