MythTV  master
previewgenerator.cpp
Go to the documentation of this file.
1 // C headers
2 #include <cmath>
3 #include <utility>
4 
5 // POSIX headers
6 #include <sys/types.h> // for utime
7 #include <sys/time.h>
8 #include <fcntl.h>
9 #include <utime.h> // for utime
10 
11 // Qt headers
12 #include <QApplication>
13 #include <QCoreApplication>
14 #include <QDir>
15 #include <QFileInfo>
16 #include <QImage>
17 #include <QMetaType>
18 #include <QTemporaryFile>
19 #include <QUrl>
20 
21 // MythTV headers
22 #include "libmythbase/exitcodes.h"
23 #include "libmythbase/mythconfig.h"
24 #include "libmythbase/mythdate.h"
25 #include "libmythbase/mythdirs.h"
27 #include "libmythbase/mythrandom.h"
28 #include "libmythbase/mythsocket.h"
30 #include "libmythbase/remotefile.h"
31 #include "libmythbase/remoteutil.h"
33 
34 #include "io/mythmediabuffer.h"
35 #include "mythpreviewplayer.h"
36 #include "playercontext.h"
37 #include "previewgenerator.h"
38 #include "tv_rec.h"
39 
40 #define LOC QString("Preview: ")
41 
74  QString token,
76  : MThread("PreviewGenerator"),
77  m_programInfo(*pginfo), m_mode(mode),
78  m_pathname(pginfo->GetPathname()),
79  m_token(std::move(token))
80 {
81  // Qt requires that a receiver have the same thread affinity as the QThread
82  // sending the event, which is used to dispatch MythEvents sent by
83  // gCoreContext->dispatchNow(me)
84  moveToThread(QApplication::instance()->thread());
85 }
86 
88 {
89  TeardownAll();
90  wait();
91 }
92 
93 void PreviewGenerator::SetOutputFilename(const QString &fileName)
94 {
95  if (fileName.isEmpty())
96  return;
97  QFileInfo fileinfo = QFileInfo(fileName);
98  m_outFileName = fileName;
99  m_outFormat = fileinfo.suffix().toUpper();
100 }
101 
103 {
104  QMutexLocker locker(&m_previewLock);
105  m_previewWaitCondition.wakeAll();
106  m_listener = nullptr;
107 }
108 
110 {
111  TeardownAll();
112  QObject::deleteLater();
113 }
114 
116 {
117  QMutexLocker locker(&m_previewLock);
118  m_listener = obj;
119 }
120 
125 {
126  QString msg;
127  QTime tm = QTime::currentTime();
128  QElapsedTimer te; te.start();
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(te.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(te.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  QTime tm = QTime::currentTime();
212  QElapsedTimer te; te.start();
213  bool ok = false;
214  QString command = GetAppBinDir() + "mythpreviewgen";
215  bool local_ok = ((IsLocal() || ((m_mode & kForceLocal) != 0)) &&
216  ((m_mode & kLocal) != 0) &&
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(te.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 >= 0s)
247  cmdargs << "--seconds" << QString::number(m_captureTime.count());
248  else if (m_captureFrame >= 0)
249  cmdargs << "--frame" << QString::number(m_captureFrame);
250  cmdargs << "--chanid"
251  << QString::number(m_programInfo.GetChanID())
252  << "--starttime"
254 
255  if (!m_outFileName.isEmpty())
256  cmdargs << "--outfile" << m_outFileName;
257 
258  // Timeout in 30s
259  auto *ms = new MythSystemLegacy(command, cmdargs,
265  ms->SetNice(10);
266  ms->SetIOPrio(7);
267 
268  ms->Run(30s);
269  uint ret = ms->Wait();
270  delete ms;
271 
272  if (ret != GENERIC_EXIT_OK)
273  {
274  LOG(VB_GENERAL, LOG_ERR, LOC +
275  QString("Encountered problems running '%1 %2' - (%3)")
276  .arg(command, cmdargs.join(" "), QString::number(ret)));
277  }
278  else
279  {
280  LOG(VB_PLAYBACK, LOG_INFO, LOC + "Preview process returned 0.");
281  QString outname = (!m_outFileName.isEmpty()) ?
282  m_outFileName : (m_pathname + ".png");
283 
284  QString lpath = QFileInfo(outname).fileName();
285  if (lpath == outname)
286  {
287  StorageGroup sgroup;
288  QString tmpFile = sgroup.FindFile(lpath);
289  outname = (tmpFile.isEmpty()) ? outname : tmpFile;
290  }
291 
292  QFileInfo fi(outname);
293  ok = (fi.exists() && fi.isReadable() && (fi.size() != 0));
294  if (ok)
295  {
296  LOG(VB_PLAYBACK, LOG_INFO, LOC + "Preview process ran ok.");
297  msg = QString("Generated on %1 in %2 seconds, starting at %3")
298  .arg(gCoreContext->GetHostName())
299  .arg(te.elapsed()*0.001)
300  .arg(tm.toString(Qt::ISODate));
301  }
302  else
303  {
304  LOG(VB_GENERAL, LOG_ERR, LOC + "Preview process not ok." +
305  QString("\n\t\t\tfileinfo(%1)").arg(outname) +
306  QString(" exists: %1").arg(fi.exists()) +
307  QString(" readable: %1").arg(fi.isReadable()) +
308  QString(" size: %1").arg(fi.size()));
309  LOG(VB_GENERAL, LOG_ERR, LOC +
310  QString("Despite command '%1' returning success")
311  .arg(command));
312  msg = QString("Failed to read preview image despite "
313  "preview process returning success.");
314  }
315  }
316  }
317 
318  QMutexLocker locker(&m_previewLock);
319 
320  // Backdate file to start of preview time in case a bookmark was made
321  // while we were generating the preview.
322  QString output_fn = m_outFileName.isEmpty() ?
324 
325  QDateTime dt;
326  if (ok)
327  {
328  QFileInfo fi(output_fn);
329  if (fi.exists())
330  dt = fi.lastModified();
331  }
332 
333  QString message = (ok) ? "PREVIEW_SUCCESS" : "PREVIEW_FAILED";
334  if (m_listener)
335  {
336  QStringList list;
337  list.push_back(QString::number(m_programInfo.GetRecordingID()));
338  list.push_back(output_fn);
339  list.push_back(msg);
340  list.push_back(dt.isValid()?dt.toUTC().toString(Qt::ISODate):"");
341  list.push_back(m_token);
342  QCoreApplication::postEvent(m_listener, new MythEvent(message, list));
343  }
344 
345  return ok;
346 }
347 
349 {
350  RunProlog();
351  Run();
352  RunEpilog();
353 }
354 
356 {
357  QStringList strlist( "QUERY_GENPIXMAP2" );
358  if (m_token.isEmpty())
359  {
360  m_token = QString("%1:%2")
361  .arg(m_programInfo.MakeUniqueKey()).arg(MythRandom());
362  }
363  strlist.push_back(m_token);
364  m_programInfo.ToStringList(strlist);
365  if (m_captureTime >= 0s)
366  {
367  strlist.push_back("s");
368  strlist.push_back(QString::number(m_captureTime.count()));
369  }
370  else
371  {
372  strlist.push_back("f");
373  strlist.push_back(QString::number(m_captureFrame));
374  }
375  if (m_outFileName.isEmpty())
376  {
377  strlist.push_back("<EMPTY>");
378  }
379  else
380  {
381  QFileInfo fi(m_outFileName);
382  strlist.push_back(fi.fileName());
383  }
384  strlist.push_back(QString::number(m_outSize.width()));
385  strlist.push_back(QString::number(m_outSize.height()));
386 
387  gCoreContext->addListener(this);
388  m_pixmapOk = false;
389 
390  bool ok = gCoreContext->SendReceiveStringList(strlist);
391  if (!ok || strlist.empty() || (strlist[0] != "OK"))
392  {
393  if (!ok)
394  {
395  LOG(VB_GENERAL, LOG_ERR, LOC +
396  "Remote Preview failed due to communications error.");
397  }
398  else if (strlist.size() > 1)
399  {
400  LOG(VB_GENERAL, LOG_ERR, LOC +
401  "Remote Preview failed, reason given: " + strlist[1]);
402  }
403 
405 
406  return false;
407  }
408 
409  QMutexLocker locker(&m_previewLock);
410 
411  // wait up to 35 seconds for the preview to complete
412  // The backend waits 30s for creation
413  if (!m_gotReply)
414  m_previewWaitCondition.wait(&m_previewLock, 35UL * 1000);
415 
416  if (!m_gotReply)
417  LOG(VB_GENERAL, LOG_NOTICE, LOC + "RemotePreviewRun() -- no reply..");
418 
420 
421  return m_pixmapOk;
422 }
423 
424 bool PreviewGenerator::event(QEvent *e)
425 {
426  if (e->type() != MythEvent::kMythEventMessage)
427  return QObject::event(e);
428 
429  auto *me = dynamic_cast<MythEvent*>(e);
430  if (me == nullptr)
431  return QObject::event(e);
432  if (me->Message() != "GENERATED_PIXMAP" || me->ExtraDataCount() < 3)
433  return QObject::event(e);
434 
435  bool ok = me->ExtraData(0) == "OK";
436  bool ours = false;
437  uint i = ok ? 4 : 3;
438  for (; i < (uint) me->ExtraDataCount() && !ours; i++)
439  ours |= me->ExtraData(i) == m_token;
440  if (!ours)
441  return QObject::event(e);
442 
443  const QString& pginfokey = me->ExtraData(1);
444 
445  QMutexLocker locker(&m_previewLock);
446  m_gotReply = true;
447  m_pixmapOk = ok;
448  if (!ok)
449  {
450  LOG(VB_GENERAL, LOG_ERR, LOC + pginfokey + ": " + me->ExtraData(2));
451  m_previewWaitCondition.wakeAll();
452  return true;
453  }
454 
455  if (me->ExtraDataCount() < 5)
456  {
457  m_pixmapOk = false;
458  m_previewWaitCondition.wakeAll();
459  return true; // could only happen with very broken client...
460  }
461 
462  QDateTime datetime = MythDate::fromString(me->ExtraData(3));
463  if (!datetime.isValid())
464  {
465  m_pixmapOk = false;
466  LOG(VB_GENERAL, LOG_ERR, LOC + pginfokey + "Got invalid date");
467  m_previewWaitCondition.wakeAll();
468  return QObject::event(e);
469  }
470 
471  size_t length = me->ExtraData(4).toULongLong();
472  quint16 checksum16 = me->ExtraData(5).toUInt();
473  QByteArray data = QByteArray::fromBase64(me->ExtraData(6).toLatin1());
474  if ((size_t) data.size() < length)
475  { // (note data.size() may be up to 3
476  // bytes longer after decoding
477  LOG(VB_GENERAL, LOG_ERR, LOC +
478  QString("Preview size check failed %1 < %2")
479  .arg(data.size()).arg(length));
480  data.clear();
481  }
482  data.resize(length);
483 
484 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
485  quint16 calculated = qChecksum(data.constData(), data.size());
486 #else
487  quint16 calculated = qChecksum(data);
488 #endif
489  if (checksum16 != calculated)
490  {
491  LOG(VB_GENERAL, LOG_ERR, LOC + "Preview checksum failed");
492  data.clear();
493  }
494 
495  m_pixmapOk = (data.isEmpty()) ? false : SaveOutFile(data, datetime);
496 
497  m_previewWaitCondition.wakeAll();
498 
499  return true;
500 }
501 
502 bool PreviewGenerator::SaveOutFile(const QByteArray &data, const QDateTime &dt)
503 {
504  if (m_outFileName.isEmpty())
505  {
506  QString remotecachedirname =
507  QString("%1/cache/remotecache").arg(GetConfDir());
508  QDir remotecachedir(remotecachedirname);
509 
510  if (!remotecachedir.exists())
511  {
512  if (!remotecachedir.mkdir(remotecachedirname))
513  {
514  LOG(VB_GENERAL, LOG_ERR, LOC +
515  "Remote Preview failed because we could not create a "
516  "remote cache directory");
517  return false;
518  }
519  }
520 
521  QString filename = m_programInfo.GetBasename() + ".png";
522  SetOutputFilename(QString("%1/%2").arg(remotecachedirname, filename));
523  }
524 
525  QFile file(m_outFileName);
526  bool ok = file.open(QIODevice::Unbuffered|QIODevice::WriteOnly);
527  if (!ok)
528  {
529  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to open: '%1'")
530  .arg(m_outFileName));
531  }
532 
533  off_t offset = 0;
534  size_t remaining = data.size();
535  uint failure_cnt = 0;
536  while ((remaining > 0) && (failure_cnt < 5))
537  {
538  ssize_t written = file.write(data.data() + offset, remaining);
539  if (written < 0)
540  {
541  failure_cnt++;
542  usleep(50ms);
543  continue;
544  }
545 
546  failure_cnt = 0;
547  offset += written;
548  remaining -= written;
549  }
550 
551  if (ok && !remaining)
552  {
553  file.close();
554  struct utimbuf times {};
555  times.actime = times.modtime = dt.toSecsSinceEpoch();
556  utime(m_outFileName.toLocal8Bit().constData(), &times);
557  LOG(VB_FILE, LOG_INFO, LOC + QString("Saved: '%1'").arg(m_outFileName));
558  }
559  else
560  {
561  file.remove();
562  }
563 
564  return ok;
565 }
566 
568  const unsigned char *data,
569  uint width, uint height, float aspect,
570  int desired_width, int desired_height,
571  const QString &format)
572 {
573  if (!data || !width || !height)
574  return false;
575 
576  const QImage img((unsigned char*) data,
577  width, height, QImage::Format_RGB32);
578 
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))
583  {
584  ppw = img.width();
585  pph = img.height();
586  desired_size_exactly_specified = false;
587  }
588 
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;
592 
593  if (!desired_size_exactly_specified)
594  {
595  if (aspect > ppw / pph)
596  pph = (ppw / aspect);
597  else
598  ppw = (pph * aspect);
599  }
600 
601  ppw = std::max(1.0F, ppw);
602  pph = std::max(1.0F, pph);;
603 
604  QImage small_img = img.scaled((int) ppw, (int) pph,
605  Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
606 
607  QTemporaryFile f(QFileInfo(filename).absoluteFilePath()+".XXXXXX");
608  f.setAutoRemove(false);
609  if (f.open() && small_img.save(&f, format.toLocal8Bit().constData()))
610  {
611  // Let anybody update it
612  bool ret = makeFileAccessible(f.fileName().toLocal8Bit().constData());
613  if (!ret)
614  {
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");
619  }
620  QFile of(filename);
621  of.remove();
622  if (f.rename(filename))
623  {
624  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Saved preview '%0' %1x%2")
625  .arg(filename).arg((int) ppw).arg((int) pph));
626  return true;
627  }
628  f.remove();
629  }
630 
631  return false;
632 }
633 
635 {
638 
639  float aspect = 0;
640  std::chrono::seconds captime = m_captureTime;
641  long long capframe = -1;
642 
643  QDateTime dt = MythDate::current();
644 
645  if (captime > 0s)
646  LOG(VB_GENERAL, LOG_INFO, "Preview from time spec");
647  else
648  {
649  capframe = m_programInfo.QueryStartMark();
650  if (capframe > 0)
651  {
652  LOG(VB_GENERAL, LOG_INFO,
653  QString("Preview from bookmark (frame %1)").arg(capframe));
654  }
655  }
656 
657  if ((captime <= 0s) && (capframe <= 0))
658  {
659  std::chrono::seconds startEarly = 0s;
660  std::chrono::seconds programDuration = 0s;
661  auto preroll = gCoreContext->GetDurSetting<std::chrono::seconds>("RecordPreRoll", 0s);
662  if (m_programInfo.GetScheduledStartTime().isValid() &&
663  m_programInfo.GetScheduledEndTime().isValid() &&
666  {
667  programDuration = std::chrono::seconds(m_programInfo.GetScheduledStartTime()
669  }
670  if (m_programInfo.GetRecordingStartTime().isValid() &&
671  m_programInfo.GetScheduledStartTime().isValid() &&
674  {
675  startEarly = std::chrono::seconds(m_programInfo.GetRecordingStartTime()
677  }
678  if (programDuration > 0s)
679  {
680  captime = programDuration / 3;
681  if (captime > 10min)
682  captime = 10min;
683  captime += startEarly;
684  }
685  if (captime < 0s)
686  captime = 10min;
687  captime += preroll;
688  LOG(VB_GENERAL, LOG_INFO,
689  QString("Preview at calculated offset (%1 seconds)").arg(captime.count()));
690  }
691 
692  int width = 0;
693  int height = 0;
694  int sz = 0;
695  auto *data = (unsigned char*) GetScreenGrab(m_programInfo, m_pathname,
696  captime, capframe,
697  sz, width, height, aspect);
698 
700 
701  QString format = (m_outFormat.isEmpty()) ? "PNG" : m_outFormat;
702 
703  int dw = (m_outSize.width() < 0) ? width : m_outSize.width();
704  int dh = (m_outSize.height() < 0) ? height : m_outSize.height();
705 
706  bool ok = SavePreview(outname, data, width, height, aspect, dw, dh,
707  format);
708 
709  if (ok)
710  {
711  // Backdate file to start of preview time in case a bookmark was made
712  // while we were generating the preview.
713  struct utimbuf times {};
714  times.actime = times.modtime = dt.toSecsSinceEpoch();
715  utime(outname.toLocal8Bit().constData(), &times);
716  }
717 
718  delete[] data;
719 
721 
722  return ok;
723 }
724 
726  const QString &pathname, const QString &outFileName)
727 {
728  QString outname = pathname + ".png";
729 
730  if (outFileName.isEmpty())
731  return outname;
732 
733  outname = outFileName;
734  QFileInfo fi(outname);
735  if (outname == fi.fileName())
736  {
737  QString dir;
738  if (pathname.contains(':'))
739  {
740  QUrl uinfo(pathname);
741  uinfo.setPath("");
742  dir = uinfo.toString();
743  }
744  else
745  {
746  dir = QFileInfo(pathname).path();
747  }
748  outname = dir + "/" + fi.fileName();
749  LOG(VB_FILE, LOG_INFO, LOC + QString("outfile '%1' -> '%2'")
750  .arg(outFileName, outname));
751  }
752 
753  return outname;
754 }
755 
757 {
758  QString tmppathname = m_pathname;
759 
760  if (tmppathname.startsWith("dvd:"))
761  tmppathname = tmppathname.section(":", 1, 1);
762 
763  if (!QFileInfo(tmppathname).isReadable())
764  return false;
765 
766  tmppathname = m_outFileName.isEmpty() ? tmppathname : m_outFileName;
767  QString pathdir = QFileInfo(tmppathname).path();
768 
769  if (!QFileInfo(pathdir).isWritable())
770  {
771  LOG(VB_GENERAL, LOG_WARNING, LOC +
772  QString("Output path '%1' is not writeable") .arg(pathdir));
773  return false;
774  }
775 
776  return true;
777 }
778 
796  const ProgramInfo &pginfo, const QString &filename,
797  std::chrono::seconds seektime, long long seekframe,
798  int &bufferlen,
799  int &video_width, int &video_height, float &video_aspect)
800 {
801  char *retbuf = nullptr;
802  bufferlen = 0;
803 
805  {
806  LOG(VB_GENERAL, LOG_ERR, LOC + "Previewer could not connect to DB.");
807  return nullptr;
808  }
809 
810  // pre-test local files for existence and size. 500 ms speed-up...
811  if (filename.startsWith("/"))
812  {
813  QFileInfo info(filename);
814  bool invalid = (!info.exists() || !info.isReadable() || (info.isFile() && (info.size() < 8LL*1024)));
815  if (invalid)
816  {
817  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Previewer file '%1' is not valid.")
818  .arg(filename));
819  return nullptr;
820  }
821  }
822 
823  MythMediaBuffer* buffer = MythMediaBuffer::Create(filename, false, false, 0ms);
824  if (!buffer || !buffer->IsOpen())
825  {
826  LOG(VB_GENERAL, LOG_ERR, LOC + "Previewer could not open file: " +
827  QString("'%1'").arg(filename));
828  delete buffer;
829  return nullptr;
830  }
831 
832  auto *ctx = new PlayerContext(kPreviewGeneratorInUseID);
833  auto * player = new MythPreviewPlayer(ctx, static_cast<PlayerFlags>(kAudioMuted | kVideoIsNull | kNoITV));
834  ctx->SetRingBuffer(buffer);
835  ctx->SetPlayingInfo(&pginfo);
836  ctx->SetPlayer(player);
837 
838  if (seektime >= 0s)
839  {
840  retbuf = player->GetScreenGrab(seektime, bufferlen,
841  video_width, video_height, video_aspect);
842  }
843  else
844  {
845  retbuf = player->GetScreenGrabAtFrame(static_cast<uint64_t>(seekframe), true,
846  bufferlen, video_width, video_height, video_aspect);
847  }
848  delete ctx;
849 
850  auto pos_text = (seektime != std::chrono::seconds::max())
851  ? QString::number(seektime.count()) + "s"
852  : QString::number(seekframe)+ "f";
853  if (retbuf)
854  {
855  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Grabbed preview '%0' %1x%2@%3")
856  .arg(filename).arg(video_width).arg(video_height).arg(pos_text));
857  }
858  else
859  {
860  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to grab preview '%0' %1x%2@%3")
861  .arg(filename).arg(video_width).arg(video_height).arg(pos_text));
862  }
863 
864  return retbuf;
865 }
PreviewGenerator::m_outSize
QSize m_outSize
Definition: previewgenerator.h:119
ProgramInfo::MakeUniqueKey
QString MakeUniqueKey(void) const
Creates a unique string that can be used to identify an existing recording.
Definition: programinfo.h:339
MythEvent::kMythEventMessage
static const Type kMythEventMessage
Definition: mythevent.h:79
kMSDontBlockInputDevs
@ kMSDontBlockInputDevs
avoid blocking LIRC & Joystick Menu
Definition: mythsystem.h:36
MythSystemLegacy
Definition: mythsystemlegacy.h:67
PreviewGenerator::PreviewGenerator
PreviewGenerator(const ProgramInfo *pginfo, QString token, Mode mode=kLocal)
Constructor.
Definition: previewgenerator.cpp:73
mythrandom.h
MythCoreContext::SendReceiveStringList
bool SendReceiveStringList(QStringList &strlist, bool quickTimeout=false, bool block=true)
Send a message to the backend and wait for a response.
Definition: mythcorecontext.cpp:1371
PreviewGenerator::kRemote
@ kRemote
Definition: previewgenerator.h:44
PreviewGenerator::run
void run(void) override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
Definition: previewgenerator.cpp:348
PreviewGenerator::AttachSignals
void AttachSignals(QObject *obj)
Definition: previewgenerator.cpp:115
MThread::wait
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:300
PreviewGenerator::Run
bool Run(void)
Definition: previewgenerator.cpp:208
PreviewGenerator::m_token
QString m_token
Definition: previewgenerator.h:122
StorageGroup::FindFile
QString FindFile(const QString &filename)
Definition: storagegroup.cpp:597
ProgramInfo::GetRecordingID
uint GetRecordingID(void) const
Definition: programinfo.h:446
MythEvent
This class is used as a container for messages.
Definition: mythevent.h:16
ProgramInfo::QueryStartMark
uint64_t QueryStartMark(void) const
Definition: programinfo.cpp:2848
MThread::usleep
static void usleep(std::chrono::microseconds time)
Definition: mthread.cpp:335
PreviewGenerator::RunReal
bool RunReal(void)
This call creates a preview without starting a new thread.
Definition: previewgenerator.cpp:124
PreviewGenerator::m_pathname
QString m_pathname
Definition: previewgenerator.h:113
MythMediaBuffer
Definition: mythmediabuffer.h:50
ProgramInfo::GetScheduledEndTime
QDateTime GetScheduledEndTime(void) const
The scheduled end time of the program.
Definition: programinfo.h:397
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MThread::RunProlog
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:196
PlayerFlags
PlayerFlags
Definition: mythplayer.h:64
PreviewGenerator::SavePreview
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)
Definition: previewgenerator.cpp:567
PreviewGenerator::LocalPreviewRun
bool LocalPreviewRun(void)
Definition: previewgenerator.cpp:634
build_compdb.file
file
Definition: build_compdb.py:55
mythdirs.h
GENERIC_EXIT_OK
@ GENERIC_EXIT_OK
Exited with no error.
Definition: exitcodes.h:11
PreviewGenerator::m_captureFrame
long long m_captureFrame
Definition: previewgenerator.h:117
remoteutil.h
MythDate::current
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:14
ProgramInfo::GetRecordingStartTime
QDateTime GetRecordingStartTime(void) const
Approximate time the recording started.
Definition: programinfo.h:404
ProgramInfo::GetPathname
QString GetPathname(void) const
Definition: programinfo.h:343
mythsystemlegacy.h
PreviewGenerator::event
bool event(QEvent *e) override
Definition: previewgenerator.cpp:424
MythObservable::addListener
void addListener(QObject *listener)
Add a listener to the observable.
Definition: mythobservable.cpp:38
PreviewGenerator::TeardownAll
void TeardownAll(void)
Definition: previewgenerator.cpp:102
ProgramInfo::MarkAsInUse
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...
Definition: programinfo.cpp:5153
PreviewGenerator::SetOutputFilename
void SetOutputFilename(const QString &fileName)
Definition: previewgenerator.cpp:93
mythdate.h
ProgramInfo::GetScheduledStartTime
QDateTime GetScheduledStartTime(void) const
The scheduled start time of program.
Definition: programinfo.h:390
PreviewGenerator::m_outFormat
QString m_outFormat
Definition: previewgenerator.h:120
mythlogging.h
GetConfDir
QString GetConfDir(void)
Definition: mythdirs.cpp:256
remotefile.h
MythDate::kFilename
@ kFilename
Default UTC, "yyyyMMddhhmmss".
Definition: mythdate.h:18
MythCoreContext::GetDurSetting
std::enable_if_t< std::chrono::__is_duration< T >::value, T > GetDurSetting(const QString &key, T defaultval=T::zero())
Definition: mythcorecontext.h:168
MSqlQuery::testDBConnection
static bool testDBConnection()
Checks DB connection + login (login info via Mythcontext)
Definition: mythdbcon.cpp:877
MThread::RunEpilog
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:209
PreviewGenerator::~PreviewGenerator
~PreviewGenerator() override
Definition: previewgenerator.cpp:87
storagegroup.h
kNoITV
@ kNoITV
Definition: mythplayer.h:75
kMSPropagateLogs
@ kMSPropagateLogs
add arguments for MythTV log propagation
Definition: mythsystem.h:52
PreviewGenerator::IsLocal
bool IsLocal(void) const
Definition: previewgenerator.cpp:756
uint
unsigned int uint
Definition: compat.h:81
PreviewGenerator::kForceLocal
@ kForceLocal
Definition: previewgenerator.h:46
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:55
kPreviewGeneratorInUseID
const QString kPreviewGeneratorInUseID
Definition: programtypes.cpp:25
PreviewGenerator::m_previewWaitCondition
QWaitCondition m_previewWaitCondition
Definition: previewgenerator.h:107
mythmediabuffer.h
kMSAutoCleanup
@ kMSAutoCleanup
automatically delete if backgrounded
Definition: mythsystem.h:45
MythDate::fromString
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
Definition: mythdate.cpp:34
PreviewGenerator::SaveOutFile
bool SaveOutFile(const QByteArray &data, const QDateTime &dt)
Definition: previewgenerator.cpp:502
off_t
#define off_t
Definition: mythiowrapper.cpp:241
PreviewGenerator::m_programInfo
ProgramInfo m_programInfo
Definition: previewgenerator.h:109
PreviewGenerator::m_outFileName
QString m_outFileName
Definition: previewgenerator.h:118
PreviewGenerator::RemotePreviewRun
bool RemotePreviewRun(void)
Definition: previewgenerator.cpp:355
PreviewGenerator::m_pixmapOk
bool m_pixmapOk
Definition: previewgenerator.h:124
PreviewGenerator::m_gotReply
bool m_gotReply
Definition: previewgenerator.h:123
ProgramInfo::GetChanID
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
Definition: programinfo.h:372
kVideoIsNull
@ kVideoIsNull
Definition: mythplayer.h:73
PreviewGenerator::m_captureTime
std::chrono::seconds m_captureTime
snapshot time in seconds or frame number (seconds has priority)
Definition: previewgenerator.h:116
ProgramInfo
Holds information on recordings and videos.
Definition: programinfo.h:67
makeFileAccessible
bool makeFileAccessible(const QString &filename)
Definition: mythmiscutil.cpp:395
ProgramInfo::ToStringList
void ToStringList(QStringList &list) const
Serializes ProgramInfo into a QStringList which can be passed over a socket.
Definition: programinfo.cpp:1267
PreviewGenerator::GetScreenGrab
static char * 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.
Definition: previewgenerator.cpp:795
playercontext.h
PreviewGenerator::CreateAccessibleFilename
static QString CreateAccessibleFilename(const QString &pathname, const QString &outFileName)
Definition: previewgenerator.cpp:725
kMSProcessEvents
@ kMSProcessEvents
process events while waiting
Definition: mythsystem.h:39
remotecachedir
static QString remotecachedir
Definition: mythdirs.cpp:28
LOC
#define LOC
Definition: previewgenerator.cpp:40
std
Definition: mythchrono.h:23
MythDate::ISODate
@ ISODate
Default UTC.
Definition: mythdate.h:17
PreviewGenerator::m_mode
Mode m_mode
Definition: previewgenerator.h:111
MThread
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:48
GetAppBinDir
QString GetAppBinDir(void)
Definition: mythdirs.cpp:253
tv_rec.h
kAudioMuted
@ kAudioMuted
Definition: mythplayer.h:74
StorageGroup
Definition: storagegroup.h:11
PlayerContext
Definition: playercontext.h:49
MythMediaBuffer::Create
static MythMediaBuffer * Create(const QString &Filename, bool Write, bool UseReadAhead=true, std::chrono::milliseconds Timeout=kDefaultOpenTimeout, bool StreamOnly=false)
Creates a RingBuffer instance.
Definition: mythmediabuffer.cpp:98
MythPreviewPlayer
Definition: mythpreviewplayer.h:7
PreviewGenerator::m_listener
QObject * m_listener
Definition: previewgenerator.h:112
PreviewGenerator::deleteLater
void deleteLater()
Definition: previewgenerator.cpp:109
MythCoreContext::GetHostName
QString GetHostName(void)
Definition: mythcorecontext.cpp:838
ProgramInfo::SetIgnoreProgStart
void SetIgnoreProgStart(bool ignore)
If "ignore" is true QueryProgStart() will return 0, otherwise QueryProgStart() will return the progst...
Definition: programinfo.h:565
PreviewGenerator::kLocal
@ kLocal
Definition: previewgenerator.h:43
kMSDontDisableDrawing
@ kMSDontDisableDrawing
avoid disabling UI drawing
Definition: mythsystem.h:37
previewgenerator.h
exitcodes.h
ProgramInfo::GetBasename
QString GetBasename(void) const
Definition: programinfo.h:344
build_compdb.filename
filename
Definition: build_compdb.py:21
mythsocket.h
mythpreviewplayer.h
MythRandomStd::MythRandom
uint32_t MythRandom()
generate 32 random bits
Definition: mythrandom.h:20
MythObservable::removeListener
void removeListener(QObject *listener)
Remove a listener to the observable.
Definition: mythobservable.cpp:55
PreviewGenerator::Mode
Mode
Definition: previewgenerator.h:40
PreviewGenerator::m_previewLock
QMutex m_previewLock
Definition: previewgenerator.h:108
MythMediaBuffer::IsOpen
virtual bool IsOpen(void) const =0