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