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  QTime 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  emit statusUpdate(QCoreApplication::translate("(mythcommflag)",
536  "%1% Completed @ %2 fps.")
537  .arg(percentage).arg(flagFPS));
538  else
539  emit statusUpdate(QCoreApplication::translate("(mythcommflag)",
540  "%1 Frames Completed @ %2 fps.")
541  .arg(currentFrameNumber).arg(flagFPS));
542 
543  if (percentage % 10 == 0 && prevpercent != percentage)
544  {
545  prevpercent = percentage;
546  LOG(VB_GENERAL, LOG_INFO, QString("%1%% Completed @ %2 fps.")
547  .arg(percentage) .arg(flagFPS));
548  }
549  }
550 
551  ProcessFrame(currentFrame, currentFrameNumber);
552 
553  if (m_stillRecording)
554  {
555  int secondsRecorded =
557  int secondsFlagged = (int)(m_framesProcessed / m_fps);
558  int secondsBehind = secondsRecorded - secondsFlagged;
559  long usecPerFrame = (long)(1.0F / m_player->GetFrameRate() * 1000000);
560 
561  struct timeval endTime {};
562  gettimeofday(&endTime, nullptr);
563 
564  long long usecSleep =
565  usecPerFrame -
566  (((endTime.tv_sec - startTime.tv_sec) * 1000000) +
567  (endTime.tv_usec - startTime.tv_usec));
568 
569  if (secondsBehind > requiredBuffer)
570  {
571  if (m_fullSpeed)
572  usecSleep = 0;
573  else
574  usecSleep = (long)(usecSleep * 0.25);
575  }
576  else if (secondsBehind < requiredBuffer)
577  usecSleep = (long)(usecPerFrame * 1.5);
578 
579  if (usecSleep > 0)
580  std::this_thread::sleep_for(std::chrono::microseconds(usecSleep));
581  }
582 
583  m_player->DiscardVideoFrame(currentFrame);
584  }
585 
586  if (m_showProgress)
587  {
588  float elapsed = flagTime.elapsed() / 1000.0;
589 
590  if (elapsed)
591  flagFPS = currentFrameNumber / elapsed;
592  else
593  flagFPS = 0.0;
594 
595  if (myTotalFrames)
596  cerr << "\b\b\b\b\b\b \b\b\b\b\b\b";
597  else
598  cerr << "\b\b\b\b\b\b\b\b\b\b\b\b\b "
599  "\b\b\b\b\b\b\b\b\b\b\b\b\b";
600  cerr.flush();
601  }
602 
603  return true;
604 }
605 
607  unsigned int framenum,bool isSceneChange,float debugValue)
608 {
609  if (isSceneChange)
610  {
611  frameInfo[framenum].flagMask |= COMM_FRAME_SCENE_CHANGE;
612  m_sceneMap[framenum] = MARK_SCENE_CHANGE;
613  }
614  else
615  {
616  frameInfo[framenum].flagMask &= ~COMM_FRAME_SCENE_CHANGE;
617  m_sceneMap.remove(framenum);
618  }
619 
620  frameInfo[framenum].sceneChangePercent = (int) (debugValue*100);
621 }
622 
624 {
625 
626  LOG(VB_COMMFLAG, LOG_INFO, "CommDetect::GetCommBreakMap()");
627 
628  marks.clear();
629 
631 
632  bool blank = (COMM_DETECT_BLANK & m_commDetectMethod) != 0;
633  bool scene = (COMM_DETECT_SCENE & m_commDetectMethod) != 0;
634  bool logo = (COMM_DETECT_LOGO & m_commDetectMethod) != 0;
635 
637  return;
638 
639  if (!blank && !scene && !logo)
640  {
641  LOG(VB_COMMFLAG, LOG_ERR, QString("Unexpected commDetectMethod: 0x%1")
642  .arg(m_commDetectMethod,0,16));
643  return;
644  }
645 
646  if (blank && scene && logo)
647  {
650  LOG(VB_COMMFLAG, LOG_INFO, "Final Commercial Break Map");
651  return;
652  }
653 
654  if (blank)
655  {
658  }
659 
660  if (scene)
661  {
664  }
665 
666  if (logo)
667  {
670  }
671 
672  int cnt = ((blank) ? 1 : 0) + ((scene) ? 1 : 0) + ((logo) ? 1 : 0);
673  if (cnt == 2)
674  {
675  if (blank && scene)
676  {
679  }
680  else if (blank && logo)
681  {
684  }
685  else if (scene && logo)
686  {
689  }
690  }
691 
692  LOG(VB_COMMFLAG, LOG_INFO, "Final Commercial Break Map");
693 }
694 
695 void ClassicCommDetector::recordingFinished(long long totalFileSize)
696 {
697  (void)totalFileSize;
698 
699  m_stillRecording = false;
700 }
701 
703 {
706 }
707 
709 {
710  int newAspect = COMM_ASPECT_WIDE;
711 
712  LOG(VB_COMMFLAG, LOG_INFO,
713  QString("CommDetect::SetVideoParams called with aspect = %1")
714  .arg(aspect));
715  // Default to Widescreen but use the same check as VideoOutput::MoveResize()
716  // to determine if is normal 4:3 aspect
717  if (fabs(aspect - 1.333333F) < 0.1F)
718  newAspect = COMM_ASPECT_NORMAL;
719 
720  if (newAspect != m_currentAspect)
721  {
722  LOG(VB_COMMFLAG, LOG_INFO,
723  QString("Aspect Ratio changed from %1 to %2 at frame %3")
724  .arg(m_currentAspect).arg(newAspect)
725  .arg(m_curFrameNumber));
726 
727  if (frameInfo.contains(m_curFrameNumber))
728  {
729  // pretend that this frame is blank so that we can create test
730  // blocks on real aspect ratio change boundaries.
734  }
735  else if (m_curFrameNumber != -1)
736  {
737  LOG(VB_COMMFLAG, LOG_ERR,
738  QString("Unable to keep track of Aspect ratio change because "
739  "frameInfo for frame number %1 does not exist.")
740  .arg(m_curFrameNumber));
741  }
742  m_currentAspect = newAspect;
743  }
744 }
745 
747  long long frame_number)
748 {
749  int max = 0;
750  int min = 255;
751  int blankPixelsChecked = 0;
752  long long totBrightness = 0;
753  unsigned char *rowMax = new unsigned char[m_height];
754  unsigned char *colMax = new unsigned char[m_width];
755  memset(rowMax, 0, sizeof(*rowMax)*m_height);
756  memset(colMax, 0, sizeof(*colMax)*m_width);
757  int topDarkRow = m_commDetectBorder;
758  int bottomDarkRow = m_height - m_commDetectBorder - 1;
759  int leftDarkCol = m_commDetectBorder;
760  int rightDarkCol = m_width - m_commDetectBorder - 1;
761  FrameInfoEntry fInfo {};
762 
763  if (!frame || !(frame->buf) || frame_number == -1 ||
764  frame->codec != FMT_YV12)
765  {
766  LOG(VB_COMMFLAG, LOG_ERR, "CommDetect: Invalid video frame or codec, "
767  "unable to process frame.");
768  delete[] rowMax;
769  delete[] colMax;
770  return;
771  }
772 
773  if (!m_width || !m_height)
774  {
775  LOG(VB_COMMFLAG, LOG_ERR, "CommDetect: Width or Height is 0, "
776  "unable to process frame.");
777  delete[] rowMax;
778  delete[] colMax;
779  return;
780  }
781 
782  m_curFrameNumber = frame_number;
783  unsigned char* framePtr = frame->buf;
784  int bytesPerLine = frame->pitches[0];
785 
786  fInfo.minBrightness = -1;
787  fInfo.maxBrightness = -1;
788  fInfo.avgBrightness = -1;
789  fInfo.sceneChangePercent = -1;
790  fInfo.aspect = m_currentAspect;
791  fInfo.format = COMM_FORMAT_NORMAL;
792  fInfo.flagMask = 0;
793 
794  int& flagMask = frameInfo[m_curFrameNumber].flagMask;
795 
796  // Fill in dummy info records for skipped frames.
797  if (m_lastFrameNumber != (m_curFrameNumber - 1))
798  {
799  if (m_lastFrameNumber > 0)
800  {
801  fInfo.aspect = frameInfo[m_lastFrameNumber].aspect;
802  fInfo.format = frameInfo[m_lastFrameNumber].format;
803  }
804  fInfo.flagMask = COMM_FRAME_SKIPPED;
805 
808  frameInfo[m_lastFrameNumber++] = fInfo;
809 
810  fInfo.flagMask = 0;
811  }
813 
814  frameInfo[m_curFrameNumber] = fInfo;
815 
817  m_frameIsBlank = false;
818 
820  {
822  }
823 
824  m_stationLogoPresent = false;
825 
826  for(int y = m_commDetectBorder; y < (m_height - m_commDetectBorder);
827  y += m_vertSpacing)
828  {
829  for(int x = m_commDetectBorder; x < (m_width - m_commDetectBorder);
830  x += m_horizSpacing)
831  {
832  uchar pixel = framePtr[y * bytesPerLine + x];
833 
835  {
836  bool checkPixel = false;
838  checkPixel = true;
839 
840  if (!m_logoInfoAvailable)
841  checkPixel = true;
842  else if (!m_logoDetector->pixelInsideLogo(x,y))
843  checkPixel=true;
844 
845  if (checkPixel)
846  {
847  blankPixelsChecked++;
848  totBrightness += pixel;
849 
850  if (pixel < min)
851  min = pixel;
852 
853  if (pixel > max)
854  max = pixel;
855 
856  if (pixel > rowMax[y])
857  rowMax[y] = pixel;
858 
859  if (pixel > colMax[x])
860  colMax[x] = pixel;
861  }
862  }
863  }
864  }
865 
866  if ((m_commDetectMethod & COMM_DETECT_BLANKS) && blankPixelsChecked)
867  {
868  for(int y = m_commDetectBorder; y < (m_height - m_commDetectBorder);
869  y += m_vertSpacing)
870  {
871  if (rowMax[y] > m_commDetectBoxBrightness)
872  break;
873  topDarkRow = y;
874  }
875 
876  for(int y = m_commDetectBorder; y < (m_height - m_commDetectBorder);
877  y += m_vertSpacing)
878  if (rowMax[y] >= m_commDetectBoxBrightness)
879  bottomDarkRow = y;
880 
881  delete[] rowMax;
882  rowMax = nullptr;
883 
884  for(int x = m_commDetectBorder; x < (m_width - m_commDetectBorder);
885  x += m_horizSpacing)
886  {
887  if (colMax[x] > m_commDetectBoxBrightness)
888  break;
889  leftDarkCol = x;
890  }
891 
892  for(int x = m_commDetectBorder; x < (m_width - m_commDetectBorder);
893  x += m_horizSpacing)
894  if (colMax[x] >= m_commDetectBoxBrightness)
895  rightDarkCol = x;
896 
897  delete[] colMax;
898  colMax = nullptr;
899 
901  if ((topDarkRow > m_commDetectBorder) &&
902  (topDarkRow < (m_height * .20)) &&
903  (bottomDarkRow < (m_height - m_commDetectBorder)) &&
904  (bottomDarkRow > (m_height * .80)))
905  {
907  }
908  if ((leftDarkCol > m_commDetectBorder) &&
909  (leftDarkCol < (m_width * .20)) &&
910  (rightDarkCol < (m_width - m_commDetectBorder)) &&
911  (rightDarkCol > (m_width * .80)))
912  {
914  }
915 
916  int avg = totBrightness / blankPixelsChecked;
917 
918  frameInfo[m_curFrameNumber].minBrightness = min;
919  frameInfo[m_curFrameNumber].maxBrightness = max;
920  frameInfo[m_curFrameNumber].avgBrightness = avg;
921 
922  m_totalMinBrightness += min;
923  m_commDetectDimAverage = min + 10;
924 
925  // Is the frame really dark
926  if (((max - min) <= m_commDetectBlankFrameMaxDiff) &&
928  m_frameIsBlank = true;
929 
930  // Are we non-strict and the frame is blank
931  if ((!m_aggressiveDetection) &&
932  ((max - min) <= m_commDetectBlankFrameMaxDiff))
933  m_frameIsBlank = true;
934 
935  // Are we non-strict and the frame is dark
936  // OR the frame is dim and has a low avg brightness
937  if ((!m_aggressiveDetection) &&
938  ((max < m_commDetectDarkBrightness) ||
940  m_frameIsBlank = true;
941  }
942 
944  {
947  }
948 
949 #if 0
951  (CheckRatingSymbol()))
952  {
953  flagMask |= COMM_FRAME_RATING_SYMBOL;
954  }
955 #endif
956 
957  if (m_frameIsBlank)
958  {
960  flagMask |= COMM_FRAME_BLANK;
962  }
963 
965  flagMask |= COMM_FRAME_LOGO_PRESENT;
966 
967  //TODO: move this debugging code out of the perframe loop, and do it after
968  // we've processed all frames. this is because a scenechangedetector can
969  // now use a few frames to determine whether the frame a few frames ago was
970  // a scene change or not.. due to this lookahead possibility the values
971  // that are currently in the frameInfo array, might be changed a few frames
972  // from now. The ClassicSceneChangeDetector doesn't use this though. future
973  // scenechangedetectors might.
974 
975  if (m_verboseDebugging)
976  LOG(VB_COMMFLAG, LOG_DEBUG,
977  QString().sprintf("Frame: %6ld -> %3d %3d %3d %3d %1d %1d %04x",
978  (long)m_curFrameNumber,
979  frameInfo[m_curFrameNumber].minBrightness,
980  frameInfo[m_curFrameNumber].maxBrightness,
981  frameInfo[m_curFrameNumber].avgBrightness,
982  frameInfo[m_curFrameNumber].sceneChangePercent,
983  frameInfo[m_curFrameNumber].format,
984  frameInfo[m_curFrameNumber].aspect,
985  frameInfo[m_curFrameNumber].flagMask ));
986 
987 #ifdef SHOW_DEBUG_WIN
988  comm_debug_show(frame->buf);
989  getchar();
990 #endif
991 
993  delete[] rowMax;
994  delete[] colMax;
995 }
996 
998 {
999  LOG(VB_COMMFLAG, LOG_INFO, "CommDetect::ClearAllMaps()");
1000 
1001  frameInfo.clear();
1002  m_blankFrameMap.clear();
1003  m_blankCommMap.clear();
1004  m_blankCommBreakMap.clear();
1005  m_sceneMap.clear();
1006  m_sceneCommBreakMap.clear();
1007  m_commBreakMap.clear();
1008 }
1009 
1011 {
1012  LOG(VB_COMMFLAG, LOG_INFO, "CommDetect::GetBlankCommMap()");
1013 
1014  if (m_blankCommMap.isEmpty())
1016 
1017  comms = m_blankCommMap;
1018 }
1019 
1021 {
1022  LOG(VB_COMMFLAG, LOG_INFO, "CommDetect::GetBlankCommBreakMap()");
1023 
1024  if (m_blankCommBreakMap.isEmpty())
1026 
1027  comms = m_blankCommBreakMap;
1028 }
1029 
1031  int64_t start_frame)
1032 {
1033  LOG(VB_COMMFLAG, LOG_INFO, "CommDetect::GetSceneChangeMap()");
1034 
1035  frm_dir_map_t::iterator it;
1036 
1037  if (start_frame == -1)
1038  scenes.clear();
1039 
1040  for (it = m_sceneMap.begin(); it != m_sceneMap.end(); ++it)
1041  if ((start_frame == -1) || ((int64_t)it.key() >= start_frame))
1042  scenes[it.key()] = *it;
1043 }
1044 
1046  const frm_dir_map_t &b) const
1047 {
1048  LOG(VB_COMMFLAG, LOG_INFO, "CommDetect::BuildMasterCommList()");
1049 
1050  frm_dir_map_t newMap;
1051 
1052  if (!a.empty())
1053  {
1054  frm_dir_map_t::const_iterator it = a.begin();
1055  for (; it != a.end(); ++it)
1056  newMap[it.key()] = *it;
1057  }
1058 
1059  if ((a.size() > 1) &&
1060  (b.size() > 1))
1061  {
1062  // see if beginning of the recording looks like a commercial
1063  frm_dir_map_t::const_iterator it_a;
1064  frm_dir_map_t::const_iterator it_b;
1065 
1066  it_a = a.begin();
1067  it_b = b.begin();
1068 
1069  if ((it_b.key() < 2) && (it_a.key() > 2))
1070  {
1071  newMap.remove(it_a.key());
1072  newMap[0] = MARK_COMM_START;
1073  }
1074 
1075 
1076  // see if ending of recording looks like a commercial
1077  frm_dir_map_t::const_iterator it;
1078  uint64_t max_a = 0;
1079  uint64_t max_b = 0;
1080 
1081  it = a.begin();
1082  for (; it != a.end(); ++it)
1083  {
1084  if ((*it == MARK_COMM_END) && (it.key() > max_a))
1085  max_a = it.key();
1086  }
1087 
1088  it = b.begin();
1089  for (; it != b.end(); ++it)
1090  {
1091  if ((*it == MARK_COMM_END) && (it.key() > max_b))
1092  max_b = it.key();
1093  }
1094 
1095  if ((max_a < (m_framesProcessed - 2)) &&
1096  (max_b > (m_framesProcessed - 2)))
1097  {
1098  newMap.remove(max_a);
1099  newMap[m_framesProcessed] = MARK_COMM_END;
1100  }
1101  }
1102 
1103  if ((a.size() > 3) &&
1104  (b.size() > 1))
1105  {
1106  frm_dir_map_t::const_iterator it_a;
1107  frm_dir_map_t::const_iterator it_b;
1108 
1109  it_a = a.begin();
1110  ++it_a;
1111  it_b = it_a;
1112  ++it_b;
1113  while (it_b != a.end())
1114  {
1115  uint64_t fdiff = it_b.key() - it_a.key();
1116  bool allTrue = false;
1117 
1118  if (fdiff < (62 * m_fps))
1119  {
1120  uint64_t f = it_a.key() + 1;
1121 
1122  allTrue = true;
1123 
1124  while ((f <= m_framesProcessed) && (f < it_b.key()) && (allTrue))
1125  allTrue = FrameIsInBreakMap(f++, b);
1126  }
1127 
1128  if (allTrue)
1129  {
1130  newMap.remove(it_a.key());
1131  newMap.remove(it_b.key());
1132  }
1133 
1134  ++it_a; ++it_a;
1135  ++it_b;
1136  if (it_b != a.end())
1137  ++it_b;
1138  }
1139  }
1140 
1141  return newMap;
1142 }
1143 
1145  FrameInfoEntry finfo,
1146  int format, int aspect)
1147 {
1148  int value = 0;
1149 
1150  value = finfo.flagMask;
1151 
1152  if (value & COMM_FRAME_LOGO_PRESENT)
1153  fbp->logoCount++;
1154 
1155  if (value & COMM_FRAME_RATING_SYMBOL)
1156  fbp->ratingCount++;
1157 
1158  if (value & COMM_FRAME_SCENE_CHANGE)
1159  fbp->scCount++;
1160 
1161  if (finfo.format == format)
1162  fbp->formatMatch++;
1163 
1164  if (finfo.aspect == aspect)
1165  fbp->aspectMatch++;
1166 }
1167 
1168 #define FORMAT_MSG(first, fbp) \
1169  msgformat.arg((first), 5) \
1170  .arg((int)((fbp)->start / m_fps) / 60, 3) \
1171  .arg((int)(((fbp)->start / m_fps )) % 60, 2, 10, QChar('0')) \
1172  .arg((fbp)->start, 6) \
1173  .arg((fbp)->end, 6) \
1174  .arg((fbp)->frames, 6) \
1175  .arg((fbp)->length, 7, 'f', 2) \
1176  .arg((fbp)->bfCount, 3) \
1177  .arg((fbp)->logoCount, 6) \
1178  .arg((fbp)->ratingCount, 6) \
1179  .arg((fbp)->scCount, 6) \
1180  .arg((fbp)->scRate, 5, 'f', 2) \
1181  .arg((fbp)->formatMatch, 6) \
1182  .arg((fbp)->aspectMatch, 6) \
1183  .arg((fbp)->score, 5);
1184 
1185 
1187 {
1188  LOG(VB_COMMFLAG, LOG_INFO, "CommDetect::BuildAllMethodsCommList()");
1189 
1190  int lastScore = 0;
1191  uint64_t lastStart = 0;
1192  uint64_t lastEnd = 0;
1193  int64_t firstLogoFrame = -1;
1194  int format = COMM_FORMAT_NORMAL;
1195  int aspect = COMM_ASPECT_NORMAL;
1196  QString msgformat("%1 %2:%3 %4 %5 %6 %7 %8 %9 %10 %11 %12 %13 %14 %15");
1197  QString msg;
1198  uint64_t formatCounts[COMM_FORMAT_MAX];
1199  frm_dir_map_t tmpCommMap;
1200  frm_dir_map_t::iterator it;
1201 
1202  m_commBreakMap.clear();
1203 
1204  FrameBlock *fblock = new FrameBlock[m_blankFrameCount + 2];
1205 
1206  int curBlock = 0;
1207  uint64_t curFrame = 1;
1208 
1209  FrameBlock *fbp = &fblock[curBlock];
1210  fbp->start = 0;
1211  fbp->bfCount = 0;
1212  fbp->logoCount = 0;
1213  fbp->ratingCount = 0;
1214  fbp->scCount = 0;
1215  fbp->scRate = 0.0;
1216  fbp->formatMatch = 0;
1217  fbp->aspectMatch = 0;
1218  fbp->score = 0;
1219 
1220  bool lastFrameWasBlank = true;
1221 
1223  {
1224  uint64_t aspectFrames = 0;
1225  for (int64_t i = m_preRoll;
1226  i < ((int64_t)m_framesProcessed - (int64_t)m_postRoll); i++)
1227  {
1228  if ((frameInfo.contains(i)) &&
1229  (frameInfo[i].aspect == COMM_ASPECT_NORMAL))
1230  aspectFrames++;
1231  }
1232 
1233  if (aspectFrames < ((m_framesProcessed - m_preRoll - m_postRoll) / 2))
1234  {
1235  aspect = COMM_ASPECT_WIDE;
1236  aspectFrames = m_framesProcessed - m_preRoll - m_postRoll - aspectFrames;
1237  }
1238  }
1239  else
1240  {
1241  memset(&formatCounts, 0, sizeof(formatCounts));
1242 
1243  for(int64_t i = m_preRoll;
1244  i < ((int64_t)m_framesProcessed - (int64_t)m_postRoll); i++ )
1245  if ((frameInfo.contains(i)) &&
1246  (frameInfo[i].format >= 0) &&
1247  (frameInfo[i].format < COMM_FORMAT_MAX))
1248  formatCounts[frameInfo[i].format]++;
1249 
1250  uint64_t formatFrames = 0;
1251  for(int i = 0; i < COMM_FORMAT_MAX; i++)
1252  {
1253  if (formatCounts[i] > formatFrames)
1254  {
1255  format = i;
1256  // cppcheck-suppress unreadVariable
1257  formatFrames = formatCounts[i];
1258  }
1259  }
1260  }
1261 
1262  while (curFrame <= m_framesProcessed)
1263  {
1264  int value = frameInfo[curFrame].flagMask;
1265 
1266  bool nextFrameIsBlank = ((curFrame + 1) <= m_framesProcessed) &&
1267  ((frameInfo[curFrame + 1].flagMask & COMM_FRAME_BLANK) != 0);
1268 
1269  if (value & COMM_FRAME_BLANK)
1270  {
1271  fbp->bfCount++;
1272 
1273  if (!nextFrameIsBlank || !lastFrameWasBlank)
1274  {
1275  UpdateFrameBlock(fbp, frameInfo[curFrame], format, aspect);
1276 
1277  fbp->end = curFrame;
1278  fbp->frames = fbp->end - fbp->start + 1;
1279  fbp->length = fbp->frames / m_fps;
1280 
1281  if ((fbp->scCount) && (fbp->length > 1.05))
1282  fbp->scRate = fbp->scCount / fbp->length;
1283 
1284  curBlock++;
1285 
1286  fbp = &fblock[curBlock];
1287  fbp->bfCount = 1;
1288  fbp->logoCount = 0;
1289  fbp->ratingCount = 0;
1290  fbp->scCount = 0;
1291  fbp->scRate = 0.0;
1292  fbp->score = 0;
1293  fbp->formatMatch = 0;
1294  fbp->aspectMatch = 0;
1295  fbp->start = curFrame;
1296  }
1297 
1298  lastFrameWasBlank = true;
1299  }
1300  else
1301  {
1302  // cppcheck-suppress unreadVariable
1303  lastFrameWasBlank = false;
1304  }
1305 
1306  UpdateFrameBlock(fbp, frameInfo[curFrame], format, aspect);
1307 
1308  if ((value & COMM_FRAME_LOGO_PRESENT) &&
1309  (firstLogoFrame == -1))
1310  firstLogoFrame = curFrame;
1311 
1312  curFrame++;
1313  }
1314 
1315  fbp->end = curFrame;
1316  fbp->frames = fbp->end - fbp->start + 1;
1317  fbp->length = fbp->frames / m_fps;
1318 
1319  if ((fbp->scCount) && (fbp->length > 1.05))
1320  fbp->scRate = fbp->scCount / fbp->length;
1321 
1322  int maxBlock = curBlock;
1323  curBlock = 0;
1324  lastScore = 0;
1325 
1326  LOG(VB_COMMFLAG, LOG_INFO, "Initial Block pass");
1327  LOG(VB_COMMFLAG, LOG_DEBUG,
1328  "Block StTime StFrm EndFrm Frames Secs "
1329  "Bf Lg Cnt RT Cnt SC Cnt SC Rt FmtMch AspMch Score");
1330  LOG(VB_COMMFLAG, LOG_INFO,
1331  "----- ------ ------ ------ ------ ------- "
1332  "--- ------ ------ ------ ----- ------ ------ -----");
1333  while (curBlock <= maxBlock)
1334  {
1335  fbp = &fblock[curBlock];
1336 
1337  msg = FORMAT_MSG(curBlock, fbp);
1338  LOG(VB_COMMFLAG, LOG_DEBUG, msg);
1339 
1340  if (fbp->frames > m_fps)
1341  {
1342  if (m_verboseDebugging)
1343  LOG(VB_COMMFLAG, LOG_DEBUG,
1344  QString(" FRAMES > %1").arg(m_fps));
1345 
1346  if (fbp->length > m_commDetectMaxCommLength)
1347  {
1348  if (m_verboseDebugging)
1349  LOG(VB_COMMFLAG, LOG_DEBUG,
1350  " length > max comm length, +20");
1351  fbp->score += 20;
1352  }
1353 
1355  {
1356  if (m_verboseDebugging)
1357  LOG(VB_COMMFLAG, LOG_DEBUG,
1358  " length > max comm break length, +20");
1359  fbp->score += 20;
1360  }
1361 
1362  if ((fbp->length > 4) &&
1363  (fbp->logoCount > (fbp->frames * 0.60)) &&
1364  (fbp->bfCount < (fbp->frames * 0.10)))
1365  {
1366  if (m_verboseDebugging)
1367  LOG(VB_COMMFLAG, LOG_DEBUG,
1368  " length > 4 && logoCount > frames * 0.60 && "
1369  "bfCount < frames * .10");
1371  {
1372  if (m_verboseDebugging)
1373  LOG(VB_COMMFLAG, LOG_DEBUG,
1374  " length > max comm break length, +20");
1375  fbp->score += 20;
1376  }
1377  else
1378  {
1379  if (m_verboseDebugging)
1380  LOG(VB_COMMFLAG, LOG_DEBUG,
1381  " length <= max comm break length, +10");
1382  fbp->score += 10;
1383  }
1384  }
1385 
1386  if ((m_logoInfoAvailable) &&
1387  (fbp->logoCount < (fbp->frames * 0.50)))
1388  {
1389  if (m_verboseDebugging)
1390  LOG(VB_COMMFLAG, LOG_DEBUG,
1391  " logoInfoAvailable && logoCount < frames * .50, "
1392  "-10");
1393  fbp->score -= 10;
1394  }
1395 
1396  if (fbp->ratingCount > (fbp->frames * 0.05))
1397  {
1398  if (m_verboseDebugging)
1399  LOG(VB_COMMFLAG, LOG_DEBUG,
1400  " rating symbol present > 5% of time, +20");
1401  fbp->score += 20;
1402  }
1403 
1404  if ((fbp->scRate > 1.0) &&
1405  (fbp->logoCount < (fbp->frames * .90)))
1406  {
1407  if (m_verboseDebugging)
1408  LOG(VB_COMMFLAG, LOG_DEBUG, " scRate > 1.0, -10");
1409  fbp->score -= 10;
1410 
1411  if (fbp->scRate > 2.0)
1412  {
1413  if (m_verboseDebugging)
1414  LOG(VB_COMMFLAG, LOG_DEBUG, " scRate > 2.0, -10");
1415  fbp->score -= 10;
1416  }
1417  }
1418 
1419  if ((!m_decoderFoundAspectChanges) &&
1420  (fbp->formatMatch < (fbp->frames * .10)))
1421  {
1422  if (m_verboseDebugging)
1423  LOG(VB_COMMFLAG, LOG_DEBUG,
1424  " < 10% of frames match show letter/pillar-box "
1425  "format, -20");
1426  fbp->score -= 20;
1427  }
1428 
1429  if ((abs((int)(fbp->frames - (15 * m_fps))) < 5 ) ||
1430  (abs((int)(fbp->frames - (30 * m_fps))) < 6 ) ||
1431  (abs((int)(fbp->frames - (60 * m_fps))) < 8 ))
1432  {
1433  if (m_verboseDebugging)
1434  LOG(VB_COMMFLAG, LOG_DEBUG,
1435  " block appears to be standard comm length, -10");
1436  fbp->score -= 10;
1437  }
1438  }
1439  else
1440  {
1441  if (m_verboseDebugging)
1442  LOG(VB_COMMFLAG, LOG_DEBUG,
1443  QString(" FRAMES <= %1").arg(m_fps));
1444 
1445  if ((m_logoInfoAvailable) &&
1446  (fbp->start >= firstLogoFrame) &&
1447  (fbp->logoCount == 0))
1448  {
1449  if (m_verboseDebugging)
1450  LOG(VB_COMMFLAG, LOG_DEBUG,
1451  " logoInfoAvailable && logoCount == 0, -10");
1452  fbp->score -= 10;
1453  }
1454 
1455  if ((!m_decoderFoundAspectChanges) &&
1456  (fbp->formatMatch < (fbp->frames * .10)))
1457  {
1458  if (m_verboseDebugging)
1459  LOG(VB_COMMFLAG, LOG_DEBUG,
1460  " < 10% of frames match show letter/pillar-box "
1461  "format, -10");
1462  fbp->score -= 10;
1463  }
1464 
1465  if (fbp->ratingCount > (fbp->frames * 0.25))
1466  {
1467  if (m_verboseDebugging)
1468  LOG(VB_COMMFLAG, LOG_DEBUG,
1469  " rating symbol present > 25% of time, +10");
1470  fbp->score += 10;
1471  }
1472  }
1473 
1475  (fbp->aspectMatch < (fbp->frames * .10)))
1476  {
1477  if (m_verboseDebugging)
1478  LOG(VB_COMMFLAG, LOG_DEBUG,
1479  " < 10% of frames match show aspect, -20");
1480  fbp->score -= 20;
1481  }
1482 
1483  msg = FORMAT_MSG("NOW", fbp);
1484  LOG(VB_COMMFLAG, LOG_DEBUG, msg);
1485 
1486  lastScore = fbp->score;
1487  curBlock++;
1488  }
1489 
1490  curBlock = 0;
1491  lastScore = 0;
1492 
1493  LOG(VB_COMMFLAG, LOG_DEBUG, "============================================");
1494  LOG(VB_COMMFLAG, LOG_INFO, "Second Block pass");
1495  LOG(VB_COMMFLAG, LOG_DEBUG,
1496  "Block StTime StFrm EndFrm Frames Secs "
1497  "Bf Lg Cnt RT Cnt SC Cnt SC Rt FmtMch AspMch Score");
1498  LOG(VB_COMMFLAG, LOG_DEBUG,
1499  "----- ------ ------ ------ ------ ------- "
1500  "--- ------ ------ ------ ----- ------ ------ -----");
1501  while (curBlock <= maxBlock)
1502  {
1503  fbp = &fblock[curBlock];
1504 
1505  msg = FORMAT_MSG(curBlock, fbp);
1506  LOG(VB_COMMFLAG, LOG_DEBUG, msg);
1507 
1508  if ((curBlock > 0) && (curBlock < maxBlock))
1509  {
1510  int nextScore = fblock[curBlock + 1].score;
1511 
1512  if ((lastScore < 0) && (nextScore < 0) && (fbp->length < 35))
1513  {
1514  if (m_verboseDebugging)
1515  LOG(VB_COMMFLAG, LOG_DEBUG,
1516  " lastScore < 0 && nextScore < 0 "
1517  "&& length < 35, setting -10");
1518  fbp->score -= 10;
1519  }
1520 
1521  if ((fbp->bfCount > (fbp->frames * 0.95)) &&
1522  (fbp->frames < (2*m_fps)) &&
1523  (lastScore < 0 && nextScore < 0))
1524  {
1525  if (m_verboseDebugging)
1526  LOG(VB_COMMFLAG, LOG_DEBUG,
1527  " blanks > frames * 0.95 && frames < 2*m_fps && "
1528  "lastScore < 0 && nextScore < 0, setting -10");
1529  fbp->score -= 10;
1530  }
1531 
1532  if ((fbp->frames < (120*m_fps)) &&
1533  (lastScore < 0) &&
1534  (fbp->score > 0) &&
1535  (fbp->score < 20) &&
1536  (nextScore < 0))
1537  {
1538  if (m_verboseDebugging)
1539  LOG(VB_COMMFLAG, LOG_DEBUG,
1540  " frames < 120 * m_fps && (-20 < lastScore < 0) && "
1541  "thisScore > 0 && nextScore < 0, setting score = -10");
1542  fbp->score = -10;
1543  }
1544 
1545  if ((fbp->frames < (30*m_fps)) &&
1546  (lastScore > 0) &&
1547  (fbp->score < 0) &&
1548  (fbp->score > -20) &&
1549  (nextScore > 0))
1550  {
1551  if (m_verboseDebugging)
1552  LOG(VB_COMMFLAG, LOG_DEBUG,
1553  " frames < 30 * m_fps && (0 < lastScore < 20) && "
1554  "thisScore < 0 && nextScore > 0, setting score = 10");
1555  fbp->score = 10;
1556  }
1557  }
1558 
1559  if ((fbp->score == 0) && (lastScore > 30))
1560  {
1561  int offset = 1;
1562  while(((curBlock + offset) <= maxBlock) &&
1563  (fblock[curBlock + offset].frames < (2 * m_fps)) &&
1564  (fblock[curBlock + offset].score == 0))
1565  offset++;
1566 
1567  if ((curBlock + offset) <= maxBlock)
1568  {
1569  offset--;
1570  if (fblock[curBlock + offset + 1].score > 0)
1571  {
1572  for (; offset >= 0; offset--)
1573  {
1574  fblock[curBlock + offset].score += 10;
1575  if (m_verboseDebugging)
1576  LOG(VB_COMMFLAG, LOG_DEBUG,
1577  QString(" Setting block %1 score +10")
1578  .arg(curBlock+offset));
1579  }
1580  }
1581  else if (fblock[curBlock + offset + 1].score < 0)
1582  {
1583  for (; offset >= 0; offset--)
1584  {
1585  fblock[curBlock + offset].score -= 10;
1586  if (m_verboseDebugging)
1587  LOG(VB_COMMFLAG, LOG_DEBUG,
1588  QString(" Setting block %1 score -10")
1589  .arg(curBlock+offset));
1590  }
1591  }
1592  }
1593  }
1594 
1595  msg = FORMAT_MSG("NOW", fbp);
1596  LOG(VB_COMMFLAG, LOG_DEBUG, msg);
1597 
1598  lastScore = fbp->score;
1599  curBlock++;
1600  }
1601 
1602  LOG(VB_COMMFLAG, LOG_DEBUG, "============================================");
1603  LOG(VB_COMMFLAG, LOG_INFO, "FINAL Block stats");
1604  LOG(VB_COMMFLAG, LOG_DEBUG,
1605  "Block StTime StFrm EndFrm Frames Secs "
1606  "Bf Lg Cnt RT Cnt SC Cnt SC Rt FmtMch AspMch Score");
1607  LOG(VB_COMMFLAG, LOG_DEBUG,
1608  "----- ------ ------ ------ ------ ------- "
1609  "--- ------ ------ ------ ----- ------ ------ -----");
1610  curBlock = 0;
1611  lastScore = 0;
1612  int64_t breakStart = -1;
1613  while (curBlock <= maxBlock)
1614  {
1615  fbp = &fblock[curBlock];
1616  int thisScore = fbp->score;
1617 
1618  if ((breakStart >= 0) &&
1619  ((fbp->end - breakStart) > (m_commDetectMaxCommBreakLength * m_fps)))
1620  {
1621  if (((fbp->start - breakStart) >
1623  (breakStart == 0))
1624  {
1625  if (m_verboseDebugging)
1626  LOG(VB_COMMFLAG, LOG_DEBUG,
1627  QString("Closing commercial block at start of "
1628  "frame block %1 with length %2, frame "
1629  "block length of %3 frames would put comm "
1630  "block length over max of %4 seconds.")
1631  .arg(curBlock).arg(fbp->start - breakStart)
1632  .arg(fbp->frames)
1634 
1635  m_commBreakMap[breakStart] = MARK_COMM_START;
1637  lastStart = breakStart;
1638  lastEnd = fbp->start;
1639  breakStart = -1;
1640  }
1641  else
1642  {
1643  if (m_verboseDebugging)
1644  LOG(VB_COMMFLAG, LOG_DEBUG,
1645  QString("Ignoring what appears to be commercial"
1646  " block at frame %1 with length %2, "
1647  "length of %3 frames would put comm "
1648  "block length under min of %4 seconds.")
1649  .arg(breakStart)
1650  .arg(fbp->start - breakStart)
1651  .arg(fbp->frames)
1653  breakStart = -1;
1654  }
1655  }
1656  if (thisScore == 0)
1657  {
1658  thisScore = lastScore;
1659  }
1660  else if (thisScore < 0)
1661  {
1662  if ((lastScore > 0) || (curBlock == 0))
1663  {
1664  if ((fbp->start - lastEnd) < (m_commDetectMinShowLength * m_fps))
1665  {
1666  m_commBreakMap.remove(lastStart);
1667  m_commBreakMap.remove(lastEnd);
1668  breakStart = lastStart;
1669 
1670  if (m_verboseDebugging)
1671  {
1672  if (breakStart)
1673  LOG(VB_COMMFLAG, LOG_DEBUG,
1674  QString("ReOpening commercial block at "
1675  "frame %1 because show less than "
1676  "%2 seconds")
1677  .arg(breakStart)
1679  else
1680  LOG(VB_COMMFLAG, LOG_DEBUG,
1681  "Opening initial commercial block "
1682  "at start of recording, block 0.");
1683  }
1684  }
1685  else
1686  {
1687  breakStart = fbp->start;
1688 
1689  if (m_verboseDebugging)
1690  LOG(VB_COMMFLAG, LOG_DEBUG,
1691  QString("Starting new commercial block at "
1692  "frame %1 from start of frame block %2")
1693  .arg(fbp->start).arg(curBlock));
1694  }
1695  }
1696  else if (curBlock == maxBlock)
1697  {
1698  if ((fbp->end - breakStart) >
1700  {
1701  if (fbp->end <=
1702  ((int64_t)m_framesProcessed - (int64_t)(2 * m_fps) - 2))
1703  {
1704  if (m_verboseDebugging)
1705  LOG(VB_COMMFLAG, LOG_DEBUG,
1706  QString("Closing final commercial block at "
1707  "frame %1").arg(fbp->end));
1708 
1709  m_commBreakMap[breakStart] = MARK_COMM_START;
1711  lastStart = breakStart;
1712  lastEnd = fbp->end;
1713  breakStart = -1;
1714  }
1715  }
1716  else
1717  {
1718  if (m_verboseDebugging)
1719  LOG(VB_COMMFLAG, LOG_DEBUG,
1720  QString("Ignoring what appears to be commercial"
1721  " block at frame %1 with length %2, "
1722  "length of %3 frames would put comm "
1723  "block length under min of %4 seconds.")
1724  .arg(breakStart)
1725  .arg(fbp->start - breakStart)
1726  .arg(fbp->frames)
1728  breakStart = -1;
1729  }
1730  }
1731  }
1732  else if ((thisScore > 0) &&
1733  (lastScore < 0) &&
1734  (breakStart != -1))
1735  {
1736  if (((fbp->start - breakStart) >
1738  (breakStart == 0))
1739  {
1740  m_commBreakMap[breakStart] = MARK_COMM_START;
1742  lastStart = breakStart;
1743  lastEnd = fbp->start;
1744 
1745  if (m_verboseDebugging)
1746  LOG(VB_COMMFLAG, LOG_DEBUG,
1747  QString("Closing commercial block at frame %1")
1748  .arg(fbp->start));
1749  }
1750  else
1751  {
1752  if (m_verboseDebugging)
1753  LOG(VB_COMMFLAG, LOG_DEBUG,
1754  QString("Ignoring what appears to be commercial "
1755  "block at frame %1 with length %2, "
1756  "length of %3 frames would put comm block "
1757  "length under min of %4 seconds.")
1758  .arg(breakStart)
1759  .arg(fbp->start - breakStart)
1760  .arg(fbp->frames)
1762  }
1763  breakStart = -1;
1764  }
1765 
1766  msg = FORMAT_MSG(curBlock, fbp);
1767  LOG(VB_COMMFLAG, LOG_DEBUG, msg);
1768 
1769  // cppcheck-suppress unreadVariable
1770  lastScore = thisScore;
1771  curBlock++;
1772  }
1773 
1774  if ((breakStart != -1) &&
1775  (breakStart <= ((int64_t)m_framesProcessed - (int64_t)(2 * m_fps) - 2)))
1776  {
1777  if (m_verboseDebugging)
1778  LOG(VB_COMMFLAG, LOG_DEBUG,
1779  QString("Closing final commercial block started at "
1780  "block %1 and going to end of program. length "
1781  "is %2 frames")
1782  .arg(curBlock)
1783  .arg((m_framesProcessed - breakStart - 1)));
1784 
1785  m_commBreakMap[breakStart] = MARK_COMM_START;
1786  // Create what is essentially an open-ended final skip region
1787  // by setting the end point 10 seconds past the end of the
1788  // recording.
1790  }
1791 
1792  // include/exclude blanks from comm breaks
1793  tmpCommMap = m_commBreakMap;
1794  m_commBreakMap.clear();
1795 
1796  if (m_verboseDebugging)
1797  LOG(VB_COMMFLAG, LOG_DEBUG,
1798  "Adjusting start/end marks according to blanks.");
1799  for (it = tmpCommMap.begin(); it != tmpCommMap.end(); ++it)
1800  {
1801  if (*it == MARK_COMM_START)
1802  {
1803  uint64_t lastStartLower = it.key();
1804  uint64_t lastStartUpper = it.key();
1805  while ((lastStartLower > 0) &&
1806  ((frameInfo[lastStartLower - 1].flagMask & COMM_FRAME_BLANK) != 0))
1807  lastStartLower--;
1808  while ((lastStartUpper < (m_framesProcessed - (2 * m_fps))) &&
1809  ((frameInfo[lastStartUpper + 1].flagMask & COMM_FRAME_BLANK) != 0))
1810  lastStartUpper++;
1811  uint64_t adj = (lastStartUpper - lastStartLower) / 2;
1812  if (adj > MAX_BLANK_FRAMES)
1813  adj = MAX_BLANK_FRAMES;
1814  lastStart = lastStartLower + adj;
1815 
1816  if (m_verboseDebugging)
1817  LOG(VB_COMMFLAG, LOG_DEBUG, QString("Start Mark: %1 -> %2")
1818  .arg(it.key()).arg(lastStart));
1819 
1820  m_commBreakMap[lastStart] = MARK_COMM_START;
1821  }
1822  else
1823  {
1824  uint64_t lastEndLower = it.key();
1825  uint64_t lastEndUpper = it.key();
1826  while ((lastEndUpper < (m_framesProcessed - (2 * m_fps))) &&
1827  ((frameInfo[lastEndUpper + 1].flagMask & COMM_FRAME_BLANK) != 0))
1828  lastEndUpper++;
1829  while ((lastEndLower > 0) &&
1830  ((frameInfo[lastEndLower - 1].flagMask & COMM_FRAME_BLANK) != 0))
1831  lastEndLower--;
1832  uint64_t adj = (lastEndUpper - lastEndLower) / 2;
1833  if (adj > MAX_BLANK_FRAMES)
1834  adj = MAX_BLANK_FRAMES;
1835  lastEnd = lastEndUpper - adj;
1836 
1837  if (m_verboseDebugging)
1838  LOG(VB_COMMFLAG, LOG_DEBUG, QString("End Mark : %1 -> %2")
1839  .arg(it.key()).arg(lastEnd));
1840 
1841  m_commBreakMap[lastEnd] = MARK_COMM_END;
1842  }
1843  }
1844 
1845  delete [] fblock;
1846 }
1847 
1848 
1850 {
1851  LOG(VB_COMMFLAG, LOG_INFO, "CommDetect::BuildBlankFrameCommList()");
1852 
1853  long long *bframes = new long long[m_blankFrameMap.count()*2];
1854  long long *c_start = new long long[m_blankFrameMap.count()];
1855  long long *c_end = new long long[m_blankFrameMap.count()];
1856  int frames = 0;
1857  int commercials = 0;
1858 
1859  m_blankCommMap.clear();
1860 
1861  for (auto it = m_blankFrameMap.begin(); it != m_blankFrameMap.end(); ++it)
1862  bframes[frames++] = it.key();
1863 
1864  if (frames == 0)
1865  {
1866  delete[] c_start;
1867  delete[] c_end;
1868  delete[] bframes;
1869  return;
1870  }
1871 
1872  // detect individual commercials from blank frames
1873  // commercial end is set to frame right before ending blank frame to
1874  // account for instances with only a single blank frame between comms.
1875  for(int i = 0; i < frames; i++ )
1876  {
1877  for(int x=i+1; x < frames; x++ )
1878  {
1879  // check for various length spots since some channels don't
1880  // have blanks inbetween commercials just at the beginning and
1881  // end of breaks
1882  int gap_length = bframes[x] - bframes[i];
1883  if (((m_aggressiveDetection) &&
1884  ((abs((int)(gap_length - (5 * m_fps))) < 5 ) ||
1885  (abs((int)(gap_length - (10 * m_fps))) < 7 ) ||
1886  (abs((int)(gap_length - (15 * m_fps))) < 10 ) ||
1887  (abs((int)(gap_length - (20 * m_fps))) < 11 ) ||
1888  (abs((int)(gap_length - (30 * m_fps))) < 12 ) ||
1889  (abs((int)(gap_length - (40 * m_fps))) < 1 ) ||
1890  (abs((int)(gap_length - (45 * m_fps))) < 1 ) ||
1891  (abs((int)(gap_length - (60 * m_fps))) < 15 ) ||
1892  (abs((int)(gap_length - (90 * m_fps))) < 10 ) ||
1893  (abs((int)(gap_length - (120 * m_fps))) < 10 ))) ||
1894  ((!m_aggressiveDetection) &&
1895  ((abs((int)(gap_length - (5 * m_fps))) < 11 ) ||
1896  (abs((int)(gap_length - (10 * m_fps))) < 13 ) ||
1897  (abs((int)(gap_length - (15 * m_fps))) < 16 ) ||
1898  (abs((int)(gap_length - (20 * m_fps))) < 17 ) ||
1899  (abs((int)(gap_length - (30 * m_fps))) < 18 ) ||
1900  (abs((int)(gap_length - (40 * m_fps))) < 3 ) ||
1901  (abs((int)(gap_length - (45 * m_fps))) < 3 ) ||
1902  (abs((int)(gap_length - (60 * m_fps))) < 20 ) ||
1903  (abs((int)(gap_length - (90 * m_fps))) < 20 ) ||
1904  (abs((int)(gap_length - (120 * m_fps))) < 20 ))))
1905  {
1906  c_start[commercials] = bframes[i];
1907  c_end[commercials] = bframes[x] - 1;
1908  commercials++;
1909  i = x-1;
1910  x = frames;
1911  }
1912 
1913  if ((!m_aggressiveDetection) &&
1914  ((abs((int)(gap_length - (30 * m_fps))) < (int)(m_fps * 0.85)) ||
1915  (abs((int)(gap_length - (60 * m_fps))) < (int)(m_fps * 0.95)) ||
1916  (abs((int)(gap_length - (90 * m_fps))) < (int)(m_fps * 1.05)) ||
1917  (abs((int)(gap_length - (120 * m_fps))) < (int)(m_fps * 1.15))) &&
1918  ((x + 2) < frames) &&
1919  ((i + 2) < frames) &&
1920  ((bframes[i] + 1) == bframes[i+1]) &&
1921  ((bframes[x] + 1) == bframes[x+1]))
1922  {
1923  c_start[commercials] = bframes[i];
1924  c_end[commercials] = bframes[x];
1925  commercials++;
1926  i = x;
1927  x = frames;
1928  }
1929  }
1930  }
1931 
1932  int i = 0;
1933 
1934  // don't allow single commercial at head
1935  // of show unless followed by another
1936  if ((commercials > 1) &&
1937  (c_end[0] < (33 * m_fps)) &&
1938  (c_start[1] > (c_end[0] + 40 * m_fps)))
1939  i = 1;
1940 
1941  // eliminate any blank frames at end of commercials
1942  bool first_comm = true;
1943  for(; i < (commercials-1); i++)
1944  {
1945  long long r = c_start[i];
1946  long long adjustment = 0;
1947 
1948  if ((r < (30 * m_fps)) &&
1949  (first_comm))
1950  r = 1;
1951 
1953 
1954  r = c_end[i];
1955  if ( i < (commercials-1))
1956  {
1957  int x = 0;
1958  for(x = 0; x < (frames-1); x++)
1959  if (bframes[x] == r)
1960  break;
1961  while((x < (frames-1)) &&
1962  ((bframes[x] + 1 ) == bframes[x+1]) &&
1963  (bframes[x+1] < c_start[i+1]))
1964  {
1965  r++;
1966  x++;
1967  }
1968 
1969  while((m_blankFrameMap.contains(r+1)) &&
1970  (c_start[i+1] != (r+1)))
1971  {
1972  r++;
1973  adjustment++;
1974  }
1975  }
1976  else
1977  {
1978  while(m_blankFrameMap.contains(r+1))
1979  {
1980  r++;
1981  adjustment++;
1982  }
1983  }
1984 
1985  adjustment /= 2;
1986  if (adjustment > MAX_BLANK_FRAMES)
1987  adjustment = MAX_BLANK_FRAMES;
1988  r -= adjustment;
1990  first_comm = false;
1991  }
1992 
1993  m_blankCommMap[c_start[i]] = MARK_COMM_START;
1994  m_blankCommMap[c_end[i]] = MARK_COMM_END;
1995 
1996  delete[] c_start;
1997  delete[] c_end;
1998  delete[] bframes;
1999 
2000  LOG(VB_COMMFLAG, LOG_INFO, "Blank-Frame Commercial Map" );
2001  for(auto it = m_blankCommMap.begin(); it != m_blankCommMap.end(); ++it)
2002  LOG(VB_COMMFLAG, LOG_INFO, QString(" %1:%2")
2003  .arg(it.key()).arg(*it));
2004 
2006 
2007  LOG(VB_COMMFLAG, LOG_INFO, "Merged Blank-Frame Commercial Break Map" );
2008  for(auto it = m_blankCommBreakMap.begin(); it != m_blankCommBreakMap.end(); ++it)
2009  LOG(VB_COMMFLAG, LOG_INFO, QString(" %1:%2")
2010  .arg(it.key()).arg(*it));
2011 }
2012 
2013 
2015 {
2016  int section_start = -1;
2017  int seconds = (int)(m_framesProcessed / m_fps);
2018  int *sc_histogram = new int[seconds+1];
2019 
2020  m_sceneCommBreakMap.clear();
2021 
2022  memset(sc_histogram, 0, (seconds+1)*sizeof(int));
2023  for (uint64_t f = 1; f <= m_framesProcessed; f++)
2024  {
2025  if (m_sceneMap.contains(f))
2026  sc_histogram[(uint64_t)(f / m_fps)]++;
2027  }
2028 
2029  for(long long s = 0; s < (seconds + 1); s++)
2030  {
2031  if (sc_histogram[s] > 2)
2032  {
2033  if (section_start == -1)
2034  {
2035  long long f = (long long)(s * m_fps);
2036  for(int i = 0; i < m_fps; i++, f++)
2037  {
2038  if (m_sceneMap.contains(f))
2039  {
2041  i = (int)(m_fps) + 1;
2042  }
2043  }
2044  }
2045 
2046  section_start = s;
2047  }
2048 
2049  if ((section_start >= 0) &&
2050  (s > (section_start + 32)))
2051  {
2052  long long f = (long long)(section_start * m_fps);
2053  bool found_end = false;
2054 
2055  for(int i = 0; i < m_fps; i++, f++)
2056  {
2057  if (m_sceneMap.contains(f))
2058  {
2059  frm_dir_map_t::iterator dit = m_sceneCommBreakMap.find(f);
2060  if (dit != m_sceneCommBreakMap.end())
2061  m_sceneCommBreakMap.erase(dit);
2062  else
2064  i = (int)(m_fps) + 1;
2065  found_end = true;
2066  }
2067  }
2068  section_start = -1;
2069 
2070  if (!found_end)
2071  {
2072  f = (long long)(section_start * m_fps);
2074  }
2075  }
2076  }
2077  delete[] sc_histogram;
2078 
2079  if (section_start >= 0)
2081 
2082  frm_dir_map_t deleteMap;
2083  frm_dir_map_t::iterator it = m_sceneCommBreakMap.begin();
2084  frm_dir_map_t::iterator prev = it;
2085  if (it != m_sceneCommBreakMap.end())
2086  {
2087  ++it;
2088  while (it != m_sceneCommBreakMap.end())
2089  {
2090  if ((*it == MARK_COMM_END) &&
2091  (it.key() - prev.key()) < (30 * m_fps))
2092  {
2093  deleteMap[it.key()] = MARK_CUT_START;
2094  deleteMap[prev.key()] = MARK_CUT_START;
2095  }
2096  ++prev;
2097  if (it != m_sceneCommBreakMap.end())
2098  ++it;
2099  }
2100 
2101  frm_dir_map_t::iterator dit;
2102  for (dit = deleteMap.begin(); dit != deleteMap.end(); ++dit)
2103  m_sceneCommBreakMap.remove(dit.key());
2104  }
2105 
2106  LOG(VB_COMMFLAG, LOG_INFO, "Scene-Change Commercial Break Map" );
2107  for (it = m_sceneCommBreakMap.begin(); it != m_sceneCommBreakMap.end(); ++it)
2108  {
2109  LOG(VB_COMMFLAG, LOG_INFO, QString(" %1:%2")
2110  .arg(it.key()).arg(*it));
2111  }
2112 }
2113 
2114 
2116 {
2117  show_map_t showmap;
2118  GetLogoCommBreakMap(showmap);
2119  CondenseMarkMap(showmap, (int)(25 * m_fps), (int)(30 * m_fps));
2121 
2122  frm_dir_map_t::iterator it;
2123  LOG(VB_COMMFLAG, LOG_INFO, "Logo Commercial Break Map" );
2124  for(it = m_logoCommBreakMap.begin(); it != m_logoCommBreakMap.end(); ++it)
2125  LOG(VB_COMMFLAG, LOG_INFO, QString(" %1:%2")
2126  .arg(it.key()).arg(*it));
2127 }
2128 
2130 {
2131  frm_dir_map_t::iterator it;
2132  frm_dir_map_t::iterator prev;
2133  QMap<long long, long long> tmpMap;
2134  QMap<long long, long long>::Iterator tmpMap_it;
2135  QMap<long long, long long>::Iterator tmpMap_prev;
2136 
2137  m_blankCommBreakMap.clear();
2138 
2139  if (m_blankCommMap.isEmpty())
2140  return;
2141 
2142  for (it = m_blankCommMap.begin(); it != m_blankCommMap.end(); ++it)
2143  m_blankCommBreakMap[it.key()] = *it;
2144 
2145  if (m_blankCommBreakMap.isEmpty())
2146  return;
2147 
2148  it = m_blankCommMap.begin();
2149  prev = it;
2150  ++it;
2151  for(; it != m_blankCommMap.end(); ++it, ++prev)
2152  {
2153  // if next commercial starts less than 15*fps frames away then merge
2154  if ((((prev.key() + 1) == it.key()) ||
2155  ((prev.key() + (15 * m_fps)) > it.key())) &&
2156  (*prev == MARK_COMM_END) &&
2157  (*it == MARK_COMM_START))
2158  {
2159  m_blankCommBreakMap.remove(prev.key());
2160  m_blankCommBreakMap.remove(it.key());
2161  }
2162  }
2163 
2164 
2165  // make temp copy of commercial break list
2166  it = m_blankCommBreakMap.begin();
2167  prev = it;
2168  ++it;
2169  tmpMap[prev.key()] = it.key();
2170  for(; it != m_blankCommBreakMap.end(); ++it, ++prev)
2171  {
2172  if ((*prev == MARK_COMM_START) &&
2173  (*it == MARK_COMM_END))
2174  tmpMap[prev.key()] = it.key();
2175  }
2176 
2177  tmpMap_it = tmpMap.begin();
2178  tmpMap_prev = tmpMap_it;
2179  tmpMap_it++;
2180  for(; tmpMap_it != tmpMap.end(); ++tmpMap_it, ++tmpMap_prev)
2181  {
2182  // if we find any segments less than 35 seconds between commercial
2183  // breaks include those segments in the commercial break.
2184  if (((*tmpMap_prev + (35 * m_fps)) > tmpMap_it.key()) &&
2185  ((*tmpMap_prev - tmpMap_prev.key()) > (35 * m_fps)) &&
2186  ((*tmpMap_it - tmpMap_it.key()) > (35 * m_fps)))
2187  {
2188  m_blankCommBreakMap.remove(*tmpMap_prev);
2189  m_blankCommBreakMap.remove(tmpMap_it.key());
2190  }
2191  }
2192 }
2193 
2195  uint64_t f, const frm_dir_map_t &breakMap) const
2196 {
2197  for (uint64_t i = f; i < m_framesProcessed; i++)
2198  {
2199  if (breakMap.contains(i))
2200  {
2201  int type = breakMap[i];
2202  if ((type == MARK_COMM_END) || (i == f))
2203  return true;
2204  if (type == MARK_COMM_START)
2205  return false;
2206  }
2207  }
2208 
2209  // We want from f down to 0, but without wrapping the counter to negative
2210  // on an unsigned counter.
2211  for (uint64_t i = (f + 1); i-- > 0; )
2212  {
2213  if (breakMap.contains(i))
2214  {
2215  int type = breakMap[i];
2216  if ((type == MARK_COMM_START) || (i == f))
2217  return true;
2218  if (type == MARK_COMM_END)
2219  return false;
2220  }
2221  }
2222 
2223  return false;
2224 }
2225 
2227 {
2228  frm_dir_map_t::iterator it;
2229  QString msg;
2230 
2231  LOG(VB_COMMFLAG, LOG_INFO,
2232  "---------------------------------------------------");
2233  for (it = map.begin(); it != map.end(); ++it)
2234  {
2235  long long frame = it.key();
2236  int flag = *it;
2237  int my_fps = (int)ceil(m_fps);
2238  int hour = (frame / my_fps) / 60 / 60;
2239  int min = (frame / my_fps) / 60 - (hour * 60);
2240  int sec = (frame / my_fps) - (min * 60) - (hour * 60 * 60);
2241  int frm = frame - ((sec * my_fps) + (min * 60 * my_fps) +
2242  (hour * 60 * 60 * my_fps));
2243  int my_sec = (int)(frame / my_fps);
2244  msg = QString("%1 : %2 (%3:%4:%5.%6) (%7)")
2245  .arg(frame, 7).arg(flag).arg(hour, 2, 10, QChar('0')).arg(min, 2, 10, QChar('0'))
2246  .arg(sec, 2, 10, QChar('0')).arg(frm, 2, 10, QChar('0')).arg(my_sec);
2247  LOG(VB_COMMFLAG, LOG_INFO, msg);
2248  }
2249  LOG(VB_COMMFLAG, LOG_INFO,
2250  "---------------------------------------------------");
2251 }
2252 
2254  int length)
2255 {
2256  show_map_t::iterator it;
2257  show_map_t::iterator prev;
2258  show_map_t tmpMap;
2259 
2260  if (map.size() <= 2)
2261  return;
2262 
2263  // merge any segments less than 'spacing' frames apart from each other
2264  LOG(VB_COMMFLAG, LOG_INFO, "Commercial Map Before condense:" );
2265  for (it = map.begin(); it != map.end(); ++it)
2266  {
2267  LOG(VB_COMMFLAG, LOG_INFO, QString(" %1:%2")
2268  .arg(it.key()).arg(*it));
2269  tmpMap[it.key()] = *it;
2270  }
2271 
2272  prev = tmpMap.begin();
2273  it = prev;
2274  ++it;
2275  while (it != tmpMap.end())
2276  {
2277  if ((*it == MARK_START) &&
2278  (*prev == MARK_END) &&
2279  ((it.key() - prev.key()) < (uint64_t)spacing))
2280  {
2281  map.remove(prev.key());
2282  map.remove(it.key());
2283  }
2284  ++prev;
2285  ++it;
2286  }
2287 
2288  if (map.empty())
2289  return;
2290 
2291  // delete any segments less than 'length' frames in length
2292  tmpMap.clear();
2293  for (it = map.begin(); it != map.end(); ++it)
2294  tmpMap[it.key()] = *it;
2295 
2296  prev = tmpMap.begin();
2297  it = prev;
2298  ++it;
2299  while (it != tmpMap.end())
2300  {
2301  if ((*prev == MARK_START) &&
2302  (*it == MARK_END) &&
2303  ((it.key() - prev.key()) < (uint64_t)length))
2304  {
2305  map.remove(prev.key());
2306  map.remove(it.key());
2307  }
2308  ++prev;
2309  ++it;
2310  }
2311 
2312  LOG(VB_COMMFLAG, LOG_INFO, "Commercial Map After condense:" );
2313  for (it = map.begin(); it != map.end(); ++it)
2314  LOG(VB_COMMFLAG, LOG_INFO, QString(" %1:%2")
2315  .arg(it.key()).arg(*it));
2316 }
2317 
2319  frm_dir_map_t &out, const show_map_t &in)
2320 {
2321  out.clear();
2322  if (in.empty())
2323  return;
2324 
2325  show_map_t::const_iterator sit;
2326  for (sit = in.begin(); sit != in.end(); ++sit)
2327  {
2328  if (*sit == MARK_START)
2329  out[sit.key()] = MARK_COMM_END;
2330  else
2331  out[sit.key()] = MARK_COMM_START;
2332  }
2333 
2334  frm_dir_map_t::iterator it = out.begin();
2335  if (it == out.end())
2336  return;
2337 
2338  switch (out[it.key()])
2339  {
2340  case MARK_COMM_END:
2341  if (it.key() == 0)
2342  out.remove(0);
2343  else
2344  out[0] = MARK_COMM_START;
2345  break;
2346  case MARK_COMM_START:
2347  break;
2348  default:
2349  out.remove(0);
2350  break;
2351  }
2352 }
2353 
2354 
2355 /* ideas for this method ported back from comskip.c mods by Jere Jones
2356  * which are partially mods based on Myth's original commercial skip
2357  * code written by Chris Pinkham. */
2358 
2360 {
2361  LOG(VB_COMMFLAG, LOG_INFO, "CommDetect::CleanupFrameInfo()");
2362 
2363  // try to account for noisy signal causing blank frames to be undetected
2364  if ((m_framesProcessed > (m_fps * 60)) &&
2365  (m_blankFrameCount < (m_framesProcessed * 0.0004)))
2366  {
2367  int avgHistogram[256];
2368  int minAvg = -1;
2369  int newThreshold = -1;
2370 
2371  LOG(VB_COMMFLAG, LOG_INFO,
2372  QString("ClassicCommDetect: Only found %1 blank frames but "
2373  "wanted at least %2, rechecking data using higher "
2374  "threshold.")
2375  .arg(m_blankFrameCount)
2376  .arg((int)(m_framesProcessed * 0.0004)));
2377  m_blankFrameMap.clear();
2378  m_blankFrameCount = 0;
2379 
2380  memset(avgHistogram, 0, sizeof(avgHistogram));
2381 
2382  for (uint64_t i = 1; i <= m_framesProcessed; i++)
2383  avgHistogram[clamp(frameInfo[i].avgBrightness, 0, 255)] += 1;
2384 
2385  for (int i = 1; i <= 255 && minAvg == -1; i++)
2386  if (avgHistogram[i] > (m_framesProcessed * 0.0004))
2387  minAvg = i;
2388 
2389  newThreshold = minAvg + 3;
2390  LOG(VB_COMMFLAG, LOG_INFO,
2391  QString("Minimum Average Brightness on a frame "
2392  "was %1, will use %2 as new threshold")
2393  .arg(minAvg).arg(newThreshold));
2394 
2395  for (uint64_t i = 1; i <= m_framesProcessed; i++)
2396  {
2397  int value = frameInfo[i].flagMask;
2398  frameInfo[i].flagMask = value & ~COMM_FRAME_BLANK;
2399 
2400  if (( (frameInfo[i].flagMask & COMM_FRAME_BLANK) == 0) &&
2401  (frameInfo[i].avgBrightness < newThreshold))
2402  {
2403  frameInfo[i].flagMask = value | COMM_FRAME_BLANK;
2406  }
2407  }
2408 
2409  LOG(VB_COMMFLAG, LOG_INFO,
2410  QString("Found %1 blank frames using new value")
2411  .arg(m_blankFrameCount));
2412  }
2413 
2414  // try to account for fuzzy logo detection
2415  for (uint64_t i = 1; i <= m_framesProcessed; i++)
2416  {
2417  if ((i < 10) || ((i+10) > m_framesProcessed))
2418  continue;
2419 
2420  int before = 0;
2421  for (int offset = 1; offset <= 10; offset++)
2422  if ((frameInfo[i - offset].flagMask & COMM_FRAME_LOGO_PRESENT) != 0)
2423  before++;
2424 
2425  int after = 0;
2426  for (int offset = 1; offset <= 10; offset++)
2427  if ((frameInfo[i + offset].flagMask & COMM_FRAME_LOGO_PRESENT) != 0)
2428  after++;
2429 
2430  int value = frameInfo[i].flagMask;
2431  if (value == -1)
2432  frameInfo[i].flagMask = 0;
2433 
2434  if (value & COMM_FRAME_LOGO_PRESENT)
2435  {
2436  if ((before < 4) && (after < 4))
2437  frameInfo[i].flagMask = value & ~COMM_FRAME_LOGO_PRESENT;
2438  }
2439  else
2440  {
2441  if ((before > 6) && (after > 6))
2442  frameInfo[i].flagMask = value | COMM_FRAME_LOGO_PRESENT;
2443  }
2444  }
2445 }
2446 
2448 {
2449  LOG(VB_COMMFLAG, LOG_INFO, "CommDetect::GetLogoCommBreakMap()");
2450 
2451  map.clear();
2452 
2453  bool PrevFrameLogo = false;
2454 
2455  for (uint64_t curFrame = 1 ; curFrame <= m_framesProcessed; curFrame++)
2456  {
2457  bool CurrentFrameLogo =
2458  (frameInfo[curFrame].flagMask & COMM_FRAME_LOGO_PRESENT) != 0;
2459 
2460  if (!PrevFrameLogo && CurrentFrameLogo)
2461  map[curFrame] = MARK_START;
2462  else if (PrevFrameLogo && !CurrentFrameLogo)
2463  map[curFrame] = MARK_END;
2464 
2465  PrevFrameLogo = CurrentFrameLogo;
2466  }
2467 }
2468 
2470 {
2471  emit breathe();
2472 }
2473 
2475  ostream &out, const frm_dir_map_t *comm_breaks, bool verbose) const
2476 {
2477  if (verbose)
2478  {
2479  QByteArray tmp = FrameInfoEntry::GetHeader().toLatin1();
2480  out << tmp.constData() << " mark" << endl;
2481  }
2482 
2483  for (long long i = 1; i < m_curFrameNumber; i++)
2484  {
2485  QMap<long long, FrameInfoEntry>::const_iterator it = frameInfo.find(i);
2486  if (it == frameInfo.end())
2487  continue;
2488 
2489  QByteArray atmp = (*it).toString(i, verbose).toLatin1();
2490  out << atmp.constData() << " ";
2491  if (comm_breaks)
2492  {
2493  frm_dir_map_t::const_iterator mit = comm_breaks->find(i);
2494  if (mit != comm_breaks->end())
2495  {
2496  QString tmp = (verbose) ?
2497  toString((MarkTypes)*mit) : QString::number(*mit);
2498  atmp = tmp.toLatin1();
2499 
2500  out << atmp.constData();
2501  }
2502  }
2503  out << "\n";
2504  }
2505 
2506  out << flush;
2507 }
2508 
2509 /* vim: set expandtab tabstop=4 shiftwidth=4: */
int pitches[3]
Y, U, & V pitches.
Definition: mythframe.h:63
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)
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
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)
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:829
QSize GetVideoSize(void) const
Definition: mythplayer.h:174
static 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
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()
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.
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:393
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