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 {}, elapsed {};
54 
55  (void)gettimeofday(&now, nullptr);
56  timersub(&now, framestart, &elapsed);
57 
58  // Sleep for one frame's worth of time.
59  long sleepus = usperframe - elapsed.tv_sec * 1000000 - elapsed.tv_usec;
60  if (sleepus <= 0)
61  return;
62 
63  if (flaglag > minlag)
64  {
65  // Try to catch up; reduce sleep time.
66  if (fullspeed)
67  return;
68  sleepus /= 4;
69  }
70  else if (flaglag < minlag)
71  {
72  // Slow down; increase sleep time
73  sleepus = sleepus * 3 / 2;
74  }
75  std::this_thread::sleep_for(std::chrono::microseconds(sleepus));
76 }
77 
78 bool MythPlayerInited(FrameAnalyzerItem &pass,
79  FrameAnalyzerItem &finishedAnalyzers,
80  FrameAnalyzerItem &deadAnalyzers,
81  MythPlayer *player,
82  long long nframes)
83 {
84  auto it = pass.begin();
85  while (it != pass.end())
86  {
88  (*it)->MythPlayerInited(player, nframes);
89 
90  if ((FrameAnalyzer::ANALYZE_OK == ares) ||
92  {
93  ++it;
94  }
95  else if (FrameAnalyzer::ANALYZE_FINISHED == ares)
96  {
97  finishedAnalyzers.push_back(*it);
98  it = pass.erase(it);
99  }
100  else if (FrameAnalyzer::ANALYZE_FATAL == ares)
101  {
102  deadAnalyzers.push_back(*it);
103  it = pass.erase(it);
104  }
105  else
106  {
107  LOG(VB_GENERAL, LOG_ERR,
108  QString("Unexpected return value from %1::MythPlayerInited: %2")
109  .arg((*it)->name()).arg(ares));
110  return false;
111  }
112  }
113 
114  return true;
115 }
116 
117 long long processFrame(FrameAnalyzerItem &pass,
118  FrameAnalyzerItem &finishedAnalyzers,
119  FrameAnalyzerItem &deadAnalyzers,
120  const VideoFrame *frame,
121  long long frameno)
122 {
123  long long nextFrame = 0;
124  long long minNextFrame = FrameAnalyzer::ANYFRAME;
125 
126  auto it = pass.begin();
127  while (it != pass.end())
128  {
130  (*it)->analyzeFrame(frame, frameno, &nextFrame);
131 
132  if ((FrameAnalyzer::ANALYZE_OK == ares) ||
134  {
135  minNextFrame = std::min(minNextFrame, nextFrame);
136  ++it;
137  }
138  else if (ares == FrameAnalyzer::ANALYZE_FINISHED)
139  {
140  finishedAnalyzers.push_back(*it);
141  it = pass.erase(it);
142  }
143  else
144  {
145  if (ares != FrameAnalyzer::ANALYZE_FATAL)
146  {
147  LOG(VB_GENERAL, LOG_ERR,
148  QString("Unexpected return value from %1::analyzeFrame: %2")
149  .arg((*it)->name()).arg(ares));
150  }
151 
152  deadAnalyzers.push_back(*it);
153  it = pass.erase(it);
154  }
155  }
156 
157  if (minNextFrame == FrameAnalyzer::ANYFRAME)
158  minNextFrame = FrameAnalyzer::NEXTFRAME;
159 
160  if (minNextFrame == FrameAnalyzer::NEXTFRAME)
161  minNextFrame = frameno + 1;
162 
163  return minNextFrame;
164 }
165 
166 int passFinished(FrameAnalyzerItem &pass, long long nframes, bool final)
167 {
168  auto it = pass.begin();
169  for (; it != pass.end(); ++it)
170  (void)(*it)->finished(nframes, final);
171 
172  return 0;
173 }
174 
175 int passReportTime(const FrameAnalyzerItem &pass)
176 {
177  auto it = pass.cbegin();
178  for (; it != pass.cend(); ++it)
179  (void)(*it)->reportTime();
180 
181  return 0;
182 }
183 
184 bool searchingForLogo(TemplateFinder *tf, const FrameAnalyzerItem &pass)
185 {
186  if (!tf)
187  return false;
188 
189  auto it = std::find(pass.cbegin(), pass.cend(), tf);
190  return it != pass.end();
191 }
192 
193 }; // namespace
194 
195 namespace commDetector2 {
196 
197 QString debugDirectory(int chanid, const QDateTime& recstartts)
198 {
199  /*
200  * Should deleting a recording also delete its associated debug directory?
201  */
202  MSqlQuery query(MSqlQuery::InitCon());
203  query.prepare("SELECT basename"
204  " FROM recorded"
205  " WHERE chanid = :CHANID"
206  " AND starttime = :STARTTIME"
207  ";");
208  query.bindValue(":CHANID", chanid);
209  query.bindValue(":STARTTIME", recstartts);
210  if (!query.exec() || !query.next())
211  {
212  MythDB::DBError("Error in CommDetector2::CommDetector2", query);
213  return "";
214  }
215 
216  ProgramInfo pginfo(chanid, recstartts);
217 
218  if (!pginfo.GetChanID())
219  return "";
220 
221  QString pburl = pginfo.GetPlaybackURL(true);
222  if (!pburl.startsWith("/"))
223  return "";
224 
225  QString basename(query.value(0).toString());
226  QString m_debugdir = pburl.section('/',0,-2) + "/" + basename + "-debug";
227 
228  return m_debugdir;
229 }
230 
231 void createDebugDirectory(const QString& dirname, const QString& comment)
232 {
233  QDir qdir(dirname);
234  if (qdir.exists())
235  {
236  LOG(VB_COMMFLAG, LOG_INFO, QString("%1 using debug directory \"%2\"")
237  .arg(comment).arg(dirname));
238  }
239  else
240  {
241  if (qdir.mkdir(dirname))
242  {
243  LOG(VB_COMMFLAG, LOG_INFO,
244  QString("%1 created debug directory \"%1\"")
245  .arg(comment).arg(dirname));
246  }
247  else
248  {
249  LOG(VB_COMMFLAG, LOG_INFO, QString("%1 failed to create \"%2\": %3")
250  .arg(comment).arg(dirname).arg(strerror(errno)));
251  }
252  }
253 }
254 
255 QString frameToTimestamp(long long frameno, float fps)
256 {
257  int ms = (int)roundf(frameno / fps * 1000);
258 
259  int ss = ms / 1000;
260  ms %= 1000;
261  if (ms >= 500)
262  ss++;
263 
264  int mm = ss / 60;
265  ss %= 60;
266 
267  int hh = mm / 60;
268  mm %= 60;
269 
270  return QString("%1:%2:%3")
271  .arg(hh).arg(mm, 2, 10, QChar('0')) .arg(ss, 2, 10, QChar('0'));
272 }
273 
274 QString frameToTimestampms(long long frameno, float fps)
275 {
276  int ms = (int)roundf(frameno / fps * 1000);
277 
278  int ss = ms / 1000;
279  ms %= 1000;
280 
281  int mm = ss / 60;
282  ss %= 60;
283 
284  return QString("%1:%2:%3")
285  .arg(mm).arg(ss, 2, 10, QChar(QChar('0'))).arg(ms, 2, 10, QChar(QChar('0')));
286 }
287 
288 QString strftimeval(const struct timeval *tv)
289 {
290  return QString("%1.%2")
291  .arg(tv->tv_sec).arg(tv->tv_usec, 6, 10, QChar(QChar('0')));
292 }
293 
294 }; /* namespace */
295 
296 using namespace commDetector2;
297 
299  SkipType commDetectMethod_in,
300  bool showProgress_in,
301  bool fullSpeed_in,
302  MythPlayer *player_in,
303  int chanid,
304  QDateTime startts_in,
305  QDateTime endts_in,
306  QDateTime recstartts_in,
307  QDateTime recendts_in,
308  bool useDB) :
309  m_commDetectMethod((SkipType)(commDetectMethod_in & ~COMM_DETECT_2)),
310  m_showProgress(showProgress_in), m_fullSpeed(fullSpeed_in),
311  m_player(player_in),
312  m_startts(std::move(startts_in)), m_endts(std::move(endts_in)),
313  m_recstartts(std::move(recstartts_in)), m_recendts(std::move(recendts_in)),
314  m_isRecording(MythDate::current() < m_recendts),
315  m_debugdir("")
316 {
317  FrameAnalyzerItem pass0, 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, tcol = 0, twidth = 0, theight = 0;
464 
465  m_breaks.clear();
466 
467  TemplateMatcher *matcher = nullptr;
468  if (m_logoFinder && m_logoFinder->getTemplate(&trow, &tcol, &twidth, &theight))
469  matcher = m_logoMatcher;
470 
471  if (matcher && m_blankFrameDetector)
472  {
473  int cmp = matcher->templateCoverage(nframes, m_finished);
474  if (cmp == 0)
475  {
476  if (matcher->adjustForBlanks(m_blankFrameDetector, nframes))
477  return -1;
478  if (matcher->computeBreaks(&m_breaks))
479  return -1;
480  }
481  else
482  {
483  if (cmp > 0 &&
485  return -1;
486  if (cmp < 0 &&
488  return -1;
489 
491  return -1;
492  }
493  }
494  else if (matcher)
495  {
496  if (matcher->computeBreaks(&m_breaks))
497  return -1;
498  }
499  else if (m_blankFrameDetector)
500  {
502  return -1;
503  }
504 
505  return 0;
506 }
507 
509 {
510  int minlag = 7; // seconds
511 
512  if (m_player->OpenFile() < 0)
513  return false;
514 
515  if (!m_player->InitVideo())
516  {
517  LOG(VB_GENERAL, LOG_ERR,
518  "NVP: Unable to initialize video for FlagCommercials.");
519  return false;
520  }
521 
522  m_player->EnableSubtitles(false);
523 
524  QTime totalFlagTime;
525  totalFlagTime.start();
526 
527  /* If still recording, estimate the eventual total number of frames. */
528  long long nframes = m_isRecording ?
529  (long long)roundf((m_recstartts.secsTo(m_recendts) + 5) *
530  m_player->GetFrameRate()) :
532  bool postprocessing = !m_isRecording;
533 
534  if (m_showProgress)
535  {
536  if (nframes)
537  cerr << " 0%/ ";
538  else
539  cerr << " 0/ ";
540  cerr.flush();
541  }
542 
543  frm_dir_map_t lastBreakMap;
544  unsigned int passno = 0;
545  unsigned int npasses = m_frameAnalyzers.size();
546  for (m_currentPass = m_frameAnalyzers.begin();
548  ++m_currentPass, passno++)
549  {
550  FrameAnalyzerItem deadAnalyzers;
551 
552  LOG(VB_COMMFLAG, LOG_INFO,
553  QString("CommDetector2::go pass %1 of %2 (%3 frames, %4 fps)")
554  .arg(passno + 1).arg(npasses)
556  .arg(m_player->GetFrameRate(), 0, 'f', 2));
557 
558  if (!MythPlayerInited(
559  *m_currentPass, m_finishedAnalyzers, deadAnalyzers, m_player, nframes))
560  {
561  return false;
562  }
563 
565  long long nextFrame = -1;
567  long long lastLoggedFrame = m_currentFrameNumber;
568  QTime passTime, clock;
569  struct timeval getframetime {};
570 
572 
573  if (searchingForLogo(m_logoFinder, *m_currentPass))
574  emit statusUpdate(QCoreApplication::translate("(mythcommflag)",
575  "Performing Logo Identification"));
576 
577  clock.start();
578  passTime.start();
579  memset(&getframetime, 0, sizeof(getframetime));
580  while (!(*m_currentPass).empty() && m_player->GetEof() == kEofStateNone)
581  {
582  struct timeval start {}, end {}, elapsedtv {};
583 
584  (void)gettimeofday(&start, nullptr);
585  bool fetchNext = (nextFrame == m_currentFrameNumber + 1);
586  VideoFrame *currentFrame =
587  m_player->GetRawVideoFrame(fetchNext ? -1 : nextFrame);
588  long long lastFrameNumber = m_currentFrameNumber;
589  m_currentFrameNumber = currentFrame->frameNumber + 1;
590  (void)gettimeofday(&end, nullptr);
591  timersub(&end, &start, &elapsedtv);
592  timeradd(&getframetime, &elapsedtv, &getframetime);
593 
594  if (nextFrame != -1 && nextFrame == lastFrameNumber + 1 &&
595  m_currentFrameNumber != nextFrame)
596  {
597  /*
598  * Don't log "Jumped" when we know we're skipping frames (e.g.,
599  * logo detection).
600  */
601  LOG(VB_COMMFLAG, LOG_INFO,
602  QString("Jumped from frame %1 to frame %2")
603  .arg(lastFrameNumber).arg(m_currentFrameNumber));
604  }
605 
606  if (stopForBreath(m_isRecording, m_currentFrameNumber))
607  {
608  emit breathe();
609  if (m_bStop)
610  {
611  m_player->DiscardVideoFrame(currentFrame);
612  return false;
613  }
614  }
615 
616  while (m_bPaused)
617  {
618  emit breathe();
619  std::this_thread::sleep_for(std::chrono::seconds(1));
620  }
621 
622  if (!searchingForLogo(m_logoFinder, *m_currentPass) &&
623  needToReportState(m_showProgress, m_isRecording,
625  {
626  reportState(passTime.elapsed(), m_currentFrameNumber,
627  nframes, passno, npasses);
628  }
629 
630  nextFrame = processFrame(
632  deadAnalyzers, currentFrame, m_currentFrameNumber);
633 
634  if (((m_currentFrameNumber >= 1) && (nframes > 0) &&
635  (((nextFrame * 10) / nframes) !=
636  ((m_currentFrameNumber * 10) / nframes))) ||
637  (nextFrame >= nframes))
638  {
639  /* Log something every 10%. */
640  int elapsed = clock.restart();
641  LOG(VB_COMMFLAG, LOG_INFO,
642  QString("processFrame %1 of %2 (%3%) - %4 fps")
644  .arg(nframes)
645  .arg((nframes == 0) ? 0 :
646  (int)roundf(m_currentFrameNumber * 100.0 / nframes))
647  .arg((m_currentFrameNumber - lastLoggedFrame) * 1000 /
648  (elapsed ? elapsed : 1)));
649  lastLoggedFrame = m_currentFrameNumber;
650  }
651 
652  if (m_isRecording)
653  {
654  waitForBuffer(&start, minlag,
655  m_recstartts.secsTo(MythDate::current()) -
656  totalFlagTime.elapsed() / 1000, m_player->GetFrameRate(),
657  m_fullSpeed);
658  }
659 
660  // sleep a little so we don't use all cpu even if we're niced
661  if (!m_fullSpeed && !m_isRecording)
662  std::this_thread::sleep_for(std::chrono::milliseconds(10));
663 
665  !(m_currentFrameNumber % 500)))
666  {
667  frm_dir_map_t breakMap;
668 
669  GetCommercialBreakList(breakMap);
670 
671  frm_dir_map_t::const_iterator ii, jj;
672  ii = breakMap.begin();
673  jj = lastBreakMap.begin();
674  while (ii != breakMap.end() && jj != lastBreakMap.end())
675  {
676  if (ii.key() != jj.key())
677  break;
678  if (*ii != *jj)
679  break;
680  ++ii;
681  ++jj;
682  }
683  bool same = ii == breakMap.end() && jj == lastBreakMap.end();
684  lastBreakMap = breakMap;
685 
686  if (m_breakMapUpdateRequested || !same)
688 
690  }
691 
692  m_player->DiscardVideoFrame(currentFrame);
693  }
694 
695  // Save total duration only on the last pass, which hopefully does
696  // no skipping.
697  if (passno + 1 == npasses)
699 
700  m_currentPass->insert(m_currentPass->end(),
701  m_finishedAnalyzers.begin(),
702  m_finishedAnalyzers.end());
703  m_finishedAnalyzers.clear();
704 
705  if (postprocessing)
707  if (passFinished(*m_currentPass, m_currentFrameNumber + 1, true))
708  return false;
709 
710  LOG(VB_COMMFLAG, LOG_INFO, QString("NVP Time: GetRawVideoFrame=%1s")
711  .arg(strftimeval(&getframetime)));
712  if (passReportTime(*m_currentPass))
713  return false;
714  }
715 
716  if (m_showProgress)
717  {
718  if (nframes)
719  cerr << "\b\b\b\b\b\b \b\b\b\b\b\b";
720  else
721  cerr << "\b\b\b\b\b\b\b\b\b\b\b\b\b "
722  "\b\b\b\b\b\b\b\b\b\b\b\b\b";
723  cerr.flush();
724  }
725 
726  m_finished = true;
727  return true;
728 }
729 
731 {
732  if (!m_finished)
733  {
734  for (auto pass = m_frameAnalyzers.begin();
735  pass != m_frameAnalyzers.end(); ++pass)
736  {
737  if (*pass == *m_currentPass &&
738  passFinished(m_finishedAnalyzers, m_currentFrameNumber + 1, false))
739  {
740  return;
741  }
742 
743  if (passFinished(*pass, m_currentFrameNumber + 1, false))
744  return;
745  }
746  }
747 
749  return;
750 
751  marks.clear();
752 
753  /* Create break list. */
754  long long breakframes = 0;
755  for (auto bb = m_breaks.begin(); bb != m_breaks.end(); ++bb)
756  {
757  long long segb = bb.key();
758  long long seglen = *bb;
759  long long sege = segb + seglen - 1;
760 
761  if (segb < sege)
762  {
763  marks[segb] = MARK_COMM_START;
764  marks[sege] = MARK_COMM_END;
765 
766  breakframes += seglen;
767  }
768  }
769 
770  /* Report results. */
771  const float fps = m_player->GetFrameRate();
772  for (frm_dir_map_t::const_iterator iimark = marks.begin();
773  iimark != marks.end();
774  ++iimark)
775  {
776  /* Display as 1-based frame numbers. */
777  long long markstart = iimark.key() + 1; /* MARK_COMM_BEGIN */
778  ++iimark; /* MARK_COMM_END */
779  if (iimark == marks.end())
780  break;
781  long long markend = iimark.key() + 1;
782 
783  LOG(VB_COMMFLAG, LOG_INFO, QString("Break: frame %1-%2 (%3-%4, %5)")
784  .arg(markstart, 6).arg(markend, 6)
785  .arg(frameToTimestamp(markstart, fps))
786  .arg(frameToTimestamp(markend, fps))
787  .arg(frameToTimestamp(markend - markstart + 1, fps)));
788  }
789 
790  const long long nframes = m_player->GetTotalFrameCount();
791  LOG(VB_COMMFLAG, LOG_INFO,
792  QString("Flagged %1 of %2 frames (%3 of %4), %5% commercials (%6)")
793  .arg(m_currentFrameNumber + 1).arg(nframes)
794  .arg(frameToTimestamp(m_currentFrameNumber + 1, fps))
795  .arg(frameToTimestamp(nframes, fps))
796  .arg(breakframes * 100 / m_currentFrameNumber)
797  .arg(frameToTimestamp(breakframes, fps)));
798 }
799 
800 void CommDetector2::recordingFinished(long long totalFileSize)
801 {
803  m_isRecording = false;
804  LOG(VB_COMMFLAG, LOG_INFO,
805  QString("CommDetector2::recordingFinished: %1 bytes")
806  .arg(totalFileSize));
807 }
808 
810 {
811  if (searchingForLogo(m_logoFinder, *m_currentPass))
812  {
813  LOG(VB_COMMFLAG, LOG_INFO, "Ignoring request for commBreakMapUpdate; "
814  "still doing logo detection");
815  return;
816  }
817 
818  LOG(VB_COMMFLAG, LOG_INFO,
819  QString("commBreakMapUpdate requested at frame %1")
820  .arg(m_currentFrameNumber + 1));
821  m_sendBreakMapUpdates = true;
823 }
824 
825 static void PrintReportMap(ostream &out,
826  const FrameAnalyzer::FrameMap &frameMap)
827 {
828  FrameAnalyzer::FrameMap::const_iterator it = frameMap.begin();
829  for (; it != frameMap.end(); ++it)
830  {
831  /*
832  * QMap'd as 0-based index, but display as 1-based index to match "Edit
833  * Recording" OSD.
834  */
835 
836  long long bb = it.key() + 1;
837  long long ee = (*it) ? (bb + *it) : 1;
838  QString tmp = QString("%1: %2").arg(bb, 10).arg(ee - 1, 10);
839 
840  out << qPrintable(tmp) << "\n";
841  }
842  out << flush;
843 }
844 
846  ostream &out, const frm_dir_map_t */*comm_breaks*/, bool /*verbose*/) const
847 {
848  FrameAnalyzer::FrameMap logoMap, blankMap, blankBreakMap, sceneMap;
849  if (m_logoFinder)
850  logoMap = m_logoFinder->GetMap(0);
851 
853  {
854  blankBreakMap = m_blankFrameDetector->GetMap(0);
855  blankMap = m_blankFrameDetector->GetMap(1);
856  }
857 
859  sceneMap = m_sceneChangeDetector->GetMap(0);
860 
861  out << "Logo Break Map" << endl;
862  PrintReportMap(out, logoMap);
863  out << "Blank Break Map" << endl;
864  PrintReportMap(out, blankBreakMap);
865  out << "Blank Map" << endl;
866  PrintReportMap(out, blankMap);
867  out << "Scene Break Map" << endl;
868  PrintReportMap(out, sceneMap);
869 }
870 
871 /* vim: set expandtab tabstop=4 shiftwidth=4: */
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:781
static const long long ANYFRAME
Definition: FrameAnalyzer.h:56
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:862
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 NEXTFRAME
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
virtual int OpenFile(uint retries=4)
Definition: mythplayer.cpp:754
Holds information on recordings and videos.
Definition: programinfo.h:66
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
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:190
EofState GetEof(void) const
SceneChangeDetector * m_sceneChangeDetector
Definition: CommDetector2.h:85
static MSqlQueryInfo InitCon(ConnectionReuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:534
void SaveTotalDuration(void)
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:806
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:959
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
Definition: programinfo.h:364
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:207
bool InitVideo(void)
Definition: mythplayer.cpp:395
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:602
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