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