MythTV  master
previewgenerator.cpp
Go to the documentation of this file.
1 // C headers
2 #include <cmath>
3 
4 // POSIX headers
5 #include <sys/types.h> // for utime
6 #include <sys/time.h>
7 #include <fcntl.h>
8 #include <utime.h> // for utime
9 
10 // Qt headers
11 #include <QCoreApplication>
12 #include <QTemporaryFile>
13 #include <QFileInfo>
14 #include <QMetaType>
15 #include <QImage>
16 #include <QDir>
17 #include <QUrl>
18 #include <QApplication>
19 
20 // MythTV headers
21 #include "mythconfig.h"
22 
23 #include "ringbuffer.h"
24 #include "mythplayer.h"
25 #include "previewgenerator.h"
26 #include "tv_rec.h"
27 #include "mythsocket.h"
28 #include "remotefile.h"
29 #include "storagegroup.h"
30 #include "mythdate.h"
31 #include "playercontext.h"
32 #include "mythdirs.h"
33 #include "remoteutil.h"
34 #include "mythsystemlegacy.h"
35 #include "exitcodes.h"
36 #include "mythlogging.h"
37 #include "mythmiscutil.h"
38 
39 #define LOC QString("Preview: ")
40 
73  const QString &token,
75  : MThread("PreviewGenerator"),
76  m_programInfo(*pginfo), m_mode(mode), m_listener(nullptr),
77  m_pathname(pginfo->GetPathname()),
78  m_timeInSeconds(true), m_captureTime(-1),
79  m_outSize(0,0), m_outFormat("PNG"),
80  m_token(token), m_gotReply(false), m_pixmapOk(false)
81 {
82  // Qt requires that a receiver have the same thread affinity as the QThread
83  // sending the event, which is used to dispatch MythEvents sent by
84  // gCoreContext->dispatchNow(me)
85  moveToThread(QApplication::instance()->thread());
86 }
87 
89 {
90  TeardownAll();
91  wait();
92 }
93 
94 void PreviewGenerator::SetOutputFilename(const QString &fileName)
95 {
96  if (fileName.isEmpty())
97  return;
98  QFileInfo fileinfo = QFileInfo(fileName);
99  m_outFileName = fileName;
100  m_outFormat = fileinfo.suffix().toUpper();
101 }
102 
104 {
105  QMutexLocker locker(&m_previewLock);
106  m_previewWaitCondition.wakeAll();
107  m_listener = nullptr;
108 }
109 
111 {
112  TeardownAll();
113  QObject::deleteLater();
114 }
115 
117 {
118  QMutexLocker locker(&m_previewLock);
119  m_listener = obj;
120 }
121 
126 {
127  QString msg;
128  QTime tm = QTime::currentTime();
129  bool ok = false;
130  bool is_local = IsLocal();
131 
132  if (!is_local && !!(m_mode & kRemote))
133  {
134  LOG(VB_GENERAL, LOG_ERR, LOC +
135  QString("RunReal() file not local: '%1'")
136  .arg(m_pathname));
137  }
138  else if (!(m_mode & kLocal) && !(m_mode & kRemote))
139  {
140  LOG(VB_GENERAL, LOG_ERR, LOC +
141  QString("RunReal() Preview of '%1' failed "
142  "because mode was invalid 0x%2")
143  .arg(m_pathname).arg((int)m_mode,0,16));
144  }
145  else if (!!(m_mode & kLocal) && LocalPreviewRun())
146  {
147  ok = true;
148  msg = QString("Generated on %1 in %2 seconds, starting at %3")
149  .arg(gCoreContext->GetHostName())
150  .arg(tm.elapsed()*0.001)
151  .arg(tm.toString(Qt::ISODate));
152  }
153  else if (!!(m_mode & kRemote))
154  {
155  if (is_local && (m_mode & kLocal))
156  {
157  LOG(VB_GENERAL, LOG_WARNING, LOC + "Failed to save preview."
158  "\n\t\t\tYou may need to check user and group ownership on"
159  "\n\t\t\tyour frontend and backend for quicker previews.\n"
160  "\n\t\t\tAttempting to regenerate preview on backend.\n");
161  }
162  ok = RemotePreviewRun();
163  if (ok)
164  {
165  msg = QString("Generated remotely in %1 seconds, starting at %2")
166  .arg(tm.elapsed()*0.001)
167  .arg(tm.toString(Qt::ISODate));
168  }
169  else
170  {
171  msg = "Remote preview failed";
172  }
173  }
174  else
175  {
176  msg = "Could not access recording";
177  }
178 
179  QMutexLocker locker(&m_previewLock);
180  if (m_listener)
181  {
182  // keep in sync with default filename in
183  // PreviewGeneratorQueue::GeneratePreviewImage
184  QString output_fn = m_outFileName.isEmpty() ?
186 
187  QDateTime dt;
188  if (ok)
189  {
190  QFileInfo fi(output_fn);
191  if (fi.exists())
192  dt = fi.lastModified();
193  }
194 
195  QString message = (ok) ? "PREVIEW_SUCCESS" : "PREVIEW_FAILED";
196  QStringList list;
197  list.push_back(QString::number(m_programInfo.GetRecordingID()));
198  list.push_back(output_fn);
199  list.push_back(msg);
200  list.push_back(dt.isValid()?dt.toUTC().toString(Qt::ISODate):"");
201  list.push_back(m_token);
202  QCoreApplication::postEvent(m_listener, new MythEvent(message, list));
203  }
204 
205  return ok;
206 }
207 
209 {
210  QString msg;
211  QDateTime dtm = MythDate::current();
212  QTime tm = QTime::currentTime();
213  bool ok = false;
214  QString command = GetAppBinDir() + "mythpreviewgen";
215  bool local_ok = ((IsLocal() || !!(m_mode & kForceLocal)) &&
216  (!!(m_mode & kLocal)) &&
217  QFileInfo(command).isExecutable());
218  if (!local_ok)
219  {
220  if (!!(m_mode & kRemote))
221  {
222  ok = RemotePreviewRun();
223  if (ok)
224  {
225  msg =
226  QString("Generated remotely in %1 seconds, starting at %2")
227  .arg(tm.elapsed()*0.001)
228  .arg(tm.toString(Qt::ISODate));
229  }
230  }
231  else
232  {
233  LOG(VB_GENERAL, LOG_ERR, LOC +
234  QString("Run() cannot generate preview locally for: '%1'")
235  .arg(m_pathname));
236  msg = "Failed, local preview requested for remote file.";
237  }
238  }
239  else
240  {
241  // This is where we fork and run mythpreviewgen to actually make preview
242  QStringList cmdargs;
243 
244  cmdargs << "--size"
245  << QString("%1x%2").arg(m_outSize.width()).arg(m_outSize.height());
246  if (m_captureTime >= 0)
247  {
248  if (m_timeInSeconds)
249  cmdargs << "--seconds";
250  else
251  cmdargs << "--frame";
252  cmdargs << QString::number(m_captureTime);
253  }
254  cmdargs << "--chanid"
255  << QString::number(m_programInfo.GetChanID())
256  << "--starttime"
258 
259  if (!m_outFileName.isEmpty())
260  cmdargs << "--outfile" << m_outFileName;
261 
262  // Timeout in 30s
263  MythSystemLegacy *ms = new MythSystemLegacy(command, cmdargs,
269  ms->SetNice(10);
270  ms->SetIOPrio(7);
271 
272  ms->Run(30);
273  uint ret = ms->Wait();
274  delete ms;
275 
276  if (ret != GENERIC_EXIT_OK)
277  {
278  LOG(VB_GENERAL, LOG_ERR, LOC +
279  QString("Encountered problems running '%1 %2' - (%3)")
280  .arg(command).arg(cmdargs.join(" ")).arg(ret));
281  }
282  else
283  {
284  LOG(VB_PLAYBACK, LOG_INFO, LOC + "Preview process returned 0.");
285  QString outname = (!m_outFileName.isEmpty()) ?
286  m_outFileName : (m_pathname + ".png");
287 
288  QString lpath = QFileInfo(outname).fileName();
289  if (lpath == outname)
290  {
291  StorageGroup sgroup;
292  QString tmpFile = sgroup.FindFile(lpath);
293  outname = (tmpFile.isEmpty()) ? outname : tmpFile;
294  }
295 
296  QFileInfo fi(outname);
297  ok = (fi.exists() && fi.isReadable() && fi.size());
298  if (ok)
299  {
300  LOG(VB_PLAYBACK, LOG_INFO, LOC + "Preview process ran ok.");
301  msg = QString("Generated on %1 in %2 seconds, starting at %3")
302  .arg(gCoreContext->GetHostName())
303  .arg(tm.elapsed()*0.001)
304  .arg(tm.toString(Qt::ISODate));
305  }
306  else
307  {
308  LOG(VB_GENERAL, LOG_ERR, LOC + "Preview process not ok." +
309  QString("\n\t\t\tfileinfo(%1)").arg(outname) +
310  QString(" exists: %1").arg(fi.exists()) +
311  QString(" readable: %1").arg(fi.isReadable()) +
312  QString(" size: %1").arg(fi.size()));
313  LOG(VB_GENERAL, LOG_ERR, LOC +
314  QString("Despite command '%1' returning success")
315  .arg(command));
316  msg = QString("Failed to read preview image despite "
317  "preview process returning success.");
318  }
319  }
320  }
321 
322  QMutexLocker locker(&m_previewLock);
323 
324  // Backdate file to start of preview time in case a bookmark was made
325  // while we were generating the preview.
326  QString output_fn = m_outFileName.isEmpty() ?
328 
329  QDateTime dt;
330  if (ok)
331  {
332  QFileInfo fi(output_fn);
333  if (fi.exists())
334  dt = fi.lastModified();
335  }
336 
337  QString message = (ok) ? "PREVIEW_SUCCESS" : "PREVIEW_FAILED";
338  if (m_listener)
339  {
340  QStringList list;
341  list.push_back(QString::number(m_programInfo.GetRecordingID()));
342  list.push_back(output_fn);
343  list.push_back(msg);
344  list.push_back(dt.isValid()?dt.toUTC().toString(Qt::ISODate):"");
345  list.push_back(m_token);
346  QCoreApplication::postEvent(m_listener, new MythEvent(message, list));
347  }
348 
349  return ok;
350 }
351 
353 {
354  RunProlog();
355  Run();
356  RunEpilog();
357 }
358 
360 {
361  QStringList strlist( "QUERY_GENPIXMAP2" );
362  if (m_token.isEmpty())
363  {
364  m_token = QString("%1:%2")
365  .arg(m_programInfo.MakeUniqueKey()).arg(random());
366  }
367  strlist.push_back(m_token);
368  m_programInfo.ToStringList(strlist);
369  strlist.push_back(m_timeInSeconds ? "s" : "f");
370  strlist.push_back(QString::number(m_captureTime));
371  if (m_outFileName.isEmpty())
372  {
373  strlist.push_back("<EMPTY>");
374  }
375  else
376  {
377  QFileInfo fi(m_outFileName);
378  strlist.push_back(fi.fileName());
379  }
380  strlist.push_back(QString::number(m_outSize.width()));
381  strlist.push_back(QString::number(m_outSize.height()));
382 
383  gCoreContext->addListener(this);
384  m_pixmapOk = false;
385 
386  bool ok = gCoreContext->SendReceiveStringList(strlist);
387  if (!ok || strlist.empty() || (strlist[0] != "OK"))
388  {
389  if (!ok)
390  {
391  LOG(VB_GENERAL, LOG_ERR, LOC +
392  "Remote Preview failed due to communications error.");
393  }
394  else if (strlist.size() > 1)
395  {
396  LOG(VB_GENERAL, LOG_ERR, LOC +
397  "Remote Preview failed, reason given: " + strlist[1]);
398  }
399 
401 
402  return false;
403  }
404 
405  QMutexLocker locker(&m_previewLock);
406 
407  // wait up to 35 seconds for the preview to complete
408  // The backend waits 30s for creation
409  if (!m_gotReply)
410  m_previewWaitCondition.wait(&m_previewLock, 35 * 1000);
411 
412  if (!m_gotReply)
413  LOG(VB_GENERAL, LOG_NOTICE, LOC + "RemotePreviewRun() -- no reply..");
414 
416 
417  return m_pixmapOk;
418 }
419 
420 bool PreviewGenerator::event(QEvent *e)
421 {
422  if (e->type() != MythEvent::MythEventMessage)
423  return QObject::event(e);
424 
425  MythEvent *me = static_cast<MythEvent*>(e);
426  if (me->Message() != "GENERATED_PIXMAP" || me->ExtraDataCount() < 3)
427  return QObject::event(e);
428 
429  bool ok = me->ExtraData(0) == "OK";
430  bool ours = false;
431  uint i = ok ? 4 : 3;
432  for (; i < (uint) me->ExtraDataCount() && !ours; i++)
433  ours |= me->ExtraData(i) == m_token;
434  if (!ours)
435  return false;
436 
437  QString pginfokey = me->ExtraData(1);
438 
439  QMutexLocker locker(&m_previewLock);
440  m_gotReply = true;
441  m_pixmapOk = ok;
442  if (!ok)
443  {
444  LOG(VB_GENERAL, LOG_ERR, LOC + pginfokey + ": " + me->ExtraData(2));
445  m_previewWaitCondition.wakeAll();
446  return true;
447  }
448 
449  if (me->ExtraDataCount() < 5)
450  {
451  m_pixmapOk = false;
452  m_previewWaitCondition.wakeAll();
453  return true; // could only happen with very broken client...
454  }
455 
456  QDateTime datetime = MythDate::fromString(me->ExtraData(3));
457  if (!datetime.isValid())
458  {
459  m_pixmapOk = false;
460  LOG(VB_GENERAL, LOG_ERR, LOC + pginfokey + "Got invalid date");
461  m_previewWaitCondition.wakeAll();
462  return false;
463  }
464 
465  size_t length = me->ExtraData(4).toULongLong();
466  quint16 checksum16 = me->ExtraData(5).toUInt();
467  QByteArray data = QByteArray::fromBase64(me->ExtraData(6).toLatin1());
468  if ((size_t) data.size() < length)
469  { // (note data.size() may be up to 3
470  // bytes longer after decoding
471  LOG(VB_GENERAL, LOG_ERR, LOC +
472  QString("Preview size check failed %1 < %2")
473  .arg(data.size()).arg(length));
474  data.clear();
475  }
476  data.resize(length);
477 
478  if (checksum16 != qChecksum(data.constData(), data.size()))
479  {
480  LOG(VB_GENERAL, LOG_ERR, LOC + "Preview checksum failed");
481  data.clear();
482  }
483 
484  m_pixmapOk = (data.isEmpty()) ? false : SaveOutFile(data, datetime);
485 
486  m_previewWaitCondition.wakeAll();
487 
488  return true;
489 }
490 
491 bool PreviewGenerator::SaveOutFile(const QByteArray &data, const QDateTime &dt)
492 {
493  if (m_outFileName.isEmpty())
494  {
495  QString remotecachedirname =
496  QString("%1/cache/remotecache").arg(GetConfDir());
497  QDir remotecachedir(remotecachedirname);
498 
499  if (!remotecachedir.exists())
500  {
501  if (!remotecachedir.mkdir(remotecachedirname))
502  {
503  LOG(VB_GENERAL, LOG_ERR, LOC +
504  "Remote Preview failed because we could not create a "
505  "remote cache directory");
506  return false;
507  }
508  }
509 
510  QString filename = m_programInfo.GetBasename() + ".png";
511  SetOutputFilename(QString("%1/%2").arg(remotecachedirname).arg(filename));
512  }
513 
514  QFile file(m_outFileName);
515  bool ok = file.open(QIODevice::Unbuffered|QIODevice::WriteOnly);
516  if (!ok)
517  {
518  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to open: '%1'")
519  .arg(m_outFileName));
520  }
521 
522  off_t offset = 0;
523  size_t remaining = data.size();
524  uint failure_cnt = 0;
525  while ((remaining > 0) && (failure_cnt < 5))
526  {
527  ssize_t written = file.write(data.data() + offset, remaining);
528  if (written < 0)
529  {
530  failure_cnt++;
531  usleep(50000);
532  continue;
533  }
534 
535  failure_cnt = 0;
536  offset += written;
537  remaining -= written;
538  }
539 
540  if (ok && !remaining)
541  {
542  file.close();
543  struct utimbuf times;
544 #if QT_VERSION < QT_VERSION_CHECK(5,8,0)
545  times.actime = times.modtime = dt.toTime_t();
546 #else
547  times.actime = times.modtime = dt.toSecsSinceEpoch();
548 #endif
549  utime(m_outFileName.toLocal8Bit().constData(), &times);
550  LOG(VB_FILE, LOG_INFO, LOC + QString("Saved: '%1'").arg(m_outFileName));
551  }
552  else
553  {
554  file.remove();
555  }
556 
557  return ok;
558 }
559 
560 bool PreviewGenerator::SavePreview(const QString &filename,
561  const unsigned char *data,
562  uint width, uint height, float aspect,
563  int desired_width, int desired_height,
564  const QString &format)
565 {
566  if (!data || !width || !height)
567  return false;
568 
569  const QImage img((unsigned char*) data,
570  width, height, QImage::Format_RGB32);
571 
572  float ppw = max(desired_width, 0);
573  float pph = max(desired_height, 0);
574  bool desired_size_exactly_specified = true;
575  if ((ppw < 1.0f) && (pph < 1.0f))
576  {
577  ppw = img.width();
578  pph = img.height();
579  desired_size_exactly_specified = false;
580  }
581 
582  aspect = (aspect <= 0.0f) ? ((float) width) / height : aspect;
583  pph = (pph < 1.0f) ? (ppw / aspect) : pph;
584  ppw = (ppw < 1.0f) ? (pph * aspect) : ppw;
585 
586  if (!desired_size_exactly_specified)
587  {
588  if (aspect > ppw / pph)
589  pph = (ppw / aspect);
590  else
591  ppw = (pph * aspect);
592  }
593 
594  ppw = max(1.0f, ppw);
595  pph = max(1.0f, pph);;
596 
597  QImage small_img = img.scaled((int) ppw, (int) pph,
598  Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
599 
600  QTemporaryFile f(QFileInfo(filename).absoluteFilePath()+".XXXXXX");
601  f.setAutoRemove(false);
602  if (f.open() && small_img.save(&f, format.toLocal8Bit().constData()))
603  {
604  // Let anybody update it
605  bool ret = makeFileAccessible(f.fileName().toLocal8Bit().constData());
606  if (!ret)
607  {
608  LOG(VB_GENERAL, LOG_ERR, "Unable to change permissions on "
609  "preview image. Backends and frontends "
610  "running under different users will be "
611  "unable to access it");
612  }
613  QFile of(filename);
614  of.remove();
615  if (f.rename(filename))
616  {
617  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Saved preview '%0' %1x%2")
618  .arg(filename).arg((int) ppw).arg((int) pph));
619  return true;
620  }
621  f.remove();
622  }
623 
624  return false;
625 }
626 
628 {
632 
633  float aspect = 0;
634  int width, height, sz;
635  long long captime = m_captureTime;
636 
637  QDateTime dt = MythDate::current();
638 
639  if (captime > 0)
640  LOG(VB_GENERAL, LOG_INFO, "Preview from time spec");
641 
642  if (captime < 0)
643  {
644  captime = m_programInfo.QueryBookmark();
645  if (captime > 0)
646  {
647  m_timeInSeconds = false;
648  LOG(VB_GENERAL, LOG_INFO,
649  QString("Preview from bookmark (frame %1)").arg(captime));
650  }
651  else
652  captime = -1;
653  }
654 
655  if (captime <= 0)
656  {
657  m_timeInSeconds = true;
658  int startEarly = 0;
659  int programDuration = 0;
660  int preroll = gCoreContext->GetNumSetting("RecordPreRoll", 0);
661  if (m_programInfo.GetScheduledStartTime().isValid() &&
662  m_programInfo.GetScheduledEndTime().isValid() &&
665  {
666  programDuration = m_programInfo.GetScheduledStartTime()
668  }
669  if (m_programInfo.GetRecordingStartTime().isValid() &&
670  m_programInfo.GetScheduledStartTime().isValid() &&
673  {
674  startEarly = m_programInfo.GetRecordingStartTime()
676  }
677  if (programDuration > 0)
678  {
679  captime = startEarly + (programDuration / 3);
680  }
681  if (captime < 0)
682  captime = 600;
683  captime += preroll;
684  LOG(VB_GENERAL, LOG_INFO,
685  QString("Preview at calculated offset (%1 seconds)").arg(captime));
686  }
687 
688  width = height = sz = 0;
689  unsigned char *data = (unsigned char*)
691  captime, m_timeInSeconds,
692  sz, width, height, aspect);
693 
695 
696  QString format = (m_outFormat.isEmpty()) ? "PNG" : m_outFormat;
697 
698  int dw = (m_outSize.width() < 0) ? width : m_outSize.width();
699  int dh = (m_outSize.height() < 0) ? height : m_outSize.height();
700 
701  bool ok = SavePreview(outname, data, width, height, aspect, dw, dh,
702  format);
703 
704  if (ok)
705  {
706  // Backdate file to start of preview time in case a bookmark was made
707  // while we were generating the preview.
708  struct utimbuf times;
709 #if QT_VERSION < QT_VERSION_CHECK(5,8,0)
710  times.actime = times.modtime = dt.toTime_t();
711 #else
712  times.actime = times.modtime = dt.toSecsSinceEpoch();
713 #endif
714  utime(outname.toLocal8Bit().constData(), &times);
715  }
716 
717  delete[] data;
718 
720 
721  return ok;
722 }
723 
725  const QString &pathname, const QString &outFileName)
726 {
727  QString outname = pathname + ".png";
728 
729  if (outFileName.isEmpty())
730  return outname;
731 
732  outname = outFileName;
733  QFileInfo fi(outname);
734  if (outname == fi.fileName())
735  {
736  QString dir;
737  if (pathname.contains(':'))
738  {
739  QUrl uinfo(pathname);
740  uinfo.setPath("");
741  dir = uinfo.toString();
742  }
743  else
744  {
745  dir = QFileInfo(pathname).path();
746  }
747  outname = dir + "/" + fi.fileName();
748  LOG(VB_FILE, LOG_INFO, LOC + QString("outfile '%1' -> '%2'")
749  .arg(outFileName).arg(outname));
750  }
751 
752  return outname;
753 }
754 
756 {
757  QString tmppathname = m_pathname;
758 
759  if (tmppathname.startsWith("dvd:"))
760  tmppathname = tmppathname.section(":", 1, 1);
761 
762  if (!QFileInfo(tmppathname).isReadable())
763  return false;
764 
765  tmppathname = m_outFileName.isEmpty() ? tmppathname : m_outFileName;
766  QString pathdir = QFileInfo(tmppathname).path();
767 
768  if (!QFileInfo(pathdir).isWritable())
769  {
770  LOG(VB_GENERAL, LOG_WARNING, LOC +
771  QString("Output path '%1' is not writeable") .arg(pathdir));
772  return false;
773  }
774 
775  return true;
776 }
777 
794  const ProgramInfo &pginfo, const QString &filename,
795  long long seektime, bool time_in_secs,
796  int &bufferlen,
797  int &video_width, int &video_height, float &video_aspect)
798 {
799  (void) pginfo;
800  (void) filename;
801  (void) seektime;
802  (void) time_in_secs;
803  (void) bufferlen;
804  (void) video_width;
805  (void) video_height;
806  char *retbuf = nullptr;
807  bufferlen = 0;
808 
810  {
811  LOG(VB_GENERAL, LOG_ERR, LOC + "Previewer could not connect to DB.");
812  return nullptr;
813  }
814 
815  // pre-test local files for existence and size. 500 ms speed-up...
816  if (filename.startsWith("/"))
817  {
818  QFileInfo info(filename);
819  bool invalid = (!info.exists() || !info.isReadable() ||
820  (info.isFile() && (info.size() < 8*1024)));
821  if (invalid)
822  {
823  LOG(VB_GENERAL, LOG_ERR, LOC + "Previewer file " +
824  QString("'%1'").arg(filename) + " is not valid.");
825  return nullptr;
826  }
827  }
828 
829  RingBuffer *rbuf = RingBuffer::Create(filename, false, false, 0);
830  if (!rbuf || !rbuf->IsOpen())
831  {
832  LOG(VB_GENERAL, LOG_ERR, LOC + "Previewer could not open file: " +
833  QString("'%1'").arg(filename));
834  delete rbuf;
835  return nullptr;
836  }
837 
839  ctx->SetRingBuffer(rbuf);
840  ctx->SetPlayingInfo(&pginfo);
842  ctx->player->SetPlayerInfo(nullptr, nullptr, ctx);
843 
844  if (time_in_secs)
845  retbuf = ctx->player->GetScreenGrab(seektime, bufferlen,
846  video_width, video_height, video_aspect);
847  else
848  retbuf = ctx->player->GetScreenGrabAtFrame(
849  seektime, true, bufferlen,
850  video_width, video_height, video_aspect);
851 
852  delete ctx;
853 
854  if (retbuf)
855  {
856  LOG(VB_GENERAL, LOG_INFO, LOC +
857  QString("Grabbed preview '%0' %1x%2@%3%4")
858  .arg(filename).arg(video_width).arg(video_height)
859  .arg(seektime).arg((time_in_secs) ? "s" : "f"));
860  }
861  else
862  {
863  LOG(VB_GENERAL, LOG_ERR, LOC +
864  QString("Failed to grab preview '%0' %1x%2@%3%4")
865  .arg(filename).arg(video_width).arg(video_height)
866  .arg(seektime).arg((time_in_secs) ? "s" : "f"));
867  }
868 
869  return retbuf;
870 }
871 
872 /* vim: set expandtab tabstop=4 shiftwidth=4: */
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:216
void Run(time_t timeout=0)
Runs a command inside the /bin/sh shell. Returns immediately.
avoid disabling UI drawing
Definition: mythsystem.h:35
bool IsLocal(void) const
virtual ~PreviewGenerator()
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:46
automatically delete if backgrounded
Definition: mythsystem.h:43
void SetAllowLastPlayPos(bool allow)
If "ignore" is true QueryLastPlayPos() will return 0, otherwise QueryLastPlayPos() will return the la...
Definition: programinfo.h:560
PlayerFlags
Definition: mythplayer.h:88
VERBOSE_PREAMBLE Most true
Definition: verbosedefs.h:91
#define GENERIC_EXIT_OK
Exited with no error.
Definition: exitcodes.h:10
bool RemotePreviewRun(void)
void AttachSignals(QObject *)
static Type MythEventMessage
Definition: mythevent.h:65
void removeListener(QObject *listener)
Remove a listener to the observable.
QWaitCondition m_previewWaitCondition
void SetOutputFilename(const QString &)
void MarkAsInUse(bool inuse, QString usedFor="")
Tracks a recording's in use status, to prevent deletion and to allow the storage scheduler to perform...
void ToStringList(QStringList &list) const
Serializes ProgramInfo into a QStringList which can be passed over a socket.
bool wait(unsigned long time=ULONG_MAX)
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:312
static QString remotecachedir
Definition: mythdirs.cpp:26
#define LOC
bool m_timeInSeconds
tells us whether to use time as seconds or frame number
static char * GetScreenGrab(const ProgramInfo &pginfo, const QString &filename, long long seektime, bool time_in_secs, int &bufferlen, int &video_width, int &video_height, float &video_aspect)
Returns a AV_PIX_FMT_RGBA32 buffer containg a frame from the video.
Default UTC, "yyyyMMddhhmmss".
Definition: mythdate.h:15
void SetPlayer(MythPlayer *new_player)
void SetRingBuffer(RingBuffer *buf)
unsigned int uint
Definition: compat.h:140
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
static RingBuffer * Create(const QString &xfilename, bool write, bool usereadahead=true, int timeout_ms=kDefaultOpenTimeout, bool stream_only=false)
Creates a RingBuffer instance.
Definition: ringbuffer.cpp:112
void addListener(QObject *listener)
Add a listener to the observable.
const char * kPreviewGeneratorInUseID
ProgramInfo m_programInfo
PreviewGenerator(const ProgramInfo *pginfo, const QString &token, Mode mode=kLocal)
Constructor.
virtual char * GetScreenGrab(int secondsin, int &bufflen, int &vw, int &vh, float &ar)
Returns a one RGB frame grab from a video.
MythPlayer * player
#define off_t
process events while waiting
Definition: mythsystem.h:37
add arguments for MythTV log propagation
Definition: mythsystem.h:50
QString GetConfDir(void)
Definition: mythdirs.cpp:224
QDateTime GetScheduledStartTime(void) const
The scheduled start time of program.
Definition: programinfo.h:384
static QString CreateAccessibleFilename(const QString &pathname, const QString &outFileName)
QString MakeUniqueKey(void) const
Creates a unique string that can be used to identify an existing recording.
Definition: programinfo.h:333
Holds information on recordings and videos.
Definition: programinfo.h:66
void SetPlayingInfo(const ProgramInfo *info)
assign programinfo to the context
bool SendReceiveStringList(QStringList &strlist, bool quickTimeout=false, bool block=true)
Send a message to the backend and wait for a response.
This class is used as a container for messages.
Definition: mythevent.h:15
VERBOSE_PREAMBLE false
Definition: verbosedefs.h:85
bool SetNice(int nice)
QString GetBasename(void) const
Definition: programinfo.h:338
QString GetAppBinDir(void)
Definition: mythdirs.cpp:221
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
bool makeFileAccessible(QString filename)
Makes a file accessible to all frontends/backends.
static bool testDBConnection()
Checks DB connection + login (login info via Mythcontext)
Definition: mythdbcon.cpp:863
uint GetRecordingID(void) const
Definition: programinfo.h:440
virtual bool IsOpen(void) const =0
Returns true if open for either reading or writing.
bool SetIOPrio(int prio)
QDateTime GetScheduledEndTime(void) const
The scheduled end time of the program.
Definition: programinfo.h:391
uint64_t QueryBookmark(void) const
Gets any bookmark position in database, unless the ignore bookmark flag is set.
uint Wait(time_t timeout=0)
void SetPlayerInfo(TV *tv, QWidget *widget, PlayerContext *ctx)
int GetNumSetting(const QString &key, int defaultval=0)
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
bool event(QEvent *e) override
const QString & ExtraData(int idx=0) const
Definition: mythevent.h:58
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)
QString FindFile(const QString &filename)
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
Definition: programinfo.h:366
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:203
void run(void) override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
QDateTime GetRecordingStartTime(void) const
Approximate time the recording started.
Definition: programinfo.h:398
long long m_captureTime
snapshot time in seconds or frame number, depending on timeInSeconds
bool LocalPreviewRun(void)
int ExtraDataCount() const
Definition: mythevent.h:60
Implements a file/stream reader/writer.
static long int random(void)
Definition: compat.h:147
static void usleep(unsigned long time)
Definition: mthread.cpp:349
void SetIgnoreProgStart(bool ignore)
If "ignore" is true QueryProgStart() will return 0, otherwise QueryProgStart() will return the progst...
Definition: programinfo.h:552
bool RunReal(void)
This call creates a preview without starting a new thread.
const QString & Message() const
Definition: mythevent.h:57
QString GetHostName(void)
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
Definition: mythdate.cpp:30
QString GetPathname(void) const
Definition: programinfo.h:337
virtual char * GetScreenGrabAtFrame(uint64_t frameNum, bool absolute, int &bufflen, int &vw, int &vh, float &ar)
Returns a one RGB frame grab from a video.
Default UTC.
Definition: mythdate.h:14
avoid blocking LIRC & Joystick Menu
Definition: mythsystem.h:34
bool SaveOutFile(const QByteArray &data, const QDateTime &dt)