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