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 <QDir>
13 #include <QFileInfo>
14 #include <QCoreApplication>
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  long sleepus;
55 
56  (void)gettimeofday(&now, nullptr);
57  timersub(&now, framestart, &elapsed);
58 
59  // Sleep for one frame's worth of time.
60  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  FrameAnalyzerItem::iterator 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;
125  long long minNextFrame = FrameAnalyzer::ANYFRAME;
126 
127  FrameAnalyzerItem::iterator 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::ANYFRAME)
159  minNextFrame = FrameAnalyzer::NEXTFRAME;
160 
161  if (minNextFrame == FrameAnalyzer::NEXTFRAME)
162  minNextFrame = frameno + 1;
163 
164  return minNextFrame;
165 }
166 
167 int passFinished(FrameAnalyzerItem &pass, long long nframes, bool final)
168 {
169  FrameAnalyzerItem::iterator it = pass.begin();
170  for (; it != pass.end(); ++it)
171  (void)(*it)->finished(nframes, final);
172 
173  return 0;
174 }
175 
176 int passReportTime(const FrameAnalyzerItem &pass)
177 {
178  FrameAnalyzerItem::const_iterator it = pass.begin();
179  for (; it != pass.end(); ++it)
180  (void)(*it)->reportTime();
181 
182  return 0;
183 }
184 
185 bool searchingForLogo(TemplateFinder *tf, const FrameAnalyzerItem &pass)
186 {
187  if (!tf)
188  return false;
189 
190  FrameAnalyzerItem::const_iterator it =
191  std::find(pass.begin(), pass.end(), tf);
192 
193  return it != pass.end();
194 }
195 
196 }; // namespace
197 
198 namespace commDetector2 {
199 
200 QString debugDirectory(int chanid, const QDateTime& recstartts)
201 {
202  /*
203  * Should deleting a recording also delete its associated debug directory?
204  */
205  MSqlQuery query(MSqlQuery::InitCon());
206  query.prepare("SELECT basename"
207  " FROM recorded"
208  " WHERE chanid = :CHANID"
209  " AND starttime = :STARTTIME"
210  ";");
211  query.bindValue(":CHANID", chanid);
212  query.bindValue(":STARTTIME", recstartts);
213  if (!query.exec() || !query.next())
214  {
215  MythDB::DBError("Error in CommDetector2::CommDetector2", query);
216  return "";
217  }
218 
219  const ProgramInfo pginfo(chanid, recstartts);
220 
221  if (!pginfo.GetChanID())
222  return "";
223 
224  QString pburl = pginfo.GetPlaybackURL(true);
225  if (!pburl.startsWith("/"))
226  return "";
227 
228  QString basename(query.value(0).toString());
229  QString m_debugdir = pburl.section('/',0,-2) + "/" + basename + "-debug";
230 
231  return m_debugdir;
232 }
233 
234 void createDebugDirectory(const QString& dirname, const QString& comment)
235 {
236  QDir qdir(dirname);
237  if (qdir.exists())
238  {
239  LOG(VB_COMMFLAG, LOG_INFO, QString("%1 using debug directory \"%2\"")
240  .arg(comment).arg(dirname));
241  }
242  else
243  {
244  if (qdir.mkdir(dirname))
245  {
246  LOG(VB_COMMFLAG, LOG_INFO,
247  QString("%1 created debug directory \"%1\"")
248  .arg(comment).arg(dirname));
249  }
250  else
251  {
252  LOG(VB_COMMFLAG, LOG_INFO, QString("%1 failed to create \"%2\": %3")
253  .arg(comment).arg(dirname).arg(strerror(errno)));
254  }
255  }
256 }
257 
258 QString frameToTimestamp(long long frameno, float fps)
259 {
260  int ms, ss, mm, hh;
261 
262  ms = (int)roundf(frameno / fps * 1000);
263 
264  ss = ms / 1000;
265  ms %= 1000;
266  if (ms >= 500)
267  ss++;
268 
269  mm = ss / 60;
270  ss %= 60;
271 
272  hh = mm / 60;
273  mm %= 60;
274 
275  return QString("%1:%2:%3")
276  .arg(hh).arg(mm, 2, 10, QChar('0')) .arg(ss, 2, 10, QChar('0'));
277 }
278 
279 QString frameToTimestampms(long long frameno, float fps)
280 {
281  int ms, ss, mm;
282 
283  ms = (int)roundf(frameno / fps * 1000);
284 
285  ss = ms / 1000;
286  ms %= 1000;
287 
288  mm = ss / 60;
289  ss %= 60;
290 
291  return QString("%1:%2:%3")
292  .arg(mm).arg(ss, 2, 10, QChar(QChar('0'))).arg(ms, 2, 10, QChar(QChar('0')));
293 }
294 
295 QString strftimeval(const struct timeval *tv)
296 {
297  return QString("%1.%2")
298  .arg(tv->tv_sec).arg(tv->tv_usec, 6, 10, QChar(QChar('0')));
299 }
300 
301 }; /* namespace */
302 
303 using namespace commDetector2;
304 
306  enum SkipTypes commDetectMethod_in,
307  bool showProgress_in,
308  bool fullSpeed_in,
309  MythPlayer *player_in,
310  int chanid,
311  const QDateTime &startts_in,
312  const QDateTime &endts_in,
313  const QDateTime &recstartts_in,
314  const QDateTime &recendts_in,
315  bool useDB) :
316  m_commDetectMethod((enum SkipTypes)(commDetectMethod_in & ~COMM_DETECT_2)),
317  m_showProgress(showProgress_in), m_fullSpeed(fullSpeed_in),
318  m_player(player_in),
319  m_startts(startts_in), m_endts(endts_in),
320  m_recstartts(recstartts_in), m_recendts(recendts_in),
321  m_isRecording(MythDate::current() < m_recendts),
322  m_debugdir("")
323 {
324  FrameAnalyzerItem pass0, pass1;
325  PGMConverter *pgmConverter = nullptr;
326  BorderDetector *borderDetector = nullptr;
327  HistogramAnalyzer *histogramAnalyzer = nullptr;
328 
329  if (useDB)
331 
332  /*
333  * Look for blank frames to use as delimiters between commercial and
334  * non-commercial segments.
335  */
337  {
338  if (!pgmConverter)
339  pgmConverter = new PGMConverter();
340 
341  if (!borderDetector)
342  borderDetector = new BorderDetector();
343 
344  if (!histogramAnalyzer)
345  {
346  histogramAnalyzer = new HistogramAnalyzer(pgmConverter,
347  borderDetector, m_debugdir);
348  }
349 
351  {
352  m_blankFrameDetector = new BlankFrameDetector(histogramAnalyzer,
353  m_debugdir);
354  pass1.push_back(m_blankFrameDetector);
355  }
356  }
357 
358  /*
359  * Look for "scene changes" to use as delimiters between commercial and
360  * non-commercial segments.
361  */
363  {
364  if (!pgmConverter)
365  pgmConverter = new PGMConverter();
366 
367  if (!borderDetector)
368  borderDetector = new BorderDetector();
369 
370  if (!histogramAnalyzer)
371  {
372  histogramAnalyzer = new HistogramAnalyzer(pgmConverter,
373  borderDetector, m_debugdir);
374  }
375 
377  {
378  m_sceneChangeDetector = new SceneChangeDetector(histogramAnalyzer,
379  m_debugdir);
380  pass1.push_back(m_sceneChangeDetector);
381  }
382  }
383 
384  /*
385  * Logo Detection requires two passes. The first pass looks for static
386  * areas of the screen to look for logos and generate a template. The
387  * second pass matches the template against the frame and computes the
388  * closeness of the match.
389  */
391  {
392  if (!pgmConverter)
393  pgmConverter = new PGMConverter();
394 
395  if (!borderDetector)
396  borderDetector = new BorderDetector();
397 
398  CannyEdgeDetector *cannyEdgeDetector = new CannyEdgeDetector();
399 
400  if (!m_logoFinder)
401  {
402  m_logoFinder = new TemplateFinder(pgmConverter, borderDetector,
403  cannyEdgeDetector, m_player, m_recstartts.secsTo(m_recendts),
404  m_debugdir);
405  pass0.push_back(m_logoFinder);
406  }
407 
408  if (!m_logoMatcher)
409  {
410  m_logoMatcher = new TemplateMatcher(pgmConverter, cannyEdgeDetector,
412  pass1.push_back(m_logoMatcher);
413  }
414  }
415 
416  if (histogramAnalyzer && m_logoFinder)
417  histogramAnalyzer->setLogoState(m_logoFinder);
418 
419  /* Aggregate them all together. */
420  m_frameAnalyzers.push_back(pass0);
421  m_frameAnalyzers.push_back(pass1);
422 }
423 
424 void CommDetector2::reportState(int elapsedms, long long frameno,
425  long long nframes, unsigned int passno, unsigned int npasses)
426 {
427  float fps = elapsedms ? (float)frameno * 1000 / elapsedms : 0;
428  static int prevpercent = -1;
429 
430  /* Assume that 0-th pass is negligible in terms of computational cost. */
431  int percentage = (passno == 0 || npasses == 1 || nframes == 0) ? 0 :
432  (passno - 1) * 100 / (npasses - 1) +
433  min((long long)100, (frameno * 100 / nframes) / (npasses - 1));
434 
435  if (m_showProgress)
436  {
437  QString tmp = "";
438 
439  if (nframes)
440  tmp = QString("\r%1%/ %2fps").arg(percentage,3).arg(fps,6,'f',2);
441  else
442  tmp = QString("\r%1/ %2fps").arg(frameno,6).arg(fps,6,'f',2);
443 
444  QByteArray tmp2 = tmp.toLocal8Bit();
445  cerr << tmp2.constData() << " \r";
446  cerr.flush();
447  }
448 
449  if (nframes)
450  {
451  emit statusUpdate(QCoreApplication::translate("(mythcommflag)",
452  "%1% Completed @ %2 fps.").arg(percentage).arg(fps));
453  }
454  else
455  {
456  emit statusUpdate(QCoreApplication::translate("(mythcommflag)",
457  "%1 Frames Completed @ %2 fps.").arg(frameno).arg(fps));
458  }
459 
460  if (percentage % 10 == 0 && prevpercent != percentage)
461  {
462  prevpercent = percentage;
463  LOG(VB_GENERAL, LOG_INFO, QString("%1%% Completed @ %2 fps.")
464  .arg(percentage) .arg(fps));
465  }
466 }
467 
468 int CommDetector2::computeBreaks(long long nframes)
469 {
470  int trow, tcol, twidth, theight;
471  TemplateMatcher *matcher;
472 
473  m_breaks.clear();
474 
475  matcher = m_logoFinder &&
476  m_logoFinder->getTemplate(&trow, &tcol, &twidth, &theight) ? m_logoMatcher :
477  nullptr;
478 
479  if (matcher && m_blankFrameDetector)
480  {
481  int cmp;
482  if (!(cmp = matcher->templateCoverage(nframes, m_finished)))
483  {
484  if (matcher->adjustForBlanks(m_blankFrameDetector, nframes))
485  return -1;
486  if (matcher->computeBreaks(&m_breaks))
487  return -1;
488  }
489  else
490  {
491  if (cmp > 0 &&
493  return -1;
494  if (cmp < 0 &&
496  return -1;
497 
499  return -1;
500  }
501  }
502  else if (matcher)
503  {
504  if (matcher->computeBreaks(&m_breaks))
505  return -1;
506  }
507  else if (m_blankFrameDetector)
508  {
510  return -1;
511  }
512 
513  return 0;
514 }
515 
517 {
518  int minlag = 7; // seconds
519 
520  if (m_player->OpenFile() < 0)
521  return false;
522 
523  if (!m_player->InitVideo())
524  {
525  LOG(VB_GENERAL, LOG_ERR,
526  "NVP: Unable to initialize video for FlagCommercials.");
527  return false;
528  }
529 
530  m_player->EnableSubtitles(false);
531 
532  QTime totalFlagTime;
533  totalFlagTime.start();
534 
535  /* If still recording, estimate the eventual total number of frames. */
536  long long nframes = m_isRecording ?
537  (long long)roundf((m_recstartts.secsTo(m_recendts) + 5) *
538  m_player->GetFrameRate()) :
540  bool postprocessing = !m_isRecording;
541 
542  if (m_showProgress)
543  {
544  if (nframes)
545  cerr << " 0%/ ";
546  else
547  cerr << " 0/ ";
548  cerr.flush();
549  }
550 
551  frm_dir_map_t lastBreakMap;
552  unsigned int passno = 0;
553  unsigned int npasses = m_frameAnalyzers.size();
554  for (m_currentPass = m_frameAnalyzers.begin();
556  ++m_currentPass, passno++)
557  {
558  FrameAnalyzerItem deadAnalyzers;
559 
560  LOG(VB_COMMFLAG, LOG_INFO,
561  QString("CommDetector2::go pass %1 of %2 (%3 frames, %4 fps)")
562  .arg(passno + 1).arg(npasses)
564  .arg(m_player->GetFrameRate(), 0, 'f', 2));
565 
566  if (!MythPlayerInited(
567  *m_currentPass, m_finishedAnalyzers, deadAnalyzers, m_player, nframes))
568  {
569  return false;
570  }
571 
573  long long nextFrame = -1;
575  long long lastLoggedFrame = m_currentFrameNumber;
576  QTime passTime, clock;
577  struct timeval getframetime;
578 
580 
581  if (searchingForLogo(m_logoFinder, *m_currentPass))
582  emit statusUpdate(QCoreApplication::translate("(mythcommflag)",
583  "Performing Logo Identification"));
584 
585  clock.start();
586  passTime.start();
587  memset(&getframetime, 0, sizeof(getframetime));
588  while (!(*m_currentPass).empty() && m_player->GetEof() == kEofStateNone)
589  {
590  struct timeval start, end, elapsedtv;
591 
592  (void)gettimeofday(&start, nullptr);
593  bool fetchNext = (nextFrame == m_currentFrameNumber + 1);
594  VideoFrame *currentFrame =
595  m_player->GetRawVideoFrame(fetchNext ? -1 : nextFrame);
596  long long lastFrameNumber = m_currentFrameNumber;
597  m_currentFrameNumber = currentFrame->frameNumber + 1;
598  (void)gettimeofday(&end, nullptr);
599  timersub(&end, &start, &elapsedtv);
600  timeradd(&getframetime, &elapsedtv, &getframetime);
601 
602  if (nextFrame != -1 && nextFrame == lastFrameNumber + 1 &&
603  m_currentFrameNumber != nextFrame)
604  {
605  /*
606  * Don't log "Jumped" when we know we're skipping frames (e.g.,
607  * logo detection).
608  */
609  LOG(VB_COMMFLAG, LOG_INFO,
610  QString("Jumped from frame %1 to frame %2")
611  .arg(lastFrameNumber).arg(m_currentFrameNumber));
612  }
613 
614  if (stopForBreath(m_isRecording, m_currentFrameNumber))
615  {
616  emit breathe();
617  if (m_bStop)
618  {
619  m_player->DiscardVideoFrame(currentFrame);
620  return false;
621  }
622  }
623 
624  while (m_bPaused)
625  {
626  emit breathe();
627  std::this_thread::sleep_for(std::chrono::seconds(1));
628  }
629 
630  if (!searchingForLogo(m_logoFinder, *m_currentPass) &&
631  needToReportState(m_showProgress, m_isRecording,
633  {
634  reportState(passTime.elapsed(), m_currentFrameNumber,
635  nframes, passno, npasses);
636  }
637 
638  nextFrame = processFrame(
640  deadAnalyzers, currentFrame, m_currentFrameNumber);
641 
642  if (((m_currentFrameNumber >= 1) && (nframes > 0) &&
643  (((nextFrame * 10) / nframes) !=
644  ((m_currentFrameNumber * 10) / nframes))) ||
645  (nextFrame >= nframes))
646  {
647  /* Log something every 10%. */
648  int elapsed = clock.restart();
649  LOG(VB_COMMFLAG, LOG_INFO,
650  QString("processFrame %1 of %2 (%3%) - %4 fps")
652  .arg(nframes)
653  .arg((nframes == 0) ? 0 :
654  (int)roundf(m_currentFrameNumber * 100.0 / nframes))
655  .arg((m_currentFrameNumber - lastLoggedFrame) * 1000 /
656  (elapsed ? elapsed : 1)));
657  lastLoggedFrame = m_currentFrameNumber;
658  }
659 
660  if (m_isRecording)
661  {
662  waitForBuffer(&start, minlag,
663  m_recstartts.secsTo(MythDate::current()) -
664  totalFlagTime.elapsed() / 1000, m_player->GetFrameRate(),
665  m_fullSpeed);
666  }
667 
668  // sleep a little so we don't use all cpu even if we're niced
669  if (!m_fullSpeed && !m_isRecording)
670  std::this_thread::sleep_for(std::chrono::milliseconds(10));
671 
673  !(m_currentFrameNumber % 500)))
674  {
675  frm_dir_map_t breakMap;
676 
677  GetCommercialBreakList(breakMap);
678 
679  frm_dir_map_t::const_iterator ii, jj;
680  ii = breakMap.begin();
681  jj = lastBreakMap.begin();
682  while (ii != breakMap.end() && jj != lastBreakMap.end())
683  {
684  if (ii.key() != jj.key())
685  break;
686  if (*ii != *jj)
687  break;
688  ++ii;
689  ++jj;
690  }
691  bool same = ii == breakMap.end() && jj == lastBreakMap.end();
692  lastBreakMap = breakMap;
693 
694  if (m_breakMapUpdateRequested || !same)
696 
698  }
699 
700  m_player->DiscardVideoFrame(currentFrame);
701  }
702 
703  // Save total duration only on the last pass, which hopefully does
704  // no skipping.
705  if (passno + 1 == npasses)
707 
708  m_currentPass->insert(m_currentPass->end(),
709  m_finishedAnalyzers.begin(),
710  m_finishedAnalyzers.end());
711  m_finishedAnalyzers.clear();
712 
713  if (postprocessing)
715  if (passFinished(*m_currentPass, m_currentFrameNumber + 1, true))
716  return false;
717 
718  LOG(VB_COMMFLAG, LOG_INFO, QString("NVP Time: GetRawVideoFrame=%1s")
719  .arg(strftimeval(&getframetime)));
720  if (passReportTime(*m_currentPass))
721  return false;
722  }
723 
724  if (m_showProgress)
725  {
726  if (nframes)
727  cerr << "\b\b\b\b\b\b \b\b\b\b\b\b";
728  else
729  cerr << "\b\b\b\b\b\b\b\b\b\b\b\b\b "
730  "\b\b\b\b\b\b\b\b\b\b\b\b\b";
731  cerr.flush();
732  }
733 
734  m_finished = true;
735  return true;
736 }
737 
739 {
740  if (!m_finished)
741  {
742  for (auto pass = m_frameAnalyzers.begin();
743  pass != m_frameAnalyzers.end(); ++pass)
744  {
745  if (*pass == *m_currentPass &&
746  passFinished(m_finishedAnalyzers, m_currentFrameNumber + 1, false))
747  {
748  return;
749  }
750 
751  if (passFinished(*pass, m_currentFrameNumber + 1, false))
752  return;
753  }
754  }
755 
757  return;
758 
759  marks.clear();
760 
761  /* Create break list. */
762  long long breakframes = 0;
763  for (auto bb = m_breaks.begin(); bb != m_breaks.end(); ++bb)
764  {
765  long long segb = bb.key();
766  long long seglen = *bb;
767  long long sege = segb + seglen - 1;
768 
769  if (segb < sege)
770  {
771  marks[segb] = MARK_COMM_START;
772  marks[sege] = MARK_COMM_END;
773 
774  breakframes += seglen;
775  }
776  }
777 
778  /* Report results. */
779  const float fps = m_player->GetFrameRate();
780  for (frm_dir_map_t::const_iterator iimark = marks.begin();
781  iimark != marks.end();
782  ++iimark)
783  {
784  long long markstart, markend;
785 
786  /* Display as 1-based frame numbers. */
787  markstart = iimark.key() + 1; /* MARK_COMM_BEGIN */
788  ++iimark; /* MARK_COMM_END */
789  if (iimark == marks.end())
790  break;
791  markend = iimark.key() + 1;
792 
793  LOG(VB_COMMFLAG, LOG_INFO, QString("Break: frame %1-%2 (%3-%4, %5)")
794  .arg(markstart, 6).arg(markend, 6)
795  .arg(frameToTimestamp(markstart, fps))
796  .arg(frameToTimestamp(markend, fps))
797  .arg(frameToTimestamp(markend - markstart + 1, fps)));
798  }
799 
800  const long long nframes = m_player->GetTotalFrameCount();
801  LOG(VB_COMMFLAG, LOG_INFO,
802  QString("Flagged %1 of %2 frames (%3 of %4), %5% commercials (%6)")
803  .arg(m_currentFrameNumber + 1).arg(nframes)
804  .arg(frameToTimestamp(m_currentFrameNumber + 1, fps))
805  .arg(frameToTimestamp(nframes, fps))
806  .arg(breakframes * 100 / m_currentFrameNumber)
807  .arg(frameToTimestamp(breakframes, fps)));
808 }
809 
810 void CommDetector2::recordingFinished(long long totalFileSize)
811 {
813  m_isRecording = false;
814  LOG(VB_COMMFLAG, LOG_INFO,
815  QString("CommDetector2::recordingFinished: %1 bytes")
816  .arg(totalFileSize));
817 }
818 
820 {
821  if (searchingForLogo(m_logoFinder, *m_currentPass))
822  {
823  LOG(VB_COMMFLAG, LOG_INFO, "Ignoring request for commBreakMapUpdate; "
824  "still doing logo detection");
825  return;
826  }
827 
828  LOG(VB_COMMFLAG, LOG_INFO,
829  QString("commBreakMapUpdate requested at frame %1")
830  .arg(m_currentFrameNumber + 1));
831  m_sendBreakMapUpdates = true;
833 }
834 
835 static void PrintReportMap(ostream &out,
836  const FrameAnalyzer::FrameMap &frameMap)
837 {
838  FrameAnalyzer::FrameMap::const_iterator it = frameMap.begin();
839  for (; it != frameMap.end(); ++it)
840  {
841  /*
842  * QMap'd as 0-based index, but display as 1-based index to match "Edit
843  * Recording" OSD.
844  */
845 
846  long long bb = it.key() + 1;
847  long long ee = (*it) ? (bb + *it) : 1;
848  QString tmp = QString("%1: %2").arg(bb, 10).arg(ee - 1, 10);
849 
850  out << qPrintable(tmp) << "\n";
851  }
852  out << flush;
853 }
854 
856  ostream &out, const frm_dir_map_t */*comm_breaks*/, bool /*verbose*/) const
857 {
858  FrameAnalyzer::FrameMap logoMap, blankMap, blankBreakMap, sceneMap;
859  if (m_logoFinder)
860  logoMap = m_logoFinder->GetMap(0);
861 
863  {
864  blankBreakMap = m_blankFrameDetector->GetMap(0);
865  blankMap = m_blankFrameDetector->GetMap(1);
866  }
867 
869  sceneMap = m_sceneChangeDetector->GetMap(0);
870 
871  out << "Logo Break Map" << endl;
872  PrintReportMap(out, logoMap);
873  out << "Blank Break Map" << endl;
874  PrintReportMap(out, blankBreakMap);
875  out << "Blank Map" << endl;
876  PrintReportMap(out, blankMap);
877  out << "Scene Break Map" << endl;
878  PrintReportMap(out, sceneMap);
879 }
880 
881 /* vim: set expandtab tabstop=4 shiftwidth=4: */
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:782
CommDetector2(SkipType commDetectMethod, bool showProgress, bool fullSpeed, MythPlayer *player, int chanid, const QDateTime &startts, const QDateTime &endts, const QDateTime &recstartts, const QDateTime &recendts, bool useDB)
SkipTypes
This is used as a bitmask.
Definition: programtypes.h:91
static const long long ANYFRAME
Definition: FrameAnalyzer.h:57
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:863
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)
vector< FrameAnalyzer * > FrameAnalyzerItem
Definition: CommDetector2.h:32
#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:58
QString m_debugdir
Definition: CommDetector2.h:87
QMap< uint64_t, MarkTypes > frm_dir_map_t
Frame # -> Mark map.
Definition: programtypes.h:81
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)
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:933
Holds information on recordings and videos.
Definition: programinfo.h:66
QDateTime m_recendts
Definition: CommDetector2.h:67
QString debugDirectory(int chanid, const QDateTime &recstartts)
void GetCommercialBreakList(frm_dir_map_t &marks) override
int computeBreaks(FrameMap *breaks)
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:176
EofState GetEof(void) const
SceneChangeDetector * m_sceneChangeDetector
Definition: CommDetector2.h:85
long long frameNumber
Definition: mythframe.h:48
static MSqlQueryInfo InitCon(ConnectionReuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:535
void SaveTotalDuration(void)
enum SkipTypes m_commDetectMethod
Definition: CommDetector2.h:60
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
void recordingFinished(long long totalFileSize) override
void statusUpdate(const QString &a)
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:807
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.
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 GetPlaybackURL(bool checkMaster=false, bool forceCheckLocal=false) const
Returns filename or URL to be used to play back this recording.
QString frameToTimestampms(long long frameno, float fps)
QString strftimeval(const struct timeval *tv)
uint64_t GetTotalFrameCount(void) const
Definition: mythplayer.h:194
bool InitVideo(void)
Definition: mythplayer.cpp:497
int computeForLogoDeficit(const TemplateMatcher *templateMatcher)
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:603
QMap< long long, long long > FrameMap
Definition: FrameAnalyzer.h:44
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)