MythTV  master
CommDetector2.cpp
Go to the documentation of this file.
1 // ANSI C headers
2 #include <cmath>
3 #include <cerrno>
4 #include <chrono> // for milliseconds
5 #include <thread> // for sleep_for
6 
7 // C++ headers
8 #include <algorithm>
9 using namespace std;
10 
11 // Qt headers
12 #include <QCoreApplication>
13 #include <QDir>
14 #include <QFileInfo>
15 
16 // MythTV headers
17 #include "compat.h"
18 #include "mythdb.h"
19 #include "mythlogging.h"
20 #include "mythplayer.h"
21 #include "programinfo.h"
22 #include "channelutil.h"
23 
24 // Commercial Flagging headers
25 #include "CommDetector2.h"
26 #include "CannyEdgeDetector.h"
27 #include "FrameAnalyzer.h"
28 #include "PGMConverter.h"
29 #include "BorderDetector.h"
30 #include "HistogramAnalyzer.h"
31 #include "BlankFrameDetector.h"
32 #include "SceneChangeDetector.h"
33 #include "TemplateFinder.h"
34 #include "TemplateMatcher.h"
35 
36 namespace {
37 
38 bool stopForBreath(bool isrecording, long long frameno)
39 {
40  return (isrecording && (frameno % 100) == 0) || (frameno % 500) == 0;
41 }
42 
43 bool needToReportState(bool showprogress, bool isrecording, long long frameno)
44 {
45  return ((showprogress || isrecording) && (frameno % 100) == 0) ||
46  (frameno % 500) == 0;
47 }
48 
49 void waitForBuffer(const struct timeval *framestart, int minlag, int flaglag,
50  float fps, bool fullspeed)
51 {
52  long usperframe = (long)(1000000.0F / fps);
53  struct timeval now {};
54  struct timeval elapsed {};
55 
56  (void)gettimeofday(&now, nullptr);
57  timersub(&now, framestart, &elapsed);
58 
59  // Sleep for one frame's worth of time.
60  long sleepus = usperframe - elapsed.tv_sec * 1000000 - elapsed.tv_usec;
61  if (sleepus <= 0)
62  return;
63 
64  if (flaglag > minlag)
65  {
66  // Try to catch up; reduce sleep time.
67  if (fullspeed)
68  return;
69  sleepus /= 4;
70  }
71  else if (flaglag < minlag)
72  {
73  // Slow down; increase sleep time
74  sleepus = sleepus * 3 / 2;
75  }
76  std::this_thread::sleep_for(std::chrono::microseconds(sleepus));
77 }
78 
79 bool MythPlayerInited(FrameAnalyzerItem &pass,
80  FrameAnalyzerItem &finishedAnalyzers,
81  FrameAnalyzerItem &deadAnalyzers,
82  MythPlayer *player,
83  long long nframes)
84 {
85  auto it = pass.begin();
86  while (it != pass.end())
87  {
89  (*it)->MythPlayerInited(player, nframes);
90 
91  if ((FrameAnalyzer::ANALYZE_OK == ares) ||
93  {
94  ++it;
95  }
96  else if (FrameAnalyzer::ANALYZE_FINISHED == ares)
97  {
98  finishedAnalyzers.push_back(*it);
99  it = pass.erase(it);
100  }
101  else if (FrameAnalyzer::ANALYZE_FATAL == ares)
102  {
103  deadAnalyzers.push_back(*it);
104  it = pass.erase(it);
105  }
106  else
107  {
108  LOG(VB_GENERAL, LOG_ERR,
109  QString("Unexpected return value from %1::MythPlayerInited: %2")
110  .arg((*it)->name()).arg(ares));
111  return false;
112  }
113  }
114 
115  return true;
116 }
117 
118 long long processFrame(FrameAnalyzerItem &pass,
119  FrameAnalyzerItem &finishedAnalyzers,
120  FrameAnalyzerItem &deadAnalyzers,
121  const VideoFrame *frame,
122  long long frameno)
123 {
124  long long nextFrame = 0;
125  long long minNextFrame = FrameAnalyzer::kAnyFrame;
126 
127  auto it = pass.begin();
128  while (it != pass.end())
129  {
131  (*it)->analyzeFrame(frame, frameno, &nextFrame);
132 
133  if ((FrameAnalyzer::ANALYZE_OK == ares) ||
135  {
136  minNextFrame = std::min(minNextFrame, nextFrame);
137  ++it;
138  }
139  else if (ares == FrameAnalyzer::ANALYZE_FINISHED)
140  {
141  finishedAnalyzers.push_back(*it);
142  it = pass.erase(it);
143  }
144  else
145  {
146  if (ares != FrameAnalyzer::ANALYZE_FATAL)
147  {
148  LOG(VB_GENERAL, LOG_ERR,
149  QString("Unexpected return value from %1::analyzeFrame: %2")
150  .arg((*it)->name()).arg(ares));
151  }
152 
153  deadAnalyzers.push_back(*it);
154  it = pass.erase(it);
155  }
156  }
157 
158  if (minNextFrame == FrameAnalyzer::kAnyFrame)
159  minNextFrame = FrameAnalyzer::kNextFrame;
160 
161  if (minNextFrame == FrameAnalyzer::kNextFrame)
162  minNextFrame = frameno + 1;
163 
164  return minNextFrame;
165 }
166 
167 int passFinished(FrameAnalyzerItem &pass, long long nframes, bool final)
168 {
169  for (auto & pas : pass)
170  (void)pas->finished(nframes, final);
171 
172  return 0;
173 }
174 
175 int passReportTime(const FrameAnalyzerItem &pass)
176 {
177  for (auto pas : pass)
178  (void)pas->reportTime();
179 
180  return 0;
181 }
182 
183 bool searchingForLogo(TemplateFinder *tf, const FrameAnalyzerItem &pass)
184 {
185  if (!tf)
186  return false;
187 
188  auto it = std::find(pass.cbegin(), pass.cend(), tf);
189  return it != pass.end();
190 }
191 
192 }; // namespace
193 
194 namespace commDetector2 {
195 
196 QString debugDirectory(int chanid, const QDateTime& recstartts)
197 {
198  /*
199  * Should deleting a recording also delete its associated debug directory?
200  */
201  MSqlQuery query(MSqlQuery::InitCon());
202  query.prepare("SELECT basename"
203  " FROM recorded"
204  " WHERE chanid = :CHANID"
205  " AND starttime = :STARTTIME"
206  ";");
207  query.bindValue(":CHANID", chanid);
208  query.bindValue(":STARTTIME", recstartts);
209  if (!query.exec() || !query.next())
210  {
211  MythDB::DBError("Error in CommDetector2::CommDetector2", query);
212  return "";
213  }
214 
215  ProgramInfo pginfo(chanid, recstartts);
216 
217  if (!pginfo.GetChanID())
218  return "";
219 
220  QString pburl = pginfo.GetPlaybackURL(true);
221  if (!pburl.startsWith("/"))
222  return "";
223 
224  QString basename(query.value(0).toString());
225  QString m_debugdir = pburl.section('/',0,-2) + "/" + basename + "-debug";
226 
227  return m_debugdir;
228 }
229 
230 void createDebugDirectory(const QString& dirname, const QString& comment)
231 {
232  QDir qdir(dirname);
233  if (qdir.exists())
234  {
235  LOG(VB_COMMFLAG, LOG_INFO, QString("%1 using debug directory \"%2\"")
236  .arg(comment).arg(dirname));
237  }
238  else
239  {
240  if (qdir.mkdir(dirname))
241  {
242  LOG(VB_COMMFLAG, LOG_INFO,
243  QString("%1 created debug directory \"%1\"")
244  .arg(comment).arg(dirname));
245  }
246  else
247  {
248  LOG(VB_COMMFLAG, LOG_INFO, QString("%1 failed to create \"%2\": %3")
249  .arg(comment).arg(dirname).arg(strerror(errno)));
250  }
251  }
252 }
253 
254 QString frameToTimestamp(long long frameno, float fps)
255 {
256  int ms = (int)roundf(frameno / fps * 1000);
257 
258  int ss = ms / 1000;
259  ms %= 1000;
260  if (ms >= 500)
261  ss++;
262 
263  int mm = ss / 60;
264  ss %= 60;
265 
266  int hh = mm / 60;
267  mm %= 60;
268 
269  return QString("%1:%2:%3")
270  .arg(hh).arg(mm, 2, 10, QChar('0')) .arg(ss, 2, 10, QChar('0'));
271 }
272 
273 QString frameToTimestampms(long long frameno, float fps)
274 {
275  int ms = (int)roundf(frameno / fps * 1000);
276 
277  int ss = ms / 1000;
278  ms %= 1000;
279 
280  int mm = ss / 60;
281  ss %= 60;
282 
283  return QString("%1:%2:%3")
284  .arg(mm).arg(ss, 2, 10, QChar(QChar('0'))).arg(ms, 2, 10, QChar(QChar('0')));
285 }
286 
287 QString strftimeval(const struct timeval *tv)
288 {
289  return QString("%1.%2")
290  .arg(tv->tv_sec).arg(tv->tv_usec, 6, 10, QChar(QChar('0')));
291 }
292 
293 }; /* namespace */
294 
295 using namespace commDetector2;
296 
298  SkipType commDetectMethod_in,
299  bool showProgress_in,
300  bool fullSpeed_in,
301  MythPlayer *player_in,
302  int chanid,
303  QDateTime startts_in,
304  QDateTime endts_in,
305  QDateTime recstartts_in,
306  QDateTime recendts_in,
307  bool useDB) :
308  m_commDetectMethod((SkipType)(commDetectMethod_in & ~COMM_DETECT_2)),
309  m_showProgress(showProgress_in), m_fullSpeed(fullSpeed_in),
310  m_player(player_in),
311  m_startts(std::move(startts_in)), m_endts(std::move(endts_in)),
312  m_recstartts(std::move(recstartts_in)), m_recendts(std::move(recendts_in)),
313  m_isRecording(MythDate::current() < m_recendts),
314  m_debugdir("")
315 {
316  FrameAnalyzerItem pass0;
317  FrameAnalyzerItem pass1;
318  PGMConverter *pgmConverter = nullptr;
319  BorderDetector *borderDetector = nullptr;
320  HistogramAnalyzer *histogramAnalyzer = nullptr;
321 
322  if (useDB)
324 
325  /*
326  * Look for blank frames to use as delimiters between commercial and
327  * non-commercial segments.
328  */
330  {
331  if (!pgmConverter)
332  pgmConverter = new PGMConverter();
333 
334  if (!borderDetector)
335  borderDetector = new BorderDetector();
336 
337  if (!histogramAnalyzer)
338  {
339  histogramAnalyzer = new HistogramAnalyzer(pgmConverter,
340  borderDetector, m_debugdir);
341  }
342 
344  {
345  m_blankFrameDetector = new BlankFrameDetector(histogramAnalyzer,
346  m_debugdir);
347  pass1.push_back(m_blankFrameDetector);
348  }
349  }
350 
351  /*
352  * Look for "scene changes" to use as delimiters between commercial and
353  * non-commercial segments.
354  */
356  {
357  if (!pgmConverter)
358  pgmConverter = new PGMConverter();
359 
360  if (!borderDetector)
361  borderDetector = new BorderDetector();
362 
363  if (!histogramAnalyzer)
364  {
365  histogramAnalyzer = new HistogramAnalyzer(pgmConverter,
366  borderDetector, m_debugdir);
367  }
368 
370  {
371  m_sceneChangeDetector = new SceneChangeDetector(histogramAnalyzer,
372  m_debugdir);
373  pass1.push_back(m_sceneChangeDetector);
374  }
375  }
376 
377  /*
378  * Logo Detection requires two passes. The first pass looks for static
379  * areas of the screen to look for logos and generate a template. The
380  * second pass matches the template against the frame and computes the
381  * closeness of the match.
382  */
384  {
385  if (!pgmConverter)
386  pgmConverter = new PGMConverter();
387 
388  if (!borderDetector)
389  borderDetector = new BorderDetector();
390 
391  auto *cannyEdgeDetector = new CannyEdgeDetector();
392 
393  if (!m_logoFinder)
394  {
395  m_logoFinder = new TemplateFinder(pgmConverter, borderDetector,
396  cannyEdgeDetector, m_player, m_recstartts.secsTo(m_recendts),
397  m_debugdir);
398  pass0.push_back(m_logoFinder);
399  }
400 
401  if (!m_logoMatcher)
402  {
403  m_logoMatcher = new TemplateMatcher(pgmConverter, cannyEdgeDetector,
405  pass1.push_back(m_logoMatcher);
406  }
407  }
408 
409  if (histogramAnalyzer && m_logoFinder)
410  histogramAnalyzer->setLogoState(m_logoFinder);
411 
412  /* Aggregate them all together. */
413  m_frameAnalyzers.push_back(pass0);
414  m_frameAnalyzers.push_back(pass1);
415 }
416 
417 void CommDetector2::reportState(int elapsedms, long long frameno,
418  long long nframes, unsigned int passno, unsigned int npasses)
419 {
420  float fps = elapsedms ? (float)frameno * 1000 / elapsedms : 0;
421  static int s_prevpercent = -1;
422 
423  /* Assume that 0-th pass is negligible in terms of computational cost. */
424  int percentage = (passno == 0 || npasses == 1 || nframes == 0) ? 0 :
425  (passno - 1) * 100 / (npasses - 1) +
426  min((long long)100, (frameno * 100 / nframes) / (npasses - 1));
427 
428  if (m_showProgress)
429  {
430  QString tmp = "";
431 
432  if (nframes)
433  tmp = QString("\r%1%/ %2fps").arg(percentage,3).arg(fps,6,'f',2);
434  else
435  tmp = QString("\r%1/ %2fps").arg(frameno,6).arg(fps,6,'f',2);
436 
437  QByteArray tmp2 = tmp.toLocal8Bit();
438  cerr << tmp2.constData() << " \r";
439  cerr.flush();
440  }
441 
442  if (nframes)
443  {
444  emit statusUpdate(QCoreApplication::translate("(mythcommflag)",
445  "%1% Completed @ %2 fps.").arg(percentage).arg(fps));
446  }
447  else
448  {
449  emit statusUpdate(QCoreApplication::translate("(mythcommflag)",
450  "%1 Frames Completed @ %2 fps.").arg(frameno).arg(fps));
451  }
452 
453  if (percentage % 10 == 0 && s_prevpercent != percentage)
454  {
455  s_prevpercent = percentage;
456  LOG(VB_GENERAL, LOG_INFO, QString("%1%% Completed @ %2 fps.")
457  .arg(percentage) .arg(fps));
458  }
459 }
460 
461 int CommDetector2::computeBreaks(long long nframes)
462 {
463  int trow = 0;
464  int tcol = 0;
465  int twidth = 0;
466  int theight = 0;
467 
468  m_breaks.clear();
469 
470  TemplateMatcher *matcher = nullptr;
471  if (m_logoFinder && m_logoFinder->getTemplate(&trow, &tcol, &twidth, &theight))
472  matcher = m_logoMatcher;
473 
474  if (matcher && m_blankFrameDetector)
475  {
476  int cmp = matcher->templateCoverage(nframes, m_finished);
477  if (cmp == 0)
478  {
479  if (matcher->adjustForBlanks(m_blankFrameDetector, nframes))
480  return -1;
481  if (matcher->computeBreaks(&m_breaks))
482  return -1;
483  }
484  else
485  {
486  if (cmp > 0 &&
488  return -1;
489  if (cmp < 0 &&
491  return -1;
492 
494  return -1;
495  }
496  }
497  else if (matcher)
498  {
499  if (matcher->computeBreaks(&m_breaks))
500  return -1;
501  }
502  else if (m_blankFrameDetector)
503  {
505  return -1;
506  }
507 
508  return 0;
509 }
510 
512 {
513  int minlag = 7; // seconds
514 
515  if (m_player->OpenFile() < 0)
516  return false;
517 
518  if (!m_player->InitVideo())
519  {
520  LOG(VB_GENERAL, LOG_ERR,
521  "NVP: Unable to initialize video for FlagCommercials.");
522  return false;
523  }
524 
525  m_player->EnableSubtitles(false);
526 
527  QElapsedTimer totalFlagTime;
528  totalFlagTime.start();
529 
530  /* If still recording, estimate the eventual total number of frames. */
531  long long nframes = m_isRecording ?
532  (long long)roundf((m_recstartts.secsTo(m_recendts) + 5) *
533  m_player->GetFrameRate()) :
535  bool postprocessing = !m_isRecording;
536 
537  if (m_showProgress)
538  {
539  if (nframes)
540  cerr << " 0%/ ";
541  else
542  cerr << " 0/ ";
543  cerr.flush();
544  }
545 
546  frm_dir_map_t lastBreakMap;
547  unsigned int passno = 0;
548  unsigned int npasses = m_frameAnalyzers.size();
549  for (m_currentPass = m_frameAnalyzers.begin();
551  ++m_currentPass, passno++)
552  {
553  FrameAnalyzerItem deadAnalyzers;
554 
555  LOG(VB_COMMFLAG, LOG_INFO,
556  QString("CommDetector2::go pass %1 of %2 (%3 frames, %4 fps)")
557  .arg(passno + 1).arg(npasses)
559  .arg(m_player->GetFrameRate(), 0, 'f', 2));
560 
561  if (!MythPlayerInited(
562  *m_currentPass, m_finishedAnalyzers, deadAnalyzers, m_player, nframes))
563  {
564  return false;
565  }
566 
568  long long nextFrame = -1;
570  long long lastLoggedFrame = m_currentFrameNumber;
571  QElapsedTimer passTime;
572  QElapsedTimer clock;
573  struct timeval getframetime {};
574 
576 
577  if (searchingForLogo(m_logoFinder, *m_currentPass))
578  emit statusUpdate(QCoreApplication::translate("(mythcommflag)",
579  "Performing Logo Identification"));
580 
581  clock.start();
582  passTime.start();
583  memset(&getframetime, 0, sizeof(getframetime));
584  while (!(*m_currentPass).empty() && m_player->GetEof() == kEofStateNone)
585  {
586  struct timeval start {};
587  struct timeval end {};
588  struct timeval elapsedtv {};
589 
590  (void)gettimeofday(&start, nullptr);
591  bool fetchNext = (nextFrame == m_currentFrameNumber + 1);
592  VideoFrame *currentFrame =
593  m_player->GetRawVideoFrame(fetchNext ? -1 : nextFrame);
594  long long lastFrameNumber = m_currentFrameNumber;
595  m_currentFrameNumber = currentFrame->frameNumber + 1;
596  (void)gettimeofday(&end, nullptr);
597  timersub(&end, &start, &elapsedtv);
598  timeradd(&getframetime, &elapsedtv, &getframetime);
599 
600  if (nextFrame != -1 && nextFrame == lastFrameNumber + 1 &&
601  m_currentFrameNumber != nextFrame)
602  {
603  /*
604  * Don't log "Jumped" when we know we're skipping frames (e.g.,
605  * logo detection).
606  */
607  LOG(VB_COMMFLAG, LOG_INFO,
608  QString("Jumped from frame %1 to frame %2")
609  .arg(lastFrameNumber).arg(m_currentFrameNumber));
610  }
611 
612  if (stopForBreath(m_isRecording, m_currentFrameNumber))
613  {
614  emit breathe();
615  if (m_bStop)
616  {
617  m_player->DiscardVideoFrame(currentFrame);
618  return false;
619  }
620  }
621 
622  while (m_bPaused)
623  {
624  emit breathe();
625  std::this_thread::sleep_for(std::chrono::seconds(1));
626  }
627 
628  if (!searchingForLogo(m_logoFinder, *m_currentPass) &&
629  needToReportState(m_showProgress, m_isRecording,
631  {
632  reportState(passTime.elapsed(), m_currentFrameNumber,
633  nframes, passno, npasses);
634  }
635 
636  nextFrame = processFrame(
638  deadAnalyzers, currentFrame, m_currentFrameNumber);
639 
640  if (((m_currentFrameNumber >= 1) && (nframes > 0) &&
641  (((nextFrame * 10) / nframes) !=
642  ((m_currentFrameNumber * 10) / nframes))) ||
643  (nextFrame >= nframes))
644  {
645  /* Log something every 10%. */
646  int elapsed = clock.restart();
647  LOG(VB_COMMFLAG, LOG_INFO,
648  QString("processFrame %1 of %2 (%3%) - %4 fps")
650  .arg(nframes)
651  .arg((nframes == 0) ? 0 :
652  (int)roundf(m_currentFrameNumber * 100.0 / nframes))
653  .arg((m_currentFrameNumber - lastLoggedFrame) * 1000 /
654  (elapsed ? elapsed : 1)));
655  lastLoggedFrame = m_currentFrameNumber;
656  }
657 
658  if (m_isRecording)
659  {
660  waitForBuffer(&start, minlag,
661  m_recstartts.secsTo(MythDate::current()) -
662  totalFlagTime.elapsed() / 1000, m_player->GetFrameRate(),
663  m_fullSpeed);
664  }
665 
666  // sleep a little so we don't use all cpu even if we're niced
667  if (!m_fullSpeed && !m_isRecording)
668  std::this_thread::sleep_for(std::chrono::milliseconds(10));
669 
671  !(m_currentFrameNumber % 500)))
672  {
673  frm_dir_map_t breakMap;
674 
675  GetCommercialBreakList(breakMap);
676 
677  auto ii = breakMap.cbegin();
678  auto jj = lastBreakMap.cbegin();
679  while (ii != breakMap.cend() && jj != lastBreakMap.cend())
680  {
681  if (ii.key() != jj.key())
682  break;
683  if (*ii != *jj)
684  break;
685  ++ii;
686  ++jj;
687  }
688  bool same = ii == breakMap.end() && jj == lastBreakMap.end();
689  lastBreakMap = breakMap;
690 
691  if (m_breakMapUpdateRequested || !same)
693 
695  }
696 
697  m_player->DiscardVideoFrame(currentFrame);
698  }
699 
700  // Save total duration only on the last pass, which hopefully does
701  // no skipping.
702  if (passno + 1 == npasses)
704 
705  m_currentPass->insert(m_currentPass->end(),
706  m_finishedAnalyzers.begin(),
707  m_finishedAnalyzers.end());
708  m_finishedAnalyzers.clear();
709 
710  if (postprocessing)
712  if (passFinished(*m_currentPass, m_currentFrameNumber + 1, true))
713  return false;
714 
715  LOG(VB_COMMFLAG, LOG_INFO, QString("NVP Time: GetRawVideoFrame=%1s")
716  .arg(strftimeval(&getframetime)));
717  if (passReportTime(*m_currentPass))
718  return false;
719  }
720 
721  if (m_showProgress)
722  {
723  if (nframes)
724  cerr << "\b\b\b\b\b\b \b\b\b\b\b\b";
725  else
726  cerr << "\b\b\b\b\b\b\b\b\b\b\b\b\b "
727  "\b\b\b\b\b\b\b\b\b\b\b\b\b";
728  cerr.flush();
729  }
730 
731  m_finished = true;
732  return true;
733 }
734 
736 {
737  if (!m_finished)
738  {
739  for (auto & analyzer : m_frameAnalyzers)
740  {
741  if (analyzer == *m_currentPass &&
742  passFinished(m_finishedAnalyzers, m_currentFrameNumber + 1, false))
743  {
744  return;
745  }
746 
747  if (passFinished(analyzer, m_currentFrameNumber + 1, false))
748  return;
749  }
750  }
751 
753  return;
754 
755  marks.clear();
756 
757  /* Create break list. */
758  long long breakframes = 0;
759  for (auto bb = m_breaks.begin(); bb != m_breaks.end(); ++bb)
760  {
761  long long segb = bb.key();
762  long long seglen = *bb;
763  long long sege = segb + seglen - 1;
764 
765  if (segb < sege)
766  {
767  marks[segb] = MARK_COMM_START;
768  marks[sege] = MARK_COMM_END;
769 
770  breakframes += seglen;
771  }
772  }
773 
774  /* Report results. */
775  const float fps = m_player->GetFrameRate();
776  for (frm_dir_map_t::const_iterator iimark = marks.begin();
777  iimark != marks.end();
778  ++iimark)
779  {
780  /* Display as 1-based frame numbers. */
781  long long markstart = iimark.key() + 1; /* MARK_COMM_BEGIN */
782  ++iimark; /* MARK_COMM_END */
783  if (iimark == marks.end())
784  break;
785  long long markend = iimark.key() + 1;
786 
787  LOG(VB_COMMFLAG, LOG_INFO, QString("Break: frame %1-%2 (%3-%4, %5)")
788  .arg(markstart, 6).arg(markend, 6)
789  .arg(frameToTimestamp(markstart, fps))
790  .arg(frameToTimestamp(markend, fps))
791  .arg(frameToTimestamp(markend - markstart + 1, fps)));
792  }
793 
794  const long long nframes = m_player->GetTotalFrameCount();
795  LOG(VB_COMMFLAG, LOG_INFO,
796  QString("Flagged %1 of %2 frames (%3 of %4), %5% commercials (%6)")
797  .arg(m_currentFrameNumber + 1).arg(nframes)
798  .arg(frameToTimestamp(m_currentFrameNumber + 1, fps))
799  .arg(frameToTimestamp(nframes, fps))
800  .arg(breakframes * 100 / m_currentFrameNumber)
801  .arg(frameToTimestamp(breakframes, fps)));
802 }
803 
804 void CommDetector2::recordingFinished(long long totalFileSize)
805 {
807  m_isRecording = false;
808  LOG(VB_COMMFLAG, LOG_INFO,
809  QString("CommDetector2::recordingFinished: %1 bytes")
810  .arg(totalFileSize));
811 }
812 
814 {
815  if (searchingForLogo(m_logoFinder, *m_currentPass))
816  {
817  LOG(VB_COMMFLAG, LOG_INFO, "Ignoring request for commBreakMapUpdate; "
818  "still doing logo detection");
819  return;
820  }
821 
822  LOG(VB_COMMFLAG, LOG_INFO,
823  QString("commBreakMapUpdate requested at frame %1")
824  .arg(m_currentFrameNumber + 1));
825  m_sendBreakMapUpdates = true;
827 }
828 
829 static void PrintReportMap(ostream &out,
830  const FrameAnalyzer::FrameMap &frameMap)
831 {
832  FrameAnalyzer::FrameMap::const_iterator it = frameMap.begin();
833  for (; it != frameMap.end(); ++it)
834  {
835  /*
836  * QMap'd as 0-based index, but display as 1-based index to match "Edit
837  * Recording" OSD.
838  */
839 
840  long long bb = it.key() + 1;
841  long long ee = (*it) ? (bb + *it) : 1;
842  QString tmp = QString("%1: %2").arg(bb, 10).arg(ee - 1, 10);
843 
844  out << qPrintable(tmp) << "\n";
845  }
846  out << flush;
847 }
848 
850  ostream &out, const frm_dir_map_t */*comm_breaks*/, bool /*verbose*/) const
851 {
852  FrameAnalyzer::FrameMap logoMap;
853  FrameAnalyzer::FrameMap blankMap;
854  FrameAnalyzer::FrameMap blankBreakMap;
855  FrameAnalyzer::FrameMap sceneMap;
856  if (m_logoFinder)
857  logoMap = m_logoFinder->GetMap(0);
858 
860  {
861  blankBreakMap = m_blankFrameDetector->GetMap(0);
862  blankMap = m_blankFrameDetector->GetMap(1);
863  }
864 
866  sceneMap = m_sceneChangeDetector->GetMap(0);
867 
868  out << "Logo Break Map" << endl;
869  PrintReportMap(out, logoMap);
870  out << "Blank Break Map" << endl;
871  PrintReportMap(out, blankBreakMap);
872  out << "Blank Map" << endl;
873  PrintReportMap(out, blankMap);
874  out << "Scene Break Map" << endl;
875  PrintReportMap(out, sceneMap);
876 }
877 
878 /* vim: set expandtab tabstop=4 shiftwidth=4: */
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:783
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:864
void ResetTotalDuration(void)
bool go(void) override
FrameAnalyzer::FrameMap m_breaks
Definition: CommDetector2.h:80
MythPlayer * m_player
Definition: CommDetector2.h:63
QString frameToTimestamp(long long frameno, float fps)
#define timeradd(a, b, result)
Definition: compat.h:300
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:125
static pid_list_t::iterator find(const PIDInfoMap &map, pid_list_t &list, pid_list_t::iterator begin, pid_list_t::iterator end, bool find_open)
QDateTime m_recstartts
Definition: CommDetector2.h:66
void reportState(int elapsedms, long long frameno, long long nframes, unsigned int passno, unsigned int npasses)
static const long long kNextFrame
Definition: FrameAnalyzer.h:57
QString m_debugdir
Definition: CommDetector2.h:87
static void PrintReportMap(ostream &out, const FrameAnalyzer::FrameMap &frameMap)
long long m_currentFrameNumber
Definition: CommDetector2.h:75
FrameMap GetMap(unsigned int) const override
int computeBreaks(long long nframes)
void PrintFullMap(ostream &out, const frm_dir_map_t *comm_breaks, bool verbose) const override
static guint32 * tmp
Definition: goom_core.c:35
int adjustForBlanks(const BlankFrameDetector *blankFrameDetector, long long nframes)
CommDetector2(SkipType commDetectMethod, bool showProgress, bool fullSpeed, MythPlayer *player, int chanid, QDateTime startts, QDateTime endts, QDateTime recstartts, QDateTime recendts, bool useDB)
bool m_sendBreakMapUpdates
Definition: CommDetector2.h:71
FrameMap GetMap(unsigned int) const override
FrameAnalyzerItem m_finishedAnalyzers
Definition: CommDetector2.h:78
QVariant value(int i) const
Definition: mythdbcon.h:198
Holds information on recordings and videos.
Definition: programinfo.h:67
QDateTime m_recendts
Definition: CommDetector2.h:67
SkipType m_commDetectMethod
Definition: CommDetector2.h:60
QString debugDirectory(int chanid, const QDateTime &recstartts)
void GetCommercialBreakList(frm_dir_map_t &marks) override
virtual int OpenFile(int Retries=4)
Definition: mythplayer.cpp:729
int computeBreaks(FrameMap *breaks)
SkipType
This is used as a bitmask.
Definition: programtypes.h:91
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
int computeForLogoSurplus(const TemplateMatcher *templateMatcher)
const struct AVFrame * getTemplate(int *prow, int *pcol, int *pwidth, int *pheight) const
VideoFrame * GetRawVideoFrame(long long frameNumber=-1)
Returns a specific frame from the video.
float GetFrameRate(void) const
Definition: mythplayer.h:211
EofState GetEof(void) const
SceneChangeDetector * m_sceneChangeDetector
Definition: CommDetector2.h:85
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:535
void SaveTotalDuration(void)
static const long long kAnyFrame
Definition: FrameAnalyzer.h:56
void setLogoState(TemplateFinder *finder)
FrameMap GetMap(unsigned int index) const override
TemplateMatcher * m_logoMatcher
Definition: CommDetector2.h:83
TemplateFinder * m_logoFinder
Definition: CommDetector2.h:82
static struct mark marks[16]
int templateCoverage(long long nframes, bool final) const
BlankFrameDetector * m_blankFrameDetector
Definition: CommDetector2.h:84
long long frameNumber
Definition: mythframe.h:147
void recordingFinished(long long totalFileSize) override
void statusUpdate(const QString &a)
QMap< long long, long long > FrameMap
Definition: FrameAnalyzer.h:43
bool m_breakMapUpdateRequested
Definition: CommDetector2.h:72
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:808
FrameAnalyzerList m_frameAnalyzers
Definition: CommDetector2.h:76
void createDebugDirectory(const QString &dirname, const QString &comment)
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
void gotNewCommercialBreakList()
virtual void recordingFinished(long long totalFileSize)
void DiscardVideoFrame(VideoFrame *buffer)
Places frame in the available frames queue.
Definition: mythplayer.cpp:944
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
Definition: programinfo.h:366
FrameAnalyzerList::iterator m_currentPass
Definition: CommDetector2.h:77
void EnableSubtitles(bool enable)
void requestCommBreakMapUpdate(void) override
QString frameToTimestampms(long long frameno, float fps)
QString strftimeval(const struct timeval *tv)
uint64_t GetTotalFrameCount(void) const
Definition: mythplayer.h:228
bool InitVideo(void)
Definition: mythplayer.cpp:367
static int computeForLogoDeficit(const TemplateMatcher *templateMatcher)
QMap< uint64_t, MarkTypes > frm_dir_map_t
Frame # -> Mark map.
Definition: programtypes.h:81
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:603
QString GetPlaybackURL(bool checkMaster=false, bool forceCheckLocal=false)
Returns filename or URL to be used to play back this recording.
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:179
#define timersub(a, b, result)
Definition: compat.h:310
int computeBreaks(FrameMap *breaks)
vector< FrameAnalyzer * > FrameAnalyzerItem
Definition: CommDetector2.h:34