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