12#include <QApplication>
13#include <QCoreApplication>
18#include <QTemporaryFile>
23#include "libmythbase/mythconfig.h"
41#define LOC QString("Preview: ")
78 m_programInfo(*pginfo), m_mode(mode),
79 m_pathname(pginfo->GetPathname()),
80 m_token(
std::move(token))
85 moveToThread(QApplication::instance()->thread());
96 if (fileName.isEmpty())
98 QFileInfo fileinfo = QFileInfo(fileName);
113 QObject::deleteLater();
128 QTime tm = QTime::currentTime();
129 QElapsedTimer te; te.start();
135 LOG(VB_GENERAL, LOG_ERR,
LOC +
136 QString(
"RunReal() file not local: '%1'")
141 LOG(VB_GENERAL, LOG_ERR,
LOC +
142 QString(
"RunReal() Preview of '%1' failed "
143 "because mode was invalid 0x%2")
149 msg = QString(
"Generated on %1 in %2 seconds, starting at %3")
151 .arg(te.elapsed()*0.001)
158 LOG(VB_GENERAL, LOG_WARNING,
LOC +
"Failed to save preview."
159 "\n\t\t\tYou may need to check user and group ownership on"
160 "\n\t\t\tyour frontend and backend for quicker previews.\n"
161 "\n\t\t\tAttempting to regenerate preview on backend.\n");
166 msg = QString(
"Generated remotely in %1 seconds, starting at %2")
167 .arg(te.elapsed()*0.001)
172 msg =
"Remote preview failed";
177 msg =
"Could not access recording";
191 QFileInfo fi(output_fn);
193 dt = fi.lastModified();
196 QString message = (ok) ?
"PREVIEW_SUCCESS" :
"PREVIEW_FAILED";
199 list.push_back(output_fn);
201 list.push_back(dt.isValid()?dt.toUTC().toString(
Qt::ISODate):
"");
212 QTime tm = QTime::currentTime();
213 QElapsedTimer te; te.start();
218 QFileInfo(command).isExecutable());
227 QString(
"Generated remotely in %1 seconds, starting at %2")
228 .arg(te.elapsed()*0.001)
234 LOG(VB_GENERAL, LOG_ERR,
LOC +
235 QString(
"Run() cannot generate preview locally for: '%1'")
237 msg =
"Failed, local preview requested for remote file.";
248 cmdargs <<
"--seconds" << QString::number(
m_captureTime.count());
251 cmdargs <<
"--chanid"
270 uint ret = ms->Wait();
275 LOG(VB_GENERAL, LOG_ERR,
LOC +
276 QString(
"Encountered problems running '%1 %2' - (%3)")
277 .arg(command, cmdargs.join(
" "), QString::number(ret)));
281 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"Preview process returned 0.");
285 QString lpath = QFileInfo(outname).fileName();
286 if (lpath == outname)
289 QString tmpFile = sgroup.
FindFile(lpath);
290 outname = (tmpFile.isEmpty()) ? outname : tmpFile;
293 QFileInfo fi(outname);
294 ok = (fi.exists() && fi.isReadable() && (fi.size() != 0));
297 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"Preview process ran ok.");
298 msg = QString(
"Generated on %1 in %2 seconds, starting at %3")
300 .arg(te.elapsed()*0.001)
305 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Preview process not ok." +
306 QString(
"\n\t\t\tfileinfo(%1)").arg(outname) +
307 QString(
" exists: %1").arg(fi.exists()) +
308 QString(
" readable: %1").arg(fi.isReadable()) +
309 QString(
" size: %1").arg(fi.size()));
310 LOG(VB_GENERAL, LOG_ERR,
LOC +
311 QString(
"Despite command '%1' returning success")
313 msg = QString(
"Failed to read preview image despite "
314 "preview process returning success.");
329 QFileInfo fi(output_fn);
331 dt = fi.lastModified();
334 QString message = (ok) ?
"PREVIEW_SUCCESS" :
"PREVIEW_FAILED";
339 list.push_back(output_fn);
341 list.push_back(dt.isValid()?dt.toUTC().toString(
Qt::ISODate):
"");
358 QStringList strlist(
"QUERY_GENPIXMAP2" );
368 strlist.push_back(
"s");
373 strlist.push_back(
"f");
378 strlist.push_back(
"<EMPTY>");
383 strlist.push_back(fi.fileName());
385 strlist.push_back(QString::number(
m_outSize.width()));
386 strlist.push_back(QString::number(
m_outSize.height()));
392 if (!ok || strlist.empty() || (strlist[0] !=
"OK"))
396 LOG(VB_GENERAL, LOG_ERR,
LOC +
397 "Remote Preview failed due to communications error.");
399 else if (strlist.size() > 1)
401 LOG(VB_GENERAL, LOG_ERR,
LOC +
402 "Remote Preview failed, reason given: " + strlist[1]);
418 LOG(VB_GENERAL, LOG_NOTICE,
LOC +
"RemotePreviewRun() -- no reply..");
428 return QObject::event(e);
432 return QObject::event(e);
433 if (me->Message() !=
"GENERATED_PIXMAP" || me->ExtraDataCount() < 3)
434 return QObject::event(e);
436 bool ok = me->ExtraData(0) ==
"OK";
439 for (; i < (
uint) me->ExtraDataCount() && !ours; i++)
440 ours |= me->ExtraData(i) ==
m_token;
442 return QObject::event(e);
444 const QString& pginfokey = me->ExtraData(1);
451 LOG(VB_GENERAL, LOG_ERR,
LOC + pginfokey +
": " + me->ExtraData(2));
456 if (me->ExtraDataCount() < 5)
464 if (!datetime.isValid())
467 LOG(VB_GENERAL, LOG_ERR,
LOC + pginfokey +
"Got invalid date");
469 return QObject::event(e);
472 size_t length = me->ExtraData(4).toULongLong();
473 quint16 checksum16 = me->ExtraData(5).toUInt();
474 QByteArray data = QByteArray::fromBase64(me->ExtraData(6).toLatin1());
475 if ((
size_t) data.size() < length)
478 LOG(VB_GENERAL, LOG_ERR,
LOC +
479 QString(
"Preview size check failed %1 < %2")
480 .arg(data.size()).arg(length));
485#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
486 quint16 calculated = qChecksum(data.constData(), data.size());
488 quint16 calculated = qChecksum(data);
490 if (checksum16 != calculated)
492 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Preview checksum failed");
507 QString remotecachedirname =
508 QString(
"%1/cache/remotecache").arg(
GetConfDir());
515 LOG(VB_GENERAL, LOG_ERR,
LOC +
516 "Remote Preview failed because we could not create a "
517 "remote cache directory");
527 bool ok =
file.open(QIODevice::Unbuffered|QIODevice::WriteOnly);
530 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Failed to open: '%1'")
535 size_t remaining = data.size();
536 uint failure_cnt = 0;
537 while ((remaining > 0) && (failure_cnt < 5))
539 ssize_t written =
file.write(data.data() + offset, remaining);
549 remaining -= written;
552 if (ok && !remaining)
555 struct utimbuf times {};
556 times.actime = times.modtime = dt.toSecsSinceEpoch();
569 const unsigned char *data,
570 uint width,
uint height,
float aspect,
571 int desired_width,
int desired_height,
572 const QString &format)
574 if (!data || !width || !height)
577 const QImage img(data, width, height, QImage::Format_RGB32);
579 float ppw = std::max(desired_width, 0);
580 float pph = std::max(desired_height, 0);
581 bool desired_size_exactly_specified =
true;
582 if ((ppw < 1.0F) && (pph < 1.0F))
586 desired_size_exactly_specified =
false;
589 aspect = (aspect <= 0.0F) ? ((
float) width) / height : aspect;
590 pph = (pph < 1.0F) ? (ppw / aspect) : pph;
591 ppw = (ppw < 1.0F) ? (pph * aspect) : ppw;
593 if (!desired_size_exactly_specified)
595 if (aspect > ppw / pph)
596 pph = (ppw / aspect);
598 ppw = (pph * aspect);
601 ppw = std::max(1.0F, ppw);
602 pph = std::max(1.0F, pph);;
604 QImage small_img = img.scaled((
int) ppw, (
int) pph,
605 Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
607 QTemporaryFile f(QFileInfo(
filename).absoluteFilePath()+
".XXXXXX");
608 f.setAutoRemove(
false);
609 if (f.open() && small_img.save(&f, format.toLocal8Bit().constData()))
615 LOG(VB_GENERAL, LOG_ERR,
"Unable to change permissions on "
616 "preview image. Backends and frontends "
617 "running under different users will be "
618 "unable to access it");
624 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"Saved preview '%0' %1x%2")
625 .arg(
filename).arg((
int) ppw).arg((
int) pph));
641 long long capframe = -1;
646 LOG(VB_GENERAL, LOG_INFO,
"Preview from time spec");
652 LOG(VB_GENERAL, LOG_INFO,
653 QString(
"Preview from bookmark (frame %1)").arg(capframe));
657 if ((captime <= 0s) && (capframe <= 0))
659 std::chrono::seconds startEarly = 0s;
660 std::chrono::seconds programDuration = 0s;
678 if (programDuration > 0s)
680 captime = programDuration / 3;
683 captime += startEarly;
688 LOG(VB_GENERAL, LOG_INFO,
689 QString(
"Preview at calculated offset (%1 seconds)").arg(captime.count()));
696 sz, width, height, aspect);
705 bool ok =
SavePreview(outname, data, width, height, aspect, dw, dh,
712 struct utimbuf times {};
713 times.actime = times.modtime = dt.toSecsSinceEpoch();
714 utime(outname.toLocal8Bit().constData(), ×);
725 const QString &pathname,
const QString &outFileName)
727 QString outname = pathname +
".png";
729 if (outFileName.isEmpty())
732 outname = outFileName;
733 QFileInfo fi(outname);
734 if (outname == fi.fileName())
737 if (pathname.contains(
':'))
739 QUrl uinfo(pathname);
741 dir = uinfo.toString();
745 dir = QFileInfo(pathname).path();
747 outname = dir +
"/" + fi.fileName();
748 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"outfile '%1' -> '%2'")
749 .arg(outFileName, outname));
759 if (tmppathname.startsWith(
"dvd:"))
760 tmppathname = tmppathname.section(
":", 1, 1);
762 if (!QFileInfo(tmppathname).isReadable())
766 QString pathdir = QFileInfo(tmppathname).path();
768 if (!QFileInfo(pathdir).isWritable())
770 LOG(VB_GENERAL, LOG_WARNING,
LOC +
771 QString(
"Output path '%1' is not writeable") .arg(pathdir));
796 std::chrono::seconds seektime,
long long seekframe,
798 int &video_width,
int &video_height,
float &video_aspect)
800 uint8_t *retbuf =
nullptr;
805 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Previewer could not connect to DB.");
813 bool invalid = (!
info.exists() || !
info.isReadable() || (
info.isFile() && (
info.size() < 8LL*1024)));
816 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Previewer file '%1' is not valid.")
823 if (!buffer || !buffer->
IsOpen())
825 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Previewer could not open file: " +
833 ctx->SetRingBuffer(buffer);
834 ctx->SetPlayingInfo(&pginfo);
835 ctx->SetPlayer(player);
839 retbuf = player->GetScreenGrab(seektime, bufferlen,
840 video_width, video_height, video_aspect);
844 retbuf = player->GetScreenGrabAtFrame(
static_cast<uint64_t
>(seekframe),
true,
845 bufferlen, video_width, video_height, video_aspect);
849 auto pos_text = (seektime != std::chrono::seconds::max())
850 ? QString::number(seektime.count()) +
"s"
851 : QString::number(seekframe)+
"f";
854 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"Grabbed preview '%0' %1x%2@%3")
855 .arg(
filename).arg(video_width).arg(video_height).arg(pos_text));
859 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Failed to grab preview '%0' %1x%2@%3")
860 .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