MythTV  master
ClassicCommDetector.cpp
Go to the documentation of this file.
1 // POSIX headers
2 #include <sys/time.h> // for gettimeofday
3 
4 // ANSI C headers
5 #include <cmath>
6 
7 // C++ headers
8 #include <algorithm> // for min/max
9 #include <iostream> // for cerr
10 #include <chrono> // for milliseconds
11 #include <thread> // for sleep_for
12 
13 using namespace std;
14 
15 // Qt headers
16 #include <QCoreApplication>
17 #include <QString>
18 
19 // MythTV headers
20 #include "mythmiscutil.h"
21 #include "mythcontext.h"
22 #include "programinfo.h"
23 #include "mythplayer.h"
24 
25 // Commercial Flagging headers
26 #include "ClassicCommDetector.h"
27 #include "ClassicLogoDetector.h"
29 
33 } FrameAspects;
34 
35 // letter-box and pillar-box are not mutually exclusive
36 // So 3 is a valid value = (COMM_FORMAT_LETTERBOX | COMM_FORMAT_PILLARBOX)
37 // And 4 = COMM_FORMAT_MAX is the number of valid values.
43 } FrameFormats;
44 
45 static QString toStringFrameMaskValues(int mask, bool verbose)
46 {
47  QString msg;
48 
49  if (verbose)
50  {
51  if (COMM_FRAME_SKIPPED & mask)
52  msg += "skipped,";
53  if (COMM_FRAME_BLANK & mask)
54  msg += "blank,";
55  if (COMM_FRAME_SCENE_CHANGE & mask)
56  msg += "scene,";
57  if (COMM_FRAME_LOGO_PRESENT & mask)
58  msg += "logo,";
59  if (COMM_FRAME_ASPECT_CHANGE & mask)
60  msg += "aspect,";
61  if (COMM_FRAME_RATING_SYMBOL & mask)
62  msg += "rating,";
63 
64  if (msg.length())
65  msg = msg.left(msg.length() - 1);
66  else
67  msg = "noflags";
68  }
69  else
70  {
71  msg += (COMM_FRAME_SKIPPED & mask) ? "s" : " ";
72  msg += (COMM_FRAME_BLANK & mask) ? "B" : " ";
73  msg += (COMM_FRAME_SCENE_CHANGE & mask) ? "S" : " ";
74  msg += (COMM_FRAME_LOGO_PRESENT & mask) ? "L" : " ";
75  msg += (COMM_FRAME_ASPECT_CHANGE & mask) ? "A" : " ";
76  msg += (COMM_FRAME_RATING_SYMBOL & mask) ? "R" : " ";
77  }
78 
79  return msg;
80 }
81 
82 static QString toStringFrameAspects(int aspect, bool verbose)
83 {
84  if (verbose)
85  return (COMM_ASPECT_NORMAL == aspect) ? "normal" : " wide ";
86  return (COMM_ASPECT_NORMAL == aspect) ? "n" : "w";
87 }
88 
89 static QString toStringFrameFormats(int format, bool verbose)
90 {
91  switch (format)
92  {
93  case COMM_FORMAT_NORMAL:
94  return (verbose) ? "normal" : " N ";
96  return (verbose) ? "letter" : " L ";
98  return (verbose) ? "pillar" : " P ";
100  return (verbose) ? "letter,pillar" : "L,P";
101  case COMM_FORMAT_MAX:
102  return (verbose) ? " max " : " M ";
103  }
104 
105  return (verbose) ? "unknown" : " U ";
106 }
107 
109 {
110  return QString(" frame min/max/avg scene aspect format flags");
111 }
112 
113 QString FrameInfoEntry::toString(uint64_t frame, bool verbose) const
114 {
115  return QString(
116  "%1: %2/%3/%4 %5% %6 %7 %8")
117  .arg(frame,10)
118  .arg(minBrightness,3)
119  .arg(maxBrightness,3)
120  .arg(avgBrightness,3)
121  .arg(sceneChangePercent,3)
122  .arg(toStringFrameAspects(aspect, verbose))
123  .arg(toStringFrameFormats(format, verbose))
124  .arg(toStringFrameMaskValues(flagMask, verbose));
125 }
126 
128  bool showProgress_in,
129  bool fullSpeed_in,
130  MythPlayer* player_in,
131  QDateTime startedAt_in,
132  QDateTime stopsAt_in,
133  QDateTime recordingStartedAt_in,
134  QDateTime recordingStopsAt_in) :
135 
136 
137  m_commDetectMethod(commDetectMethod_in),
138  m_player(player_in),
139  m_startedAt(std::move(startedAt_in)),
140  m_stopsAt(std::move(stopsAt_in)),
141  m_recordingStartedAt(std::move(recordingStartedAt_in)),
142  m_recordingStopsAt(std::move(recordingStopsAt_in)),
143  m_stillRecording(m_recordingStopsAt > MythDate::current()),
144  m_fullSpeed(fullSpeed_in),
145  m_showProgress(showProgress_in)
146 {
148  gCoreContext->GetNumSetting("CommDetectBlankFrameMaxDiff", 25);
150  gCoreContext->GetNumSetting("CommDetectDarkBrightness", 80);
152  gCoreContext->GetNumSetting("CommDetectDimBrightness", 120);
154  gCoreContext->GetNumSetting("CommDetectBoxBrightness", 30);
156  gCoreContext->GetNumSetting("CommDetectDimAverage", 35);
158  gCoreContext->GetNumSetting("CommDetectMaxCommBreakLength", 395);
160  gCoreContext->GetNumSetting("CommDetectMinCommBreakLength", 60);
162  gCoreContext->GetNumSetting("CommDetectMinShowLength", 65);
164  gCoreContext->GetNumSetting("CommDetectMaxCommLength", 125);
165 
167  !!gCoreContext->GetBoolSetting("CommDetectBlankCanHaveLogo", true);
168 }
169 
171 {
172  QSize video_disp_dim = m_player->GetVideoSize();
173  m_width = video_disp_dim.width();
174  m_height = video_disp_dim.height();
176 
177  m_preRoll = (long long)(
178  max(int64_t(0), int64_t(m_recordingStartedAt.secsTo(m_startedAt))) * m_fps);
179  m_postRoll = (long long)(
180  max(int64_t(0), int64_t(m_stopsAt.secsTo(m_recordingStopsAt))) * m_fps);
181 
182  // CommDetectBorder's default value of 20 predates the change to use
183  // ffmpeg's lowres decoding capability by 5 years.
184  // I believe it should be adjusted based on the height of the lowres video
185  // CommDetectBorder * height / 720 seems to produce reasonable results.
186  // source height = 480 gives border = 20 * 480 / 4 / 720 = 2
187  // source height = 720 gives border = 20 * 720 / 4 / 720 = 5
188  // source height = 1080 gives border = 20 * 1080 / 4 / 720 = 7
190  gCoreContext->GetNumSetting("CommDetectBorder", 20) * m_height / 720;
191 
192 #ifdef SHOW_DEBUG_WIN
193  comm_debug_init(m_width, m_height);
194 #endif
195 
197 
198  m_lastFrameNumber = -2;
199  m_curFrameNumber = -1;
200  m_verboseDebugging = (getenv("DEBUGCOMMFLAG") != nullptr);
201 
202  LOG(VB_COMMFLAG, LOG_INFO,
203  QString("Commercial Detection initialized: "
204  "width = %1, height = %2, fps = %3, method = %4")
205  .arg(m_width).arg(m_height)
207 
208  if ((m_width * m_height) > 1000000)
209  {
210  m_horizSpacing = 10;
211  m_vertSpacing = 10;
212  }
213  else if ((m_width * m_height) > 800000)
214  {
215  m_horizSpacing = 8;
216  m_vertSpacing = 8;
217  }
218  else if ((m_width * m_height) > 400000)
219  {
220  m_horizSpacing = 6;
221  m_vertSpacing = 6;
222  }
223  else if ((m_width * m_height) > 300000)
224  {
225  m_horizSpacing = 6;
226  m_vertSpacing = 4;
227  }
228  else
229  {
230  m_horizSpacing = 4;
231  m_vertSpacing = 4;
232  }
233 
234  LOG(VB_COMMFLAG, LOG_INFO,
235  QString("Using Sample Spacing of %1 horizontal & %2 vertical pixels.")
236  .arg(m_horizSpacing).arg(m_vertSpacing));
237 
238  m_framesProcessed = 0;
240  m_blankFrameCount = 0;
241 
242  m_aggressiveDetection = true;
245 
246  m_lastSentCommBreakMap.clear();
247 
248  // Check if close to 4:3
249  if (fabs(((m_width*1.0)/m_height) - 1.333333) < 0.1)
251 
254  connect(
256  SIGNAL(haveNewInformation(unsigned int,bool,float)),
257  this,
258  SLOT(sceneChangeDetectorHasNewInformation(unsigned int,bool,float))
259  );
260 
261  m_frameIsBlank = false;
262  m_stationLogoPresent = false;
263 
264  m_logoInfoAvailable = false;
265 
266  ClearAllMaps();
267 
268  if (m_verboseDebugging)
269  {
270  LOG(VB_COMMFLAG, LOG_DEBUG,
271  " Fr # Min Max Avg Scn F A Mask");
272  LOG(VB_COMMFLAG, LOG_DEBUG,
273  " ------ --- --- --- --- - - ----");
274  }
275 }
276 
278 {
280  m_sceneChangeDetector->deleteLater();
281 
282  if (m_logoDetector)
283  m_logoDetector->deleteLater();
284 
285  CommDetectorBase::deleteLater();
286 }
287 
289 {
290  int secsSince = 0;
291  int requiredBuffer = 30;
292  int requiredHeadStart = requiredBuffer;
293  bool wereRecording = m_stillRecording;
294 
295  emit statusUpdate(QCoreApplication::translate("(mythcommflag)",
296  "Building Head Start Buffer"));
297  secsSince = m_recordingStartedAt.secsTo(MythDate::current());
298  while (m_stillRecording && (secsSince < requiredHeadStart))
299  {
300  emit breathe();
301  if (m_bStop)
302  return false;
303 
304  std::this_thread::sleep_for(std::chrono::seconds(2));
305  secsSince = m_recordingStartedAt.secsTo(MythDate::current());
306  }
307 
308  if (m_player->OpenFile() < 0)
309  return false;
310 
311  Init();
312 
314  {
315  // Use a different border for logo detection.
316  // If we try to detect logos in letterboxed areas,
317  // chances are we won't detect the logo.
318  // Generally speaking, SD video is likely to be letter boxed
319  // and HD video is not likely to be letter boxed.
320  // To detect logos, try to exclude letterboxed area from SD video
321  // but exclude too much from HD video and you'll miss the logo.
322  // Using the same border for both with no scaling seems to be
323  // a good compromise.
324  int logoDetectBorder =
325  gCoreContext->GetNumSetting("CommDetectLogoBorder", 16);
327  logoDetectBorder);
328 
329  requiredHeadStart += max(
330  int64_t(0), int64_t(m_recordingStartedAt.secsTo(m_startedAt)));
331  requiredHeadStart += m_logoDetector->getRequiredAvailableBufferForSearch();
332 
333  emit statusUpdate(QCoreApplication::translate("(mythcommflag)",
334  "Building Logo Detection Buffer"));
335  secsSince = m_recordingStartedAt.secsTo(MythDate::current());
336  while (m_stillRecording && (secsSince < requiredHeadStart))
337  {
338  emit breathe();
339  if (m_bStop)
340  return false;
341 
342  std::this_thread::sleep_for(std::chrono::seconds(2));
343  secsSince = m_recordingStartedAt.secsTo(MythDate::current());
344  }
345  }
346 
347  // Don't bother flagging short ~realtime recordings
348  if ((wereRecording) && (!m_stillRecording) && (secsSince < requiredHeadStart))
349  return false;
350 
352  gCoreContext->GetBoolSetting("AggressiveCommDetect", true);
353 
354  if (!m_player->InitVideo())
355  {
356  LOG(VB_GENERAL, LOG_ERR,
357  "NVP: Unable to initialize video for FlagCommercials.");
358  return false;
359  }
360  m_player->EnableSubtitles(false);
361 
363  {
364  emit statusUpdate(QCoreApplication::translate("(mythcommflag)",
365  "Searching for Logo"));
366 
367  if (m_showProgress)
368  {
369  cerr << "Finding Logo";
370  cerr.flush();
371  }
372  LOG(VB_GENERAL, LOG_INFO, "Finding Logo");
373 
375 
376  if (m_showProgress)
377  {
378  cerr << "\b\b\b\b\b\b\b\b\b\b\b\b "
379  "\b\b\b\b\b\b\b\b\b\b\b\b";
380  cerr.flush();
381  }
382  }
383 
384  emit breathe();
385  if (m_bStop)
386  return false;
387 
388  QElapsedTimer flagTime;
389  flagTime.start();
390 
391  long long myTotalFrames = 0;
393  myTotalFrames = m_player->GetTotalFrameCount();
394  else
395  myTotalFrames = (long long)(m_player->GetFrameRate() *
397 
398  if (m_showProgress)
399  {
400  if (myTotalFrames)
401  cerr << "\r 0%/ \r" << flush;
402  else
403  cerr << "\r 0/ \r" << flush;
404  }
405 
406 
407  float flagFPS = 0.0;
408  long long currentFrameNumber = 0LL;
409  float aspect = m_player->GetVideoAspect();
410  int prevpercent = -1;
411 
412  SetVideoParams(aspect);
413 
414  emit breathe();
415 
417 
418  while (m_player->GetEof() == kEofStateNone)
419  {
420  struct timeval startTime {};
421  if (m_stillRecording)
422  gettimeofday(&startTime, nullptr);
423 
424  VideoFrame* currentFrame = m_player->GetRawVideoFrame();
425  currentFrameNumber = currentFrame->frameNumber;
426 
427  //Lucas: maybe we should make the nuppelvideoplayer send out a signal
428  //when the aspect ratio changes.
429  //In order to not change too many things at a time, I"m using basic
430  //polling for now.
431  float newAspect = currentFrame->aspect;
432  if (newAspect != aspect)
433  {
434  SetVideoParams(aspect);
435  aspect = newAspect;
436  }
437 
438  if (((currentFrameNumber % 500) == 0) ||
439  (((currentFrameNumber % 100) == 0) &&
440  (m_stillRecording)))
441  {
442  emit breathe();
443  if (m_bStop)
444  {
445  m_player->DiscardVideoFrame(currentFrame);
446  return false;
447  }
448  }
449 
452  ((currentFrameNumber % 500) == 0)))
453  {
454  frm_dir_map_t commBreakMap;
455  frm_dir_map_t::iterator it;
456  frm_dir_map_t::iterator lastIt;
457  bool mapsAreIdentical = false;
458 
459  GetCommercialBreakList(commBreakMap);
460 
461  if ((commBreakMap.empty()) &&
462  (m_lastSentCommBreakMap.empty()))
463  {
464  mapsAreIdentical = true;
465  }
466  else if (commBreakMap.size() == m_lastSentCommBreakMap.size())
467  {
468  // assume true for now and set false if we find a difference
469  mapsAreIdentical = true;
470  for (it = commBreakMap.begin();
471  it != commBreakMap.end() && mapsAreIdentical; ++it)
472  {
473  lastIt = m_lastSentCommBreakMap.find(it.key());
474  if ((lastIt == m_lastSentCommBreakMap.end()) ||
475  (*lastIt != *it))
476  mapsAreIdentical = false;
477  }
478  }
479 
480  if (m_commBreakMapUpdateRequested || !mapsAreIdentical)
481  {
483  m_lastSentCommBreakMap = commBreakMap;
484  }
485 
488  }
489 
490  while (m_bPaused)
491  {
492  emit breathe();
493  std::this_thread::sleep_for(std::chrono::seconds(1));
494  }
495 
496  // sleep a little so we don't use all cpu even if we're niced
497  if (!m_fullSpeed && !m_stillRecording)
498  std::this_thread::sleep_for(std::chrono::milliseconds(10));
499 
500  if (((currentFrameNumber % 500) == 0) ||
502  ((currentFrameNumber % 100) == 0)))
503  {
504  float elapsed = flagTime.elapsed() / 1000.0;
505 
506  if (elapsed)
507  flagFPS = currentFrameNumber / elapsed;
508  else
509  flagFPS = 0.0;
510 
511  int percentage = 0;
512  if (myTotalFrames)
513  percentage = currentFrameNumber * 100 / myTotalFrames;
514 
515  if (percentage > 100)
516  percentage = 100;
517 
518  if (m_showProgress)
519  {
520  if (myTotalFrames)
521  {
522  QString tmp = QString("\r%1%/%2fps \r")
523  .arg(percentage, 3).arg((int)flagFPS, 4);
524  cerr << qPrintable(tmp) << flush;
525  }
526  else
527  {
528  QString tmp = QString("\r%1/%2fps \r")
529  .arg(currentFrameNumber, 6).arg((int)flagFPS, 4);
530  cerr << qPrintable(tmp) << flush;
531  }
532  }
533 
534  if (myTotalFrames)
535  {
536  emit statusUpdate(QCoreApplication::translate("(mythcommflag)",
537  "%1% Completed @ %2 fps.")
538  .arg(percentage).arg(flagFPS));
539  }
540  else
541  {
542  emit statusUpdate(QCoreApplication::translate("(mythcommflag)",
543  "%1 Frames Completed @ %2 fps.")
544  .arg(currentFrameNumber).arg(flagFPS));
545  }
546 
547  if (percentage % 10 == 0 && prevpercent != percentage)
548  {
549  prevpercent = percentage;
550  LOG(VB_GENERAL, LOG_INFO, QString("%1%% Completed @ %2 fps.")
551  .arg(percentage) .arg(flagFPS));
552  }
553  }
554 
555  ProcessFrame(currentFrame, currentFrameNumber);
556 
557  if (m_stillRecording)
558  {
559  int secondsRecorded =
561  int secondsFlagged = (int)(m_framesProcessed / m_fps);
562  int secondsBehind = secondsRecorded - secondsFlagged;
563  long usecPerFrame = (long)(1.0F / m_player->GetFrameRate() * 1000000);
564 
565  struct timeval endTime {};
566  gettimeofday(&endTime, nullptr);
567 
568  long long usecSleep =
569  usecPerFrame -
570  (((endTime.tv_sec - startTime.tv_sec) * 1000000) +
571  (endTime.tv_usec - startTime.tv_usec));
572 
573  if (secondsBehind > requiredBuffer)
574  {
575  if (m_fullSpeed)
576  usecSleep = 0;
577  else
578  usecSleep = (long)(usecSleep * 0.25);
579  }
580  else if (secondsBehind < requiredBuffer)
581  usecSleep = (long)(usecPerFrame * 1.5);
582 
583  if (usecSleep > 0)
584  std::this_thread::sleep_for(std::chrono::microseconds(usecSleep));
585  }
586 
587  m_player->DiscardVideoFrame(currentFrame);
588  }
589 
590  if (m_showProgress)
591  {
592 #if 0
593  float elapsed = flagTime.elapsed() / 1000.0;
594 
595  if (elapsed)
596  flagFPS = currentFrameNumber / elapsed;
597  else
598  flagFPS = 0.0;
599 #endif
600 
601  if (myTotalFrames)
602  cerr << "\b\b\b\b\b\b \b\b\b\b\b\b";
603  else
604  cerr << "\b\b\b\b\b\b\b\b\b\b\b\b\b "
605  "\b\b\b\b\b\b\b\b\b\b\b\b\b";
606  cerr.flush();
607  }
608 
609  return true;
610 }
611 
613  unsigned int framenum,bool isSceneChange,float debugValue)
614 {
615  if (isSceneChange)
616  {
617  m_frameInfo[framenum].flagMask |= COMM_FRAME_SCENE_CHANGE;
618  m_sceneMap[framenum] = MARK_SCENE_CHANGE;
619  }
620  else
621  {
622  m_frameInfo[framenum].flagMask &= ~COMM_FRAME_SCENE_CHANGE;
623  m_sceneMap.remove(framenum);
624  }
625 
626  m_frameInfo[framenum].sceneChangePercent = (int) (debugValue*100);
627 }
628 
630 {
631 
632  LOG(VB_COMMFLAG, LOG_INFO, "CommDetect::GetCommBreakMap()");
633 
634  marks.clear();
635 
637 
638  bool blank = (COMM_DETECT_BLANK & m_commDetectMethod) != 0;
639  bool scene = (COMM_DETECT_SCENE & m_commDetectMethod) != 0;
640  bool logo = (COMM_DETECT_LOGO & m_commDetectMethod) != 0;
641 
643  return;
644 
645  if (!blank && !scene && !logo)
646  {
647  LOG(VB_COMMFLAG, LOG_ERR, QString("Unexpected commDetectMethod: 0x%1")
648  .arg(m_commDetectMethod,0,16));
649  return;
650  }
651 
652  if (blank && scene && logo)
653  {
656  LOG(VB_COMMFLAG, LOG_INFO, "Final Commercial Break Map");
657  return;
658  }
659 
660  if (blank)
661  {
664  }
665 
666  if (scene)
667  {
670  }
671 
672  if (logo)
673  {
676  }
677 
678  int cnt = ((blank) ? 1 : 0) + ((scene) ? 1 : 0) + ((logo) ? 1 : 0);
679  if (cnt == 2)
680  {
681  if (blank && scene)
682  {
685  }
686  else if (blank && logo)
687  {
690  }
691  else if (scene && logo)
692  {
695  }
696  }
697 
698  LOG(VB_COMMFLAG, LOG_INFO, "Final Commercial Break Map");
699 }
700 
701 void ClassicCommDetector::recordingFinished(long long totalFileSize)
702 {
703  (void)totalFileSize;
704 
705  m_stillRecording = false;
706 }
707 
709 {
712 }
713 
715 {
716  int newAspect = COMM_ASPECT_WIDE;
717 
718  LOG(VB_COMMFLAG, LOG_INFO,
719  QString("CommDetect::SetVideoParams called with aspect = %1")
720  .arg(aspect));
721  // Default to Widescreen but use the same check as VideoOutput::MoveResize()
722  // to determine if is normal 4:3 aspect
723  if (fabs(aspect - 1.333333F) < 0.1F)
724  newAspect = COMM_ASPECT_NORMAL;
725 
726  if (newAspect != m_currentAspect)
727  {
728  LOG(VB_COMMFLAG, LOG_INFO,
729  QString("Aspect Ratio changed from %1 to %2 at frame %3")
730  .arg(m_currentAspect).arg(newAspect)
731  .arg(m_curFrameNumber));
732 
733  if (m_frameInfo.contains(m_curFrameNumber))
734  {
735  // pretend that this frame is blank so that we can create test
736  // blocks on real aspect ratio change boundaries.
740  }
741  else if (m_curFrameNumber != -1)
742  {
743  LOG(VB_COMMFLAG, LOG_ERR,
744  QString("Unable to keep track of Aspect ratio change because "
745  "frameInfo for frame number %1 does not exist.")
746  .arg(m_curFrameNumber));
747  }
748  m_currentAspect = newAspect;
749  }
750 }
751 
753  long long frame_number)
754 {
755  int max = 0;
756  int min = 255;
757  int blankPixelsChecked = 0;
758  long long totBrightness = 0;
759  auto *rowMax = new unsigned char[m_height];
760  auto *colMax = new unsigned char[m_width];
761  memset(rowMax, 0, sizeof(*rowMax)*m_height);
762  memset(colMax, 0, sizeof(*colMax)*m_width);
763  int topDarkRow = m_commDetectBorder;
764  int bottomDarkRow = m_height - m_commDetectBorder - 1;
765  int leftDarkCol = m_commDetectBorder;
766  int rightDarkCol = m_width - m_commDetectBorder - 1;
767  FrameInfoEntry fInfo {};
768 
769  if (!frame || !(frame->buf) || frame_number == -1 ||
770  frame->codec != FMT_YV12)
771  {
772  LOG(VB_COMMFLAG, LOG_ERR, "CommDetect: Invalid video frame or codec, "
773  "unable to process frame.");
774  delete[] rowMax;
775  delete[] colMax;
776  return;
777  }
778 
779  if (!m_width || !m_height)
780  {
781  LOG(VB_COMMFLAG, LOG_ERR, "CommDetect: Width or Height is 0, "
782  "unable to process frame.");
783  delete[] rowMax;
784  delete[] colMax;
785  return;
786  }
787 
788  m_curFrameNumber = frame_number;
789  unsigned char* framePtr = frame->buf;
790  int bytesPerLine = frame->pitches[0];
791 
792  fInfo.minBrightness = -1;
793  fInfo.maxBrightness = -1;
794  fInfo.avgBrightness = -1;
795  fInfo.sceneChangePercent = -1;
796  fInfo.aspect = m_currentAspect;
797  fInfo.format = COMM_FORMAT_NORMAL;
798  fInfo.flagMask = 0;
799 
800  int& flagMask = m_frameInfo[m_curFrameNumber].flagMask;
801 
802  // Fill in dummy info records for skipped frames.
803  if (m_lastFrameNumber != (m_curFrameNumber - 1))
804  {
805  if (m_lastFrameNumber > 0)
806  {
807  fInfo.aspect = m_frameInfo[m_lastFrameNumber].aspect;
808  fInfo.format = m_frameInfo[m_lastFrameNumber].format;
809  }
810  fInfo.flagMask = COMM_FRAME_SKIPPED;
811 
814  m_frameInfo[m_lastFrameNumber++] = fInfo;
815 
816  fInfo.flagMask = 0;
817  }
819 
820  m_frameInfo[m_curFrameNumber] = fInfo;
821 
823  m_frameIsBlank = false;
824 
826  {
828  }
829 
830  m_stationLogoPresent = false;
831 
832  for(int y = m_commDetectBorder; y < (m_height - m_commDetectBorder);
833  y += m_vertSpacing)
834  {
835  for(int x = m_commDetectBorder; x < (m_width - m_commDetectBorder);
836  x += m_horizSpacing)
837  {
838  uchar pixel = framePtr[y * bytesPerLine + x];
839 
841  {
842  bool checkPixel = false;
844  checkPixel = true;
845 
846  if (!m_logoInfoAvailable ||
848  checkPixel=true;
849 
850  if (checkPixel)
851  {
852  blankPixelsChecked++;
853  totBrightness += pixel;
854 
855  if (pixel < min)
856  min = pixel;
857 
858  if (pixel > max)
859  max = pixel;
860 
861  if (pixel > rowMax[y])
862  rowMax[y] = pixel;
863 
864  if (pixel > colMax[x])
865  colMax[x] = pixel;
866  }
867  }
868  }
869  }
870 
871  if ((m_commDetectMethod & COMM_DETECT_BLANKS) && blankPixelsChecked)
872  {
873  for(int y = m_commDetectBorder; y < (m_height - m_commDetectBorder);
874  y += m_vertSpacing)
875  {
876  if (rowMax[y] > m_commDetectBoxBrightness)
877  break;
878  topDarkRow = y;
879  }
880 
881  for(int y = m_commDetectBorder; y < (m_height - m_commDetectBorder);
882  y += m_vertSpacing)
883  if (rowMax[y] >= m_commDetectBoxBrightness)
884  bottomDarkRow = y;
885 
886  delete[] rowMax;
887  rowMax = nullptr;
888 
889  for(int x = m_commDetectBorder; x < (m_width - m_commDetectBorder);
890  x += m_horizSpacing)
891  {
892  if (colMax[x] > m_commDetectBoxBrightness)
893  break;
894  leftDarkCol = x;
895  }
896 
897  for(int x = m_commDetectBorder; x < (m_width - m_commDetectBorder);
898  x += m_horizSpacing)
899  if (colMax[x] >= m_commDetectBoxBrightness)
900  rightDarkCol = x;
901 
902  delete[] colMax;
903  colMax = nullptr;
904 
906  if ((topDarkRow > m_commDetectBorder) &&
907  (topDarkRow < (m_height * .20)) &&
908  (bottomDarkRow < (m_height - m_commDetectBorder)) &&
909  (bottomDarkRow > (m_height * .80)))
910  {
912  }
913  if ((leftDarkCol > m_commDetectBorder) &&
914  (leftDarkCol < (m_width * .20)) &&
915  (rightDarkCol < (m_width - m_commDetectBorder)) &&
916  (rightDarkCol > (m_width * .80)))
917  {
919  }
920 
921  int avg = totBrightness / blankPixelsChecked;
922 
923  m_frameInfo[m_curFrameNumber].minBrightness = min;
924  m_frameInfo[m_curFrameNumber].maxBrightness = max;
925  m_frameInfo[m_curFrameNumber].avgBrightness = avg;
926 
927  m_totalMinBrightness += min;
928  m_commDetectDimAverage = min + 10;
929 
930  // Is the frame really dark
931  if (((max - min) <= m_commDetectBlankFrameMaxDiff) &&
933  m_frameIsBlank = true;
934 
935  // Are we non-strict and the frame is blank
936  if ((!m_aggressiveDetection) &&
937  ((max - min) <= m_commDetectBlankFrameMaxDiff))
938  m_frameIsBlank = true;
939 
940  // Are we non-strict and the frame is dark
941  // OR the frame is dim and has a low avg brightness
942  if ((!m_aggressiveDetection) &&
943  ((max < m_commDetectDarkBrightness) ||
945  m_frameIsBlank = true;
946  }
947 
949  {
952  }
953 
954 #if 0
956  (CheckRatingSymbol()))
957  {
958  flagMask |= COMM_FRAME_RATING_SYMBOL;
959  }
960 #endif
961 
962  if (m_frameIsBlank)
963  {
965  flagMask |= COMM_FRAME_BLANK;
967  }
968 
970  flagMask |= COMM_FRAME_LOGO_PRESENT;
971 
972  //TODO: move this debugging code out of the perframe loop, and do it after
973  // we've processed all frames. this is because a scenechangedetector can
974  // now use a few frames to determine whether the frame a few frames ago was
975  // a scene change or not.. due to this lookahead possibility the values
976  // that are currently in the frameInfo array, might be changed a few frames
977  // from now. The ClassicSceneChangeDetector doesn't use this though. future
978  // scenechangedetectors might.
979 
980  if (m_verboseDebugging)
981  {
982  LOG(VB_COMMFLAG, LOG_DEBUG, QString("Frame: %1 -> %2 %3 %4 %5 %6 %7 %8")
983  .arg(m_curFrameNumber, 6)
984  .arg(m_frameInfo[m_curFrameNumber].minBrightness, 3)
985  .arg(m_frameInfo[m_curFrameNumber].maxBrightness, 3)
986  .arg(m_frameInfo[m_curFrameNumber].avgBrightness, 3)
987  .arg(m_frameInfo[m_curFrameNumber].sceneChangePercent, 3)
988  .arg(m_frameInfo[m_curFrameNumber].format, 1)
989  .arg(m_frameInfo[m_curFrameNumber].aspect, 1)
990  .arg(m_frameInfo[m_curFrameNumber].flagMask, 4, 16, QChar('0')));
991  }
992 
993 #ifdef SHOW_DEBUG_WIN
994  comm_debug_show(frame->buf);
995  getchar();
996 #endif
997 
999  delete[] rowMax;
1000  delete[] colMax;
1001 }
1002 
1004 {
1005  LOG(VB_COMMFLAG, LOG_INFO, "CommDetect::ClearAllMaps()");
1006 
1007  m_frameInfo.clear();
1008  m_blankFrameMap.clear();
1009  m_blankCommMap.clear();
1010  m_blankCommBreakMap.clear();
1011  m_sceneMap.clear();
1012  m_sceneCommBreakMap.clear();
1013  m_commBreakMap.clear();
1014 }
1015 
1017 {
1018  LOG(VB_COMMFLAG, LOG_INFO, "CommDetect::GetBlankCommMap()");
1019 
1020  if (m_blankCommMap.isEmpty())
1022 
1023  comms = m_blankCommMap;
1024 }
1025 
1027 {
1028  LOG(VB_COMMFLAG, LOG_INFO, "CommDetect::GetBlankCommBreakMap()");
1029 
1030  if (m_blankCommBreakMap.isEmpty())
1032 
1033  comms = m_blankCommBreakMap;
1034 }
1035 
1037  int64_t start_frame)
1038 {
1039  LOG(VB_COMMFLAG, LOG_INFO, "CommDetect::GetSceneChangeMap()");
1040 
1041  frm_dir_map_t::iterator it;
1042 
1043  if (start_frame == -1)
1044  scenes.clear();
1045 
1046  for (it = m_sceneMap.begin(); it != m_sceneMap.end(); ++it)
1047  if ((start_frame == -1) || ((int64_t)it.key() >= start_frame))
1048  scenes[it.key()] = *it;
1049 }
1050 
1052  const frm_dir_map_t &b) const
1053 {
1054  LOG(VB_COMMFLAG, LOG_INFO, "CommDetect::BuildMasterCommList()");
1055 
1056  frm_dir_map_t newMap;
1057 
1058  if (!a.empty())
1059  {
1060  frm_dir_map_t::const_iterator it = a.begin();
1061  for (; it != a.end(); ++it)
1062  newMap[it.key()] = *it;
1063  }
1064 
1065  if ((a.size() > 1) &&
1066  (b.size() > 1))
1067  {
1068  // see if beginning of the recording looks like a commercial
1069  frm_dir_map_t::const_iterator it_a;
1070  frm_dir_map_t::const_iterator it_b;
1071 
1072  it_a = a.begin();
1073  it_b = b.begin();
1074 
1075  if ((it_b.key() < 2) && (it_a.key() > 2))
1076  {
1077  newMap.remove(it_a.key());
1078  newMap[0] = MARK_COMM_START;
1079  }
1080 
1081 
1082  // see if ending of recording looks like a commercial
1083  frm_dir_map_t::const_iterator it;
1084  uint64_t max_a = 0;
1085  uint64_t max_b = 0;
1086 
1087  it = a.begin();
1088  for (; it != a.end(); ++it)
1089  {
1090  if ((*it == MARK_COMM_END) && (it.key() > max_a))
1091  max_a = it.key();
1092  }
1093 
1094  it = b.begin();
1095  for (; it != b.end(); ++it)
1096  {
1097  if ((*it == MARK_COMM_END) && (it.key() > max_b))
1098  max_b = it.key();
1099  }
1100 
1101  if ((max_a < (m_framesProcessed - 2)) &&
1102  (max_b > (m_framesProcessed - 2)))
1103  {
1104  newMap.remove(max_a);
1105  newMap[m_framesProcessed] = MARK_COMM_END;
1106  }
1107  }
1108 
1109  if ((a.size() > 3) &&
1110  (b.size() > 1))
1111  {
1112  frm_dir_map_t::const_iterator it_a;
1113  frm_dir_map_t::const_iterator it_b;
1114 
1115  it_a = a.begin();
1116  ++it_a;
1117  it_b = it_a;
1118  ++it_b;
1119  while (it_b != a.end())
1120  {
1121  uint64_t fdiff = it_b.key() - it_a.key();
1122  bool allTrue = false;
1123 
1124  if (fdiff < (62 * m_fps))
1125  {
1126  uint64_t f = it_a.key() + 1;
1127 
1128  allTrue = true;
1129 
1130  while ((f <= m_framesProcessed) && (f < it_b.key()) && (allTrue))
1131  allTrue = FrameIsInBreakMap(f++, b);
1132  }
1133 
1134  if (allTrue)
1135  {
1136  newMap.remove(it_a.key());
1137  newMap.remove(it_b.key());
1138  }
1139 
1140  ++it_a; ++it_a;
1141  ++it_b;
1142  if (it_b != a.end())
1143  ++it_b;
1144  }
1145  }
1146 
1147  return newMap;
1148 }
1149 
1151  FrameInfoEntry finfo,
1152  int format, int aspect)
1153 {
1154  int value = 0;
1155 
1156  value = finfo.flagMask;
1157 
1158  if (value & COMM_FRAME_LOGO_PRESENT)
1159  fbp->logoCount++;
1160 
1161  if (value & COMM_FRAME_RATING_SYMBOL)
1162  fbp->ratingCount++;
1163 
1164  if (value & COMM_FRAME_SCENE_CHANGE)
1165  fbp->scCount++;
1166 
1167  if (finfo.format == format)
1168  fbp->formatMatch++;
1169 
1170  if (finfo.aspect == aspect)
1171  fbp->aspectMatch++;
1172 }
1173 
1174 #define FORMAT_MSG(first, fbp) \
1175  msgformat.arg((first), 5) \
1176  .arg((int)((fbp)->start / m_fps) / 60, 3) \
1177  .arg((int)(((fbp)->start / m_fps )) % 60, 2, 10, QChar('0')) \
1178  .arg((fbp)->start, 6) \
1179  .arg((fbp)->end, 6) \
1180  .arg((fbp)->frames, 6) \
1181  .arg((fbp)->length, 7, 'f', 2) \
1182  .arg((fbp)->bfCount, 3) \
1183  .arg((fbp)->logoCount, 6) \
1184  .arg((fbp)->ratingCount, 6) \
1185  .arg((fbp)->scCount, 6) \
1186  .arg((fbp)->scRate, 5, 'f', 2) \
1187  .arg((fbp)->formatMatch, 6) \
1188  .arg((fbp)->aspectMatch, 6) \
1189  .arg((fbp)->score, 5);
1190 
1191 
1193 {
1194  LOG(VB_COMMFLAG, LOG_INFO, "CommDetect::BuildAllMethodsCommList()");
1195 
1196  int lastScore = 0;
1197  uint64_t lastStart = 0;
1198  uint64_t lastEnd = 0;
1199  int64_t firstLogoFrame = -1;
1200  int format = COMM_FORMAT_NORMAL;
1201  int aspect = COMM_ASPECT_NORMAL;
1202  QString msgformat("%1 %2:%3 %4 %5 %6 %7 %8 %9 %10 %11 %12 %13 %14 %15");
1203  QString msg;
1204  uint64_t formatCounts[COMM_FORMAT_MAX];
1205  frm_dir_map_t tmpCommMap;
1206  frm_dir_map_t::iterator it;
1207 
1208  m_commBreakMap.clear();
1209 
1210  auto *fblock = new FrameBlock[m_blankFrameCount + 2];
1211 
1212  int curBlock = 0;
1213  uint64_t curFrame = 1;
1214 
1215  FrameBlock *fbp = &fblock[curBlock];
1216  fbp->start = 0;
1217  fbp->bfCount = 0;
1218  fbp->logoCount = 0;
1219  fbp->ratingCount = 0;
1220  fbp->scCount = 0;
1221  fbp->scRate = 0.0;
1222  fbp->formatMatch = 0;
1223  fbp->aspectMatch = 0;
1224  fbp->score = 0;
1225 
1226  bool lastFrameWasBlank = true;
1227 
1229  {
1230  uint64_t aspectFrames = 0;
1231  for (int64_t i = m_preRoll;
1232  i < ((int64_t)m_framesProcessed - (int64_t)m_postRoll); i++)
1233  {
1234  if ((m_frameInfo.contains(i)) &&
1235  (m_frameInfo[i].aspect == COMM_ASPECT_NORMAL))
1236  aspectFrames++;
1237  }
1238 
1239  if (aspectFrames < ((m_framesProcessed - m_preRoll - m_postRoll) / 2))
1240  {
1241  aspect = COMM_ASPECT_WIDE;
1242 // aspectFrames = m_framesProcessed - m_preRoll - m_postRoll - aspectFrames;
1243  }
1244  }
1245  else
1246  {
1247  memset(&formatCounts, 0, sizeof(formatCounts));
1248 
1249  for(int64_t i = m_preRoll;
1250  i < ((int64_t)m_framesProcessed - (int64_t)m_postRoll); i++ )
1251  {
1252  if ((m_frameInfo.contains(i)) &&
1253  (m_frameInfo[i].format >= 0) &&
1254  (m_frameInfo[i].format < COMM_FORMAT_MAX))
1255  formatCounts[m_frameInfo[i].format]++;
1256  }
1257 
1258  uint64_t formatFrames = 0;
1259  for(int i = 0; i < COMM_FORMAT_MAX; i++)
1260  {
1261  if (formatCounts[i] > formatFrames)
1262  {
1263  format = i;
1264  // cppcheck-suppress unreadVariable
1265  formatFrames = formatCounts[i];
1266  }
1267  }
1268  }
1269 
1270  while (curFrame <= m_framesProcessed)
1271  {
1272  int value = m_frameInfo[curFrame].flagMask;
1273 
1274  bool nextFrameIsBlank = ((curFrame + 1) <= m_framesProcessed) &&
1275  ((m_frameInfo[curFrame + 1].flagMask & COMM_FRAME_BLANK) != 0);
1276 
1277  if (value & COMM_FRAME_BLANK)
1278  {
1279  fbp->bfCount++;
1280 
1281  if (!nextFrameIsBlank || !lastFrameWasBlank)
1282  {
1283  UpdateFrameBlock(fbp, m_frameInfo[curFrame], format, aspect);
1284 
1285  fbp->end = curFrame;
1286  fbp->frames = fbp->end - fbp->start + 1;
1287  fbp->length = fbp->frames / m_fps;
1288 
1289  if ((fbp->scCount) && (fbp->length > 1.05))
1290  fbp->scRate = fbp->scCount / fbp->length;
1291 
1292  curBlock++;
1293 
1294  fbp = &fblock[curBlock];
1295  fbp->bfCount = 1;
1296  fbp->logoCount = 0;
1297  fbp->ratingCount = 0;
1298  fbp->scCount = 0;
1299  fbp->scRate = 0.0;
1300  fbp->score = 0;
1301  fbp->formatMatch = 0;
1302  fbp->aspectMatch = 0;
1303  fbp->start = curFrame;
1304  }
1305 
1306  lastFrameWasBlank = true;
1307  }
1308  else
1309  {
1310  // cppcheck-suppress unreadVariable
1311  lastFrameWasBlank = false;
1312  }
1313 
1314  UpdateFrameBlock(fbp, m_frameInfo[curFrame], format, aspect);
1315 
1316  if ((value & COMM_FRAME_LOGO_PRESENT) &&
1317  (firstLogoFrame == -1))
1318  firstLogoFrame = curFrame;
1319 
1320  curFrame++;
1321  }
1322 
1323  fbp->end = curFrame;
1324  fbp->frames = fbp->end - fbp->start + 1;
1325  fbp->length = fbp->frames / m_fps;
1326 
1327  if ((fbp->scCount) && (fbp->length > 1.05))
1328  fbp->scRate = fbp->scCount / fbp->length;
1329 
1330  int maxBlock = curBlock;
1331  curBlock = 0;
1332 // lastScore = 0;
1333 
1334  LOG(VB_COMMFLAG, LOG_INFO, "Initial Block pass");
1335  LOG(VB_COMMFLAG, LOG_DEBUG,
1336  "Block StTime StFrm EndFrm Frames Secs "
1337  "Bf Lg Cnt RT Cnt SC Cnt SC Rt FmtMch AspMch Score");
1338  LOG(VB_COMMFLAG, LOG_INFO,
1339  "----- ------ ------ ------ ------ ------- "
1340  "--- ------ ------ ------ ----- ------ ------ -----");
1341  while (curBlock <= maxBlock)
1342  {
1343  fbp = &fblock[curBlock];
1344 
1345  msg = FORMAT_MSG(curBlock, fbp);
1346  LOG(VB_COMMFLAG, LOG_DEBUG, msg);
1347 
1348  if (fbp->frames > m_fps)
1349  {
1350  if (m_verboseDebugging)
1351  LOG(VB_COMMFLAG, LOG_DEBUG,
1352  QString(" FRAMES > %1").arg(m_fps));
1353 
1354  if (fbp->length > m_commDetectMaxCommLength)
1355  {
1356  if (m_verboseDebugging)
1357  LOG(VB_COMMFLAG, LOG_DEBUG,
1358  " length > max comm length, +20");
1359  fbp->score += 20;
1360  }
1361 
1363  {
1364  if (m_verboseDebugging)
1365  LOG(VB_COMMFLAG, LOG_DEBUG,
1366  " length > max comm break length, +20");
1367  fbp->score += 20;
1368  }
1369 
1370  if ((fbp->length > 4) &&
1371  (fbp->logoCount > (fbp->frames * 0.60)) &&
1372  (fbp->bfCount < (fbp->frames * 0.10)))
1373  {
1374  if (m_verboseDebugging)
1375  {
1376  LOG(VB_COMMFLAG, LOG_DEBUG,
1377  " length > 4 && logoCount > frames * 0.60 && "
1378  "bfCount < frames * .10");
1379  }
1381  {
1382  if (m_verboseDebugging)
1383  LOG(VB_COMMFLAG, LOG_DEBUG,
1384  " length > max comm break length, +20");
1385  fbp->score += 20;
1386  }
1387  else
1388  {
1389  if (m_verboseDebugging)
1390  LOG(VB_COMMFLAG, LOG_DEBUG,
1391  " length <= max comm break length, +10");
1392  fbp->score += 10;
1393  }
1394  }
1395 
1396  if ((m_logoInfoAvailable) &&
1397  (fbp->logoCount < (fbp->frames * 0.50)))
1398  {
1399  if (m_verboseDebugging)
1400  {
1401  LOG(VB_COMMFLAG, LOG_DEBUG,
1402  " logoInfoAvailable && logoCount < frames * .50, "
1403  "-10");
1404  }
1405  fbp->score -= 10;
1406  }
1407 
1408  if (fbp->ratingCount > (fbp->frames * 0.05))
1409  {
1410  if (m_verboseDebugging)
1411  LOG(VB_COMMFLAG, LOG_DEBUG,
1412  " rating symbol present > 5% of time, +20");
1413  fbp->score += 20;
1414  }
1415 
1416  if ((fbp->scRate > 1.0) &&
1417  (fbp->logoCount < (fbp->frames * .90)))
1418  {
1419  if (m_verboseDebugging)
1420  LOG(VB_COMMFLAG, LOG_DEBUG, " scRate > 1.0, -10");
1421  fbp->score -= 10;
1422 
1423  if (fbp->scRate > 2.0)
1424  {
1425  if (m_verboseDebugging)
1426  LOG(VB_COMMFLAG, LOG_DEBUG, " scRate > 2.0, -10");
1427  fbp->score -= 10;
1428  }
1429  }
1430 
1431  if ((!m_decoderFoundAspectChanges) &&
1432  (fbp->formatMatch < (fbp->frames * .10)))
1433  {
1434  if (m_verboseDebugging)
1435  {
1436  LOG(VB_COMMFLAG, LOG_DEBUG,
1437  " < 10% of frames match show letter/pillar-box "
1438  "format, -20");
1439  }
1440  fbp->score -= 20;
1441  }
1442 
1443  if ((abs((int)(fbp->frames - (15 * m_fps))) < 5 ) ||
1444  (abs((int)(fbp->frames - (30 * m_fps))) < 6 ) ||
1445  (abs((int)(fbp->frames - (60 * m_fps))) < 8 ))
1446  {
1447  if (m_verboseDebugging)
1448  LOG(VB_COMMFLAG, LOG_DEBUG,
1449  " block appears to be standard comm length, -10");
1450  fbp->score -= 10;
1451  }
1452  }
1453  else
1454  {
1455  if (m_verboseDebugging)
1456  LOG(VB_COMMFLAG, LOG_DEBUG,
1457  QString(" FRAMES <= %1").arg(m_fps));
1458 
1459  if ((m_logoInfoAvailable) &&
1460  (fbp->start >= firstLogoFrame) &&
1461  (fbp->logoCount == 0))
1462  {
1463  if (m_verboseDebugging)
1464  LOG(VB_COMMFLAG, LOG_DEBUG,
1465  " logoInfoAvailable && logoCount == 0, -10");
1466  fbp->score -= 10;
1467  }
1468 
1469  if ((!m_decoderFoundAspectChanges) &&
1470  (fbp->formatMatch < (fbp->frames * .10)))
1471  {
1472  if (m_verboseDebugging)
1473  {
1474  LOG(VB_COMMFLAG, LOG_DEBUG,
1475  " < 10% of frames match show letter/pillar-box "
1476  "format, -10");
1477  }
1478  fbp->score -= 10;
1479  }
1480 
1481  if (fbp->ratingCount > (fbp->frames * 0.25))
1482  {
1483  if (m_verboseDebugging)
1484  LOG(VB_COMMFLAG, LOG_DEBUG,
1485  " rating symbol present > 25% of time, +10");
1486  fbp->score += 10;
1487  }
1488  }
1489 
1491  (fbp->aspectMatch < (fbp->frames * .10)))
1492  {
1493  if (m_verboseDebugging)
1494  LOG(VB_COMMFLAG, LOG_DEBUG,
1495  " < 10% of frames match show aspect, -20");
1496  fbp->score -= 20;
1497  }
1498 
1499  msg = FORMAT_MSG("NOW", fbp);
1500  LOG(VB_COMMFLAG, LOG_DEBUG, msg);
1501 
1502 // lastScore = fbp->score;
1503  curBlock++;
1504  }
1505 
1506  curBlock = 0;
1507  lastScore = 0;
1508 
1509  LOG(VB_COMMFLAG, LOG_DEBUG, "============================================");
1510  LOG(VB_COMMFLAG, LOG_INFO, "Second Block pass");
1511  LOG(VB_COMMFLAG, LOG_DEBUG,
1512  "Block StTime StFrm EndFrm Frames Secs "
1513  "Bf Lg Cnt RT Cnt SC Cnt SC Rt FmtMch AspMch Score");
1514  LOG(VB_COMMFLAG, LOG_DEBUG,
1515  "----- ------ ------ ------ ------ ------- "
1516  "--- ------ ------ ------ ----- ------ ------ -----");
1517  while (curBlock <= maxBlock)
1518  {
1519  fbp = &fblock[curBlock];
1520 
1521  msg = FORMAT_MSG(curBlock, fbp);
1522  LOG(VB_COMMFLAG, LOG_DEBUG, msg);
1523 
1524  if ((curBlock > 0) && (curBlock < maxBlock))
1525  {
1526  int nextScore = fblock[curBlock + 1].score;
1527 
1528  if ((lastScore < 0) && (nextScore < 0) && (fbp->length < 35))
1529  {
1530  if (m_verboseDebugging)
1531  {
1532  LOG(VB_COMMFLAG, LOG_DEBUG,
1533  " lastScore < 0 && nextScore < 0 "
1534  "&& length < 35, setting -10");
1535  }
1536  fbp->score -= 10;
1537  }
1538 
1539  if ((fbp->bfCount > (fbp->frames * 0.95)) &&
1540  (fbp->frames < (2*m_fps)) &&
1541  (lastScore < 0 && nextScore < 0))
1542  {
1543  if (m_verboseDebugging)
1544  {
1545  LOG(VB_COMMFLAG, LOG_DEBUG,
1546  " blanks > frames * 0.95 && frames < 2*m_fps && "
1547  "lastScore < 0 && nextScore < 0, setting -10");
1548  }
1549  fbp->score -= 10;
1550  }
1551 
1552  if ((fbp->frames < (120*m_fps)) &&
1553  (lastScore < 0) &&
1554  (fbp->score > 0) &&
1555  (fbp->score < 20) &&
1556  (nextScore < 0))
1557  {
1558  if (m_verboseDebugging)
1559  {
1560  LOG(VB_COMMFLAG, LOG_DEBUG,
1561  " frames < 120 * m_fps && (-20 < lastScore < 0) && "
1562  "thisScore > 0 && nextScore < 0, setting score = -10");
1563  }
1564  fbp->score = -10;
1565  }
1566 
1567  if ((fbp->frames < (30*m_fps)) &&
1568  (lastScore > 0) &&
1569  (fbp->score < 0) &&
1570  (fbp->score > -20) &&
1571  (nextScore > 0))
1572  {
1573  if (m_verboseDebugging)
1574  {
1575  LOG(VB_COMMFLAG, LOG_DEBUG,
1576  " frames < 30 * m_fps && (0 < lastScore < 20) && "
1577  "thisScore < 0 && nextScore > 0, setting score = 10");
1578  }
1579  fbp->score = 10;
1580  }
1581  }
1582 
1583  if ((fbp->score == 0) && (lastScore > 30))
1584  {
1585  int offset = 1;
1586  while(((curBlock + offset) <= maxBlock) &&
1587  (fblock[curBlock + offset].frames < (2 * m_fps)) &&
1588  (fblock[curBlock + offset].score == 0))
1589  offset++;
1590 
1591  if ((curBlock + offset) <= maxBlock)
1592  {
1593  offset--;
1594  if (fblock[curBlock + offset + 1].score > 0)
1595  {
1596  for (; offset >= 0; offset--)
1597  {
1598  fblock[curBlock + offset].score += 10;
1599  if (m_verboseDebugging)
1600  {
1601  LOG(VB_COMMFLAG, LOG_DEBUG,
1602  QString(" Setting block %1 score +10")
1603  .arg(curBlock+offset));
1604  }
1605  }
1606  }
1607  else if (fblock[curBlock + offset + 1].score < 0)
1608  {
1609  for (; offset >= 0; offset--)
1610  {
1611  fblock[curBlock + offset].score -= 10;
1612  if (m_verboseDebugging)
1613  {
1614  LOG(VB_COMMFLAG, LOG_DEBUG,
1615  QString(" Setting block %1 score -10")
1616  .arg(curBlock+offset));
1617  }
1618  }
1619  }
1620  }
1621  }
1622 
1623  msg = FORMAT_MSG("NOW", fbp);
1624  LOG(VB_COMMFLAG, LOG_DEBUG, msg);
1625 
1626  lastScore = fbp->score;
1627  curBlock++;
1628  }
1629 
1630  LOG(VB_COMMFLAG, LOG_DEBUG, "============================================");
1631  LOG(VB_COMMFLAG, LOG_INFO, "FINAL Block stats");
1632  LOG(VB_COMMFLAG, LOG_DEBUG,
1633  "Block StTime StFrm EndFrm Frames Secs "
1634  "Bf Lg Cnt RT Cnt SC Cnt SC Rt FmtMch AspMch Score");
1635  LOG(VB_COMMFLAG, LOG_DEBUG,
1636  "----- ------ ------ ------ ------ ------- "
1637  "--- ------ ------ ------ ----- ------ ------ -----");
1638  curBlock = 0;
1639  lastScore = 0;
1640  int64_t breakStart = -1;
1641  while (curBlock <= maxBlock)
1642  {
1643  fbp = &fblock[curBlock];
1644  int thisScore = fbp->score;
1645 
1646  if ((breakStart >= 0) &&
1647  ((fbp->end - breakStart) > (m_commDetectMaxCommBreakLength * m_fps)))
1648  {
1649  if (((fbp->start - breakStart) >
1651  (breakStart == 0))
1652  {
1653  if (m_verboseDebugging)
1654  {
1655  LOG(VB_COMMFLAG, LOG_DEBUG,
1656  QString("Closing commercial block at start of "
1657  "frame block %1 with length %2, frame "
1658  "block length of %3 frames would put comm "
1659  "block length over max of %4 seconds.")
1660  .arg(curBlock).arg(fbp->start - breakStart)
1661  .arg(fbp->frames)
1663  }
1664 
1665  m_commBreakMap[breakStart] = MARK_COMM_START;
1667  lastStart = breakStart;
1668  lastEnd = fbp->start;
1669  breakStart = -1;
1670  }
1671  else
1672  {
1673  if (m_verboseDebugging)
1674  {
1675  LOG(VB_COMMFLAG, LOG_DEBUG,
1676  QString("Ignoring what appears to be commercial"
1677  " block at frame %1 with length %2, "
1678  "length of %3 frames would put comm "
1679  "block length under min of %4 seconds.")
1680  .arg(breakStart)
1681  .arg(fbp->start - breakStart)
1682  .arg(fbp->frames)
1684  }
1685  breakStart = -1;
1686  }
1687  }
1688  if (thisScore == 0)
1689  {
1690  thisScore = lastScore;
1691  }
1692  else if (thisScore < 0)
1693  {
1694  if ((lastScore > 0) || (curBlock == 0))
1695  {
1696  if ((fbp->start - lastEnd) < (m_commDetectMinShowLength * m_fps))
1697  {
1698  m_commBreakMap.remove(lastStart);
1699  m_commBreakMap.remove(lastEnd);
1700  breakStart = lastStart;
1701 
1702  if (m_verboseDebugging)
1703  {
1704  if (breakStart)
1705  {
1706  LOG(VB_COMMFLAG, LOG_DEBUG,
1707  QString("ReOpening commercial block at "
1708  "frame %1 because show less than "
1709  "%2 seconds")
1710  .arg(breakStart)
1712  }
1713  else
1714  {
1715  LOG(VB_COMMFLAG, LOG_DEBUG,
1716  "Opening initial commercial block "
1717  "at start of recording, block 0.");
1718  }
1719  }
1720  }
1721  else
1722  {
1723  breakStart = fbp->start;
1724 
1725  if (m_verboseDebugging)
1726  {
1727  LOG(VB_COMMFLAG, LOG_DEBUG,
1728  QString("Starting new commercial block at "
1729  "frame %1 from start of frame block %2")
1730  .arg(fbp->start).arg(curBlock));
1731  }
1732  }
1733  }
1734  else if (curBlock == maxBlock)
1735  {
1736  if ((fbp->end - breakStart) >
1738  {
1739  if (fbp->end <=
1740  ((int64_t)m_framesProcessed - (int64_t)(2 * m_fps) - 2))
1741  {
1742  if (m_verboseDebugging)
1743  {
1744  LOG(VB_COMMFLAG, LOG_DEBUG,
1745  QString("Closing final commercial block at "
1746  "frame %1").arg(fbp->end));
1747  }
1748 
1749  m_commBreakMap[breakStart] = MARK_COMM_START;
1751  lastStart = breakStart;
1752  lastEnd = fbp->end;
1753  breakStart = -1;
1754  }
1755  }
1756  else
1757  {
1758  if (m_verboseDebugging)
1759  {
1760  LOG(VB_COMMFLAG, LOG_DEBUG,
1761  QString("Ignoring what appears to be commercial"
1762  " block at frame %1 with length %2, "
1763  "length of %3 frames would put comm "
1764  "block length under min of %4 seconds.")
1765  .arg(breakStart)
1766  .arg(fbp->start - breakStart)
1767  .arg(fbp->frames)
1769  }
1770  breakStart = -1;
1771  }
1772  }
1773  }
1774  else if ((thisScore > 0) &&
1775  (lastScore < 0) &&
1776  (breakStart != -1))
1777  {
1778  if (((fbp->start - breakStart) >
1780  (breakStart == 0))
1781  {
1782  m_commBreakMap[breakStart] = MARK_COMM_START;
1784  lastStart = breakStart;
1785  lastEnd = fbp->start;
1786 
1787  if (m_verboseDebugging)
1788  {
1789  LOG(VB_COMMFLAG, LOG_DEBUG,
1790  QString("Closing commercial block at frame %1")
1791  .arg(fbp->start));
1792  }
1793  }
1794  else
1795  {
1796  if (m_verboseDebugging)
1797  {
1798  LOG(VB_COMMFLAG, LOG_DEBUG,
1799  QString("Ignoring what appears to be commercial "
1800  "block at frame %1 with length %2, "
1801  "length of %3 frames would put comm block "
1802  "length under min of %4 seconds.")
1803  .arg(breakStart)
1804  .arg(fbp->start - breakStart)
1805  .arg(fbp->frames)
1807  }
1808  }
1809  breakStart = -1;
1810  }
1811 
1812  msg = FORMAT_MSG(curBlock, fbp);
1813  LOG(VB_COMMFLAG, LOG_DEBUG, msg);
1814 
1815  // cppcheck-suppress unreadVariable
1816  lastScore = thisScore;
1817  curBlock++;
1818  }
1819 
1820  if ((breakStart != -1) &&
1821  (breakStart <= ((int64_t)m_framesProcessed - (int64_t)(2 * m_fps) - 2)))
1822  {
1823  if (m_verboseDebugging)
1824  {
1825  LOG(VB_COMMFLAG, LOG_DEBUG,
1826  QString("Closing final commercial block started at "
1827  "block %1 and going to end of program. length "
1828  "is %2 frames")
1829  .arg(curBlock)
1830  .arg((m_framesProcessed - breakStart - 1)));
1831  }
1832 
1833  m_commBreakMap[breakStart] = MARK_COMM_START;
1834  // Create what is essentially an open-ended final skip region
1835  // by setting the end point 10 seconds past the end of the
1836  // recording.
1838  }
1839 
1840  // include/exclude blanks from comm breaks
1841  tmpCommMap = m_commBreakMap;
1842  m_commBreakMap.clear();
1843 
1844  if (m_verboseDebugging)
1845  LOG(VB_COMMFLAG, LOG_DEBUG,
1846  "Adjusting start/end marks according to blanks.");
1847  for (it = tmpCommMap.begin(); it != tmpCommMap.end(); ++it)
1848  {
1849  if (*it == MARK_COMM_START)
1850  {
1851  uint64_t lastStartLower = it.key();
1852  uint64_t lastStartUpper = it.key();
1853  while ((lastStartLower > 0) &&
1854  ((m_frameInfo[lastStartLower - 1].flagMask & COMM_FRAME_BLANK) != 0))
1855  lastStartLower--;
1856  while ((lastStartUpper < (m_framesProcessed - (2 * m_fps))) &&
1857  ((m_frameInfo[lastStartUpper + 1].flagMask & COMM_FRAME_BLANK) != 0))
1858  lastStartUpper++;
1859  uint64_t adj = (lastStartUpper - lastStartLower) / 2;
1860  if (adj > MAX_BLANK_FRAMES)
1861  adj = MAX_BLANK_FRAMES;
1862  lastStart = lastStartLower + adj;
1863 
1864  if (m_verboseDebugging)
1865  LOG(VB_COMMFLAG, LOG_DEBUG, QString("Start Mark: %1 -> %2")
1866  .arg(it.key()).arg(lastStart));
1867 
1868  m_commBreakMap[lastStart] = MARK_COMM_START;
1869  }
1870  else
1871  {
1872  uint64_t lastEndLower = it.key();
1873  uint64_t lastEndUpper = it.key();
1874  while ((lastEndUpper < (m_framesProcessed - (2 * m_fps))) &&
1875  ((m_frameInfo[lastEndUpper + 1].flagMask & COMM_FRAME_BLANK) != 0))
1876  lastEndUpper++;
1877  while ((lastEndLower > 0) &&
1878  ((m_frameInfo[lastEndLower - 1].flagMask & COMM_FRAME_BLANK) != 0))
1879  lastEndLower--;
1880  uint64_t adj = (lastEndUpper - lastEndLower) / 2;
1881  if (adj > MAX_BLANK_FRAMES)
1882  adj = MAX_BLANK_FRAMES;
1883  lastEnd = lastEndUpper - adj;
1884 
1885  if (m_verboseDebugging)
1886  LOG(VB_COMMFLAG, LOG_DEBUG, QString("End Mark : %1 -> %2")
1887  .arg(it.key()).arg(lastEnd));
1888 
1889  m_commBreakMap[lastEnd] = MARK_COMM_END;
1890  }
1891  }
1892 
1893  delete [] fblock;
1894 }
1895 
1896 
1898 {
1899  LOG(VB_COMMFLAG, LOG_INFO, "CommDetect::BuildBlankFrameCommList()");
1900 
1901  auto *bframes = new long long[m_blankFrameMap.count()*2];
1902  auto *c_start = new long long[m_blankFrameMap.count()];
1903  auto *c_end = new long long[m_blankFrameMap.count()];
1904  int frames = 0;
1905  int commercials = 0;
1906 
1907  m_blankCommMap.clear();
1908 
1909  for (auto it = m_blankFrameMap.begin(); it != m_blankFrameMap.end(); ++it)
1910  bframes[frames++] = it.key();
1911 
1912  if (frames == 0)
1913  {
1914  delete[] c_start;
1915  delete[] c_end;
1916  delete[] bframes;
1917  return;
1918  }
1919 
1920  // detect individual commercials from blank frames
1921  // commercial end is set to frame right before ending blank frame to
1922  // account for instances with only a single blank frame between comms.
1923  for(int i = 0; i < frames; i++ )
1924  {
1925  for(int x=i+1; x < frames; x++ )
1926  {
1927  // check for various length spots since some channels don't
1928  // have blanks inbetween commercials just at the beginning and
1929  // end of breaks
1930  int gap_length = bframes[x] - bframes[i];
1931  if (((m_aggressiveDetection) &&
1932  ((abs((int)(gap_length - (5 * m_fps))) < 5 ) ||
1933  (abs((int)(gap_length - (10 * m_fps))) < 7 ) ||
1934  (abs((int)(gap_length - (15 * m_fps))) < 10 ) ||
1935  (abs((int)(gap_length - (20 * m_fps))) < 11 ) ||
1936  (abs((int)(gap_length - (30 * m_fps))) < 12 ) ||
1937  (abs((int)(gap_length - (40 * m_fps))) < 1 ) ||
1938  (abs((int)(gap_length - (45 * m_fps))) < 1 ) ||
1939  (abs((int)(gap_length - (60 * m_fps))) < 15 ) ||
1940  (abs((int)(gap_length - (90 * m_fps))) < 10 ) ||
1941  (abs((int)(gap_length - (120 * m_fps))) < 10 ))) ||
1942  ((!m_aggressiveDetection) &&
1943  ((abs((int)(gap_length - (5 * m_fps))) < 11 ) ||
1944  (abs((int)(gap_length - (10 * m_fps))) < 13 ) ||
1945  (abs((int)(gap_length - (15 * m_fps))) < 16 ) ||
1946  (abs((int)(gap_length - (20 * m_fps))) < 17 ) ||
1947  (abs((int)(gap_length - (30 * m_fps))) < 18 ) ||
1948  (abs((int)(gap_length - (40 * m_fps))) < 3 ) ||
1949  (abs((int)(gap_length - (45 * m_fps))) < 3 ) ||
1950  (abs((int)(gap_length - (60 * m_fps))) < 20 ) ||
1951  (abs((int)(gap_length - (90 * m_fps))) < 20 ) ||
1952  (abs((int)(gap_length - (120 * m_fps))) < 20 ))))
1953  {
1954  c_start[commercials] = bframes[i];
1955  c_end[commercials] = bframes[x] - 1;
1956  commercials++;
1957  i = x-1;
1958  x = frames;
1959  }
1960 
1961  if ((!m_aggressiveDetection) &&
1962  ((abs((int)(gap_length - (30 * m_fps))) < (int)(m_fps * 0.85)) ||
1963  (abs((int)(gap_length - (60 * m_fps))) < (int)(m_fps * 0.95)) ||
1964  (abs((int)(gap_length - (90 * m_fps))) < (int)(m_fps * 1.05)) ||
1965  (abs((int)(gap_length - (120 * m_fps))) < (int)(m_fps * 1.15))) &&
1966  ((x + 2) < frames) &&
1967  ((i + 2) < frames) &&
1968  ((bframes[i] + 1) == bframes[i+1]) &&
1969  ((bframes[x] + 1) == bframes[x+1]))
1970  {
1971  c_start[commercials] = bframes[i];
1972  c_end[commercials] = bframes[x];
1973  commercials++;
1974  i = x;
1975  x = frames;
1976  }
1977  }
1978  }
1979 
1980  int i = 0;
1981 
1982  // don't allow single commercial at head
1983  // of show unless followed by another
1984  if ((commercials > 1) &&
1985  (c_end[0] < (33 * m_fps)) &&
1986  (c_start[1] > (c_end[0] + 40 * m_fps)))
1987  i = 1;
1988 
1989  // eliminate any blank frames at end of commercials
1990  bool first_comm = true;
1991  for(; i < (commercials-1); i++)
1992  {
1993  long long r = c_start[i];
1994  long long adjustment = 0;
1995 
1996  if ((r < (30 * m_fps)) &&
1997  (first_comm))
1998  r = 1;
1999 
2001 
2002  r = c_end[i];
2003  if ( i < (commercials-1))
2004  {
2005  int x = 0;
2006  for(x = 0; x < (frames-1); x++)
2007  if (bframes[x] == r)
2008  break;
2009  while((x < (frames-1)) &&
2010  ((bframes[x] + 1 ) == bframes[x+1]) &&
2011  (bframes[x+1] < c_start[i+1]))
2012  {
2013  r++;
2014  x++;
2015  }
2016 
2017  while((m_blankFrameMap.contains(r+1)) &&
2018  (c_start[i+1] != (r+1)))
2019  {
2020  r++;
2021  adjustment++;
2022  }
2023  }
2024  else
2025  {
2026  while(m_blankFrameMap.contains(r+1))
2027  {
2028  r++;
2029  adjustment++;
2030  }
2031  }
2032 
2033  adjustment /= 2;
2034  if (adjustment > MAX_BLANK_FRAMES)
2035  adjustment = MAX_BLANK_FRAMES;
2036  r -= adjustment;
2038  first_comm = false;
2039  }
2040 
2041  m_blankCommMap[c_start[i]] = MARK_COMM_START;
2042  m_blankCommMap[c_end[i]] = MARK_COMM_END;
2043 
2044  delete[] c_start;
2045  delete[] c_end;
2046  delete[] bframes;
2047 
2048  LOG(VB_COMMFLAG, LOG_INFO, "Blank-Frame Commercial Map" );
2049  for(auto it = m_blankCommMap.begin(); it != m_blankCommMap.end(); ++it)
2050  LOG(VB_COMMFLAG, LOG_INFO, QString(" %1:%2")
2051  .arg(it.key()).arg(*it));
2052 
2054 
2055  LOG(VB_COMMFLAG, LOG_INFO, "Merged Blank-Frame Commercial Break Map" );
2056  for(auto it = m_blankCommBreakMap.begin(); it != m_blankCommBreakMap.end(); ++it)
2057  LOG(VB_COMMFLAG, LOG_INFO, QString(" %1:%2")
2058  .arg(it.key()).arg(*it));
2059 }
2060 
2061 
2063 {
2064  int section_start = -1;
2065  int seconds = (int)(m_framesProcessed / m_fps);
2066  int *sc_histogram = new int[seconds+1];
2067 
2068  m_sceneCommBreakMap.clear();
2069 
2070  memset(sc_histogram, 0, (seconds+1)*sizeof(int));
2071  for (uint64_t f = 1; f <= m_framesProcessed; f++)
2072  {
2073  if (m_sceneMap.contains(f))
2074  sc_histogram[(uint64_t)(f / m_fps)]++;
2075  }
2076 
2077  for(long long s = 0; s < (seconds + 1); s++)
2078  {
2079  if (sc_histogram[s] > 2)
2080  {
2081  if (section_start == -1)
2082  {
2083  auto f = (long long)(s * m_fps);
2084  for(int i = 0; i < m_fps; i++, f++)
2085  {
2086  if (m_sceneMap.contains(f))
2087  {
2089  i = (int)(m_fps) + 1;
2090  }
2091  }
2092  }
2093 
2094  section_start = s;
2095  }
2096 
2097  if ((section_start >= 0) &&
2098  (s > (section_start + 32)))
2099  {
2100  auto f = (long long)(section_start * m_fps);
2101  bool found_end = false;
2102 
2103  for(int i = 0; i < m_fps; i++, f++)
2104  {
2105  if (m_sceneMap.contains(f))
2106  {
2107  frm_dir_map_t::iterator dit = m_sceneCommBreakMap.find(f);
2108  if (dit != m_sceneCommBreakMap.end())
2109  m_sceneCommBreakMap.erase(dit);
2110  else
2112  i = (int)(m_fps) + 1;
2113  found_end = true;
2114  }
2115  }
2116  section_start = -1;
2117 
2118  if (!found_end)
2119  {
2120  f = (long long)(section_start * m_fps);
2122  }
2123  }
2124  }
2125  delete[] sc_histogram;
2126 
2127  if (section_start >= 0)
2129 
2130  frm_dir_map_t deleteMap;
2131  frm_dir_map_t::iterator it = m_sceneCommBreakMap.begin();
2132  frm_dir_map_t::iterator prev = it;
2133  if (it != m_sceneCommBreakMap.end())
2134  {
2135  ++it;
2136  while (it != m_sceneCommBreakMap.end())
2137  {
2138  if ((*it == MARK_COMM_END) &&
2139  (it.key() - prev.key()) < (30 * m_fps))
2140  {
2141  deleteMap[it.key()] = MARK_CUT_START;
2142  deleteMap[prev.key()] = MARK_CUT_START;
2143  }
2144  ++prev;
2145  if (it != m_sceneCommBreakMap.end())
2146  ++it;
2147  }
2148 
2149  frm_dir_map_t::iterator dit;
2150  for (dit = deleteMap.begin(); dit != deleteMap.end(); ++dit)
2151  m_sceneCommBreakMap.remove(dit.key());
2152  }
2153 
2154  LOG(VB_COMMFLAG, LOG_INFO, "Scene-Change Commercial Break Map" );
2155  for (it = m_sceneCommBreakMap.begin(); it != m_sceneCommBreakMap.end(); ++it)
2156  {
2157  LOG(VB_COMMFLAG, LOG_INFO, QString(" %1:%2")
2158  .arg(it.key()).arg(*it));
2159  }
2160 }
2161 
2162 
2164 {
2165  show_map_t showmap;
2166  GetLogoCommBreakMap(showmap);
2167  CondenseMarkMap(showmap, (int)(25 * m_fps), (int)(30 * m_fps));
2169 
2170  frm_dir_map_t::iterator it;
2171  LOG(VB_COMMFLAG, LOG_INFO, "Logo Commercial Break Map" );
2172  for(it = m_logoCommBreakMap.begin(); it != m_logoCommBreakMap.end(); ++it)
2173  LOG(VB_COMMFLAG, LOG_INFO, QString(" %1:%2")
2174  .arg(it.key()).arg(*it));
2175 }
2176 
2178 {
2179  frm_dir_map_t::iterator it;
2180  frm_dir_map_t::iterator prev;
2181  QMap<long long, long long> tmpMap;
2182  QMap<long long, long long>::Iterator tmpMap_it;
2183  QMap<long long, long long>::Iterator tmpMap_prev;
2184 
2185  m_blankCommBreakMap.clear();
2186 
2187  if (m_blankCommMap.isEmpty())
2188  return;
2189 
2190  for (it = m_blankCommMap.begin(); it != m_blankCommMap.end(); ++it)
2191  m_blankCommBreakMap[it.key()] = *it;
2192 
2193  if (m_blankCommBreakMap.isEmpty())
2194  return;
2195 
2196  it = m_blankCommMap.begin();
2197  prev = it;
2198  ++it;
2199  for(; it != m_blankCommMap.end(); ++it, ++prev)
2200  {
2201  // if next commercial starts less than 15*fps frames away then merge
2202  if ((((prev.key() + 1) == it.key()) ||
2203  ((prev.key() + (15 * m_fps)) > it.key())) &&
2204  (*prev == MARK_COMM_END) &&
2205  (*it == MARK_COMM_START))
2206  {
2207  m_blankCommBreakMap.remove(prev.key());
2208  m_blankCommBreakMap.remove(it.key());
2209  }
2210  }
2211 
2212 
2213  // make temp copy of commercial break list
2214  it = m_blankCommBreakMap.begin();
2215  prev = it;
2216  ++it;
2217  tmpMap[prev.key()] = it.key();
2218  for(; it != m_blankCommBreakMap.end(); ++it, ++prev)
2219  {
2220  if ((*prev == MARK_COMM_START) &&
2221  (*it == MARK_COMM_END))
2222  tmpMap[prev.key()] = it.key();
2223  }
2224 
2225  tmpMap_it = tmpMap.begin();
2226  tmpMap_prev = tmpMap_it;
2227  tmpMap_it++;
2228  for(; tmpMap_it != tmpMap.end(); ++tmpMap_it, ++tmpMap_prev)
2229  {
2230  // if we find any segments less than 35 seconds between commercial
2231  // breaks include those segments in the commercial break.
2232  if (((*tmpMap_prev + (35 * m_fps)) > tmpMap_it.key()) &&
2233  ((*tmpMap_prev - tmpMap_prev.key()) > (35 * m_fps)) &&
2234  ((*tmpMap_it - tmpMap_it.key()) > (35 * m_fps)))
2235  {
2236  m_blankCommBreakMap.remove(*tmpMap_prev);
2237  m_blankCommBreakMap.remove(tmpMap_it.key());
2238  }
2239  }
2240 }
2241 
2243  uint64_t f, const frm_dir_map_t &breakMap) const
2244 {
2245  for (uint64_t i = f; i < m_framesProcessed; i++)
2246  {
2247  if (breakMap.contains(i))
2248  {
2249  int type = breakMap[i];
2250  if ((type == MARK_COMM_END) || (i == f))
2251  return true;
2252  if (type == MARK_COMM_START)
2253  return false;
2254  }
2255  }
2256 
2257  // We want from f down to 0, but without wrapping the counter to negative
2258  // on an unsigned counter.
2259  for (uint64_t i = (f + 1); i-- > 0; )
2260  {
2261  if (breakMap.contains(i))
2262  {
2263  int type = breakMap[i];
2264  if ((type == MARK_COMM_START) || (i == f))
2265  return true;
2266  if (type == MARK_COMM_END)
2267  return false;
2268  }
2269  }
2270 
2271  return false;
2272 }
2273 
2275 {
2276  frm_dir_map_t::iterator it;
2277  QString msg;
2278 
2279  LOG(VB_COMMFLAG, LOG_INFO,
2280  "---------------------------------------------------");
2281  for (it = map.begin(); it != map.end(); ++it)
2282  {
2283  long long frame = it.key();
2284  int flag = *it;
2285  int my_fps = (int)ceil(m_fps);
2286  int hour = (frame / my_fps) / 60 / 60;
2287  int min = (frame / my_fps) / 60 - (hour * 60);
2288  int sec = (frame / my_fps) - (min * 60) - (hour * 60 * 60);
2289  int frm = frame - ((sec * my_fps) + (min * 60 * my_fps) +
2290  (hour * 60 * 60 * my_fps));
2291  int my_sec = (int)(frame / my_fps);
2292  msg = QString("%1 : %2 (%3:%4:%5.%6) (%7)")
2293  .arg(frame, 7).arg(flag).arg(hour, 2, 10, QChar('0')).arg(min, 2, 10, QChar('0'))
2294  .arg(sec, 2, 10, QChar('0')).arg(frm, 2, 10, QChar('0')).arg(my_sec);
2295  LOG(VB_COMMFLAG, LOG_INFO, msg);
2296  }
2297  LOG(VB_COMMFLAG, LOG_INFO,
2298  "---------------------------------------------------");
2299 }
2300 
2302  int length)
2303 {
2304  show_map_t::iterator it;
2305  show_map_t::iterator prev;
2306  show_map_t tmpMap;
2307 
2308  if (map.size() <= 2)
2309  return;
2310 
2311  // merge any segments less than 'spacing' frames apart from each other
2312  LOG(VB_COMMFLAG, LOG_INFO, "Commercial Map Before condense:" );
2313  for (it = map.begin(); it != map.end(); ++it)
2314  {
2315  LOG(VB_COMMFLAG, LOG_INFO, QString(" %1:%2")
2316  .arg(it.key()).arg(*it));
2317  tmpMap[it.key()] = *it;
2318  }
2319 
2320  prev = tmpMap.begin();
2321  it = prev;
2322  ++it;
2323  while (it != tmpMap.end())
2324  {
2325  if ((*it == MARK_START) &&
2326  (*prev == MARK_END) &&
2327  ((it.key() - prev.key()) < (uint64_t)spacing))
2328  {
2329  map.remove(prev.key());
2330  map.remove(it.key());
2331  }
2332  ++prev;
2333  ++it;
2334  }
2335 
2336  if (map.empty())
2337  return;
2338 
2339  // delete any segments less than 'length' frames in length
2340  tmpMap.clear();
2341  for (it = map.begin(); it != map.end(); ++it)
2342  tmpMap[it.key()] = *it;
2343 
2344  prev = tmpMap.begin();
2345  it = prev;
2346  ++it;
2347  while (it != tmpMap.end())
2348  {
2349  if ((*prev == MARK_START) &&
2350  (*it == MARK_END) &&
2351  ((it.key() - prev.key()) < (uint64_t)length))
2352  {
2353  map.remove(prev.key());
2354  map.remove(it.key());
2355  }
2356  ++prev;
2357  ++it;
2358  }
2359 
2360  LOG(VB_COMMFLAG, LOG_INFO, "Commercial Map After condense:" );
2361  for (it = map.begin(); it != map.end(); ++it)
2362  LOG(VB_COMMFLAG, LOG_INFO, QString(" %1:%2")
2363  .arg(it.key()).arg(*it));
2364 }
2365 
2367  frm_dir_map_t &out, const show_map_t &in)
2368 {
2369  out.clear();
2370  if (in.empty())
2371  return;
2372 
2373  show_map_t::const_iterator sit;
2374  for (sit = in.begin(); sit != in.end(); ++sit)
2375  {
2376  if (*sit == MARK_START)
2377  out[sit.key()] = MARK_COMM_END;
2378  else
2379  out[sit.key()] = MARK_COMM_START;
2380  }
2381 
2382  frm_dir_map_t::iterator it = out.begin();
2383  if (it == out.end())
2384  return;
2385 
2386  switch (out[it.key()])
2387  {
2388  case MARK_COMM_END:
2389  if (it.key() == 0)
2390  out.remove(0);
2391  else
2392  out[0] = MARK_COMM_START;
2393  break;
2394  case MARK_COMM_START:
2395  break;
2396  default:
2397  out.remove(0);
2398  break;
2399  }
2400 }
2401 
2402 
2403 /* ideas for this method ported back from comskip.c mods by Jere Jones
2404  * which are partially mods based on Myth's original commercial skip
2405  * code written by Chris Pinkham. */
2406 
2408 {
2409  LOG(VB_COMMFLAG, LOG_INFO, "CommDetect::CleanupFrameInfo()");
2410 
2411  // try to account for noisy signal causing blank frames to be undetected
2412  if ((m_framesProcessed > (m_fps * 60)) &&
2413  (m_blankFrameCount < (m_framesProcessed * 0.0004)))
2414  {
2415  int avgHistogram[256];
2416  int minAvg = -1;
2417  int newThreshold = -1;
2418 
2419  LOG(VB_COMMFLAG, LOG_INFO,
2420  QString("ClassicCommDetect: Only found %1 blank frames but "
2421  "wanted at least %2, rechecking data using higher "
2422  "threshold.")
2423  .arg(m_blankFrameCount)
2424  .arg((int)(m_framesProcessed * 0.0004)));
2425  m_blankFrameMap.clear();
2426  m_blankFrameCount = 0;
2427 
2428  memset(avgHistogram, 0, sizeof(avgHistogram));
2429 
2430  for (uint64_t i = 1; i <= m_framesProcessed; i++)
2431  avgHistogram[clamp(m_frameInfo[i].avgBrightness, 0, 255)] += 1;
2432 
2433  for (int i = 1; i <= 255 && minAvg == -1; i++)
2434  if (avgHistogram[i] > (m_framesProcessed * 0.0004))
2435  minAvg = i;
2436 
2437  newThreshold = minAvg + 3;
2438  LOG(VB_COMMFLAG, LOG_INFO,
2439  QString("Minimum Average Brightness on a frame "
2440  "was %1, will use %2 as new threshold")
2441  .arg(minAvg).arg(newThreshold));
2442 
2443  for (uint64_t i = 1; i <= m_framesProcessed; i++)
2444  {
2445  int value = m_frameInfo[i].flagMask;
2446  m_frameInfo[i].flagMask = value & ~COMM_FRAME_BLANK;
2447 
2448  if (( (m_frameInfo[i].flagMask & COMM_FRAME_BLANK) == 0) &&
2449  (m_frameInfo[i].avgBrightness < newThreshold))
2450  {
2451  m_frameInfo[i].flagMask = value | COMM_FRAME_BLANK;
2454  }
2455  }
2456 
2457  LOG(VB_COMMFLAG, LOG_INFO,
2458  QString("Found %1 blank frames using new value")
2459  .arg(m_blankFrameCount));
2460  }
2461 
2462  // try to account for fuzzy logo detection
2463  for (uint64_t i = 1; i <= m_framesProcessed; i++)
2464  {
2465  if ((i < 10) || ((i+10) > m_framesProcessed))
2466  continue;
2467 
2468  int before = 0;
2469  for (int offset = 1; offset <= 10; offset++)
2470  if ((m_frameInfo[i - offset].flagMask & COMM_FRAME_LOGO_PRESENT) != 0)
2471  before++;
2472 
2473  int after = 0;
2474  for (int offset = 1; offset <= 10; offset++)
2475  if ((m_frameInfo[i + offset].flagMask & COMM_FRAME_LOGO_PRESENT) != 0)
2476  after++;
2477 
2478  int value = m_frameInfo[i].flagMask;
2479  if (value == -1)
2480  m_frameInfo[i].flagMask = 0;
2481 
2482  if (value & COMM_FRAME_LOGO_PRESENT)
2483  {
2484  if ((before < 4) && (after < 4))
2485  m_frameInfo[i].flagMask = value & ~COMM_FRAME_LOGO_PRESENT;
2486  }
2487  else
2488  {
2489  if ((before > 6) && (after > 6))
2490  m_frameInfo[i].flagMask = value | COMM_FRAME_LOGO_PRESENT;
2491  }
2492  }
2493 }
2494 
2496 {
2497  LOG(VB_COMMFLAG, LOG_INFO, "CommDetect::GetLogoCommBreakMap()");
2498 
2499  map.clear();
2500 
2501  bool PrevFrameLogo = false;
2502 
2503  for (uint64_t curFrame = 1 ; curFrame <= m_framesProcessed; curFrame++)
2504  {
2505  bool CurrentFrameLogo =
2506  (m_frameInfo[curFrame].flagMask & COMM_FRAME_LOGO_PRESENT) != 0;
2507 
2508  if (!PrevFrameLogo && CurrentFrameLogo)
2509  map[curFrame] = MARK_START;
2510  else if (PrevFrameLogo && !CurrentFrameLogo)
2511  map[curFrame] = MARK_END;
2512 
2513  PrevFrameLogo = CurrentFrameLogo;
2514  }
2515 }
2516 
2518 {
2519  emit breathe();
2520 }
2521 
2523  ostream &out, const frm_dir_map_t *comm_breaks, bool verbose) const
2524 {
2525  if (verbose)
2526  {
2527  QByteArray tmp = FrameInfoEntry::GetHeader().toLatin1();
2528  out << tmp.constData() << " mark" << endl;
2529  }
2530 
2531  for (long long i = 1; i < m_curFrameNumber; i++)
2532  {
2533  QMap<long long, FrameInfoEntry>::const_iterator it = m_frameInfo.find(i);
2534  if (it == m_frameInfo.end())
2535  continue;
2536 
2537  QByteArray atmp = (*it).toString(i, verbose).toLatin1();
2538  out << atmp.constData() << " ";
2539  if (comm_breaks)
2540  {
2541  frm_dir_map_t::const_iterator mit = comm_breaks->find(i);
2542  if (mit != comm_breaks->end())
2543  {
2544  QString tmp = (verbose) ?
2545  toString((MarkTypes)*mit) : QString::number(*mit);
2546  atmp = tmp.toLatin1();
2547 
2548  out << atmp.constData();
2549  }
2550  }
2551  out << "\n";
2552  }
2553 
2554  out << flush;
2555 }
2556 
2557 /* vim: set expandtab tabstop=4 shiftwidth=4: */
ClassicCommDetector(SkipType commDetectMethod, bool showProgress, bool fullSpeed, MythPlayer *player, QDateTime startedAt_in, QDateTime stopsAt_in, QDateTime recordingStartedAt_in, QDateTime recordingStopsAt_in)
#define MAX_BLANK_FRAMES
void sceneChangeDetectorHasNewInformation(unsigned int framenum, bool isSceneChange, float debugValue)
int pitches[3]
Y, U, & V pitches.
Definition: mythframe.h:159
void recordingFinished(long long totalFileSize) override
frm_dir_map_t m_blankFrameMap
float GetVideoAspect(void) const
Definition: mythplayer.h:210
virtual void deleteLater(void)
void GetCommercialBreakList(frm_dir_map_t &marks) override
void ResetTotalDuration(void)
SceneChangeDetectorBase * m_sceneChangeDetector
unsigned char * buf
Definition: mythframe.h:139
static void UpdateFrameBlock(FrameBlock *fbp, FrameInfoEntry finfo, int format, int aspect)
QString toString(MarkTypes type)
void GetLogoCommBreakMap(show_map_t &map)
static QString toStringFrameAspects(int aspect, bool verbose)
LogoDetectorBase * m_logoDetector
void GetSceneChangeMap(frm_dir_map_t &scenes, int64_t start_frame)
VideoFrameType codec
Definition: mythframe.h:138
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
friend class ClassicLogoDetector
static QString toStringFrameMaskValues(int mask, bool verbose)
static guint32 * tmp
Definition: goom_core.c:35
float aspect
Definition: mythframe.h:143
virtual bool searchForLogo(MythPlayer *player)=0
virtual bool doesThisFrameContainTheFoundLogo(VideoFrame *frame)=0
frm_dir_map_t Combine2Maps(const frm_dir_map_t &a, const frm_dir_map_t &b) const
MarkTypes
Definition: programtypes.h:48
QSize GetVideoSize(void) const
Definition: mythplayer.h:209
static void CondenseMarkMap(show_map_t &map, int spacing, int length)
virtual int OpenFile(int Retries=4)
Definition: mythplayer.cpp:729
#define FORMAT_MSG(first, fbp)
void GetBlankCommBreakMap(frm_dir_map_t &comms)
void PrintFullMap(ostream &out, const frm_dir_map_t *comm_breaks, bool verbose) const override
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
frm_dir_map_t m_lastSentCommBreakMap
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
void ProcessFrame(VideoFrame *frame, long long frame_number)
QMap< long long, FrameInfoEntry > m_frameInfo
enum frameFormats FrameFormats
static struct mark marks[16]
float clamp(float val, float minimum, float maximum)
Definition: mythmiscutil.h:41
long long frameNumber
Definition: mythframe.h:147
void requestCommBreakMapUpdate(void) override
frm_dir_map_t m_sceneCommBreakMap
bool FrameIsInBreakMap(uint64_t f, const frm_dir_map_t &breakMap) const
void statusUpdate(const QString &a)
QString toString(uint64_t frame, bool verbose) const
frm_dir_map_t m_blankCommBreakMap
virtual void processFrame(VideoFrame *frame)=0
int GetNumSetting(const QString &key, int defaultval=0)
virtual bool pixelInsideLogo(unsigned int x, unsigned int y)=0
void GetBlankCommMap(frm_dir_map_t &comms)
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
void gotNewCommercialBreakList()
static void ConvertShowMapToCommMap(frm_dir_map_t &out, const show_map_t &in)
bool GetBoolSetting(const QString &key, bool defaultval=false)
void DumpMap(frm_dir_map_t &map)
static guint32 * pixel
--------------------------------------------------—**
Definition: goom_core.c:33
void DiscardVideoFrame(VideoFrame *buffer)
Places frame in the available frames queue.
Definition: mythplayer.cpp:944
void EnableSubtitles(bool enable)
uint64_t GetTotalFrameCount(void) const
Definition: mythplayer.h:228
static QString GetHeader(void)
const char * frames[3]
Definition: element.c:46
bool InitVideo(void)
Definition: mythplayer.cpp:367
frm_dir_map_t m_logoCommBreakMap
QMap< uint64_t, CommMapValue > show_map_t
QMap< uint64_t, MarkTypes > frm_dir_map_t
Frame # -> Mark map.
Definition: programtypes.h:81
void SetVideoParams(float aspect)
virtual unsigned int getRequiredAvailableBufferForSearch()=0
static QString toStringFrameFormats(int format, bool verbose)
frm_dir_map_t m_blankCommMap
frm_dir_map_t m_commBreakMap
enum frameAspects FrameAspects