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