26isBlank(
unsigned char median,
float stddev,
unsigned char maxmedian,
29 return ((median < maxmedian) ||
30 ((median == maxmedian) && (stddev <= maxstddev)));
36 return *(
unsigned char*)aa - *(
unsigned char*)bb;
42 float faa = *(
float*)aa;
43 float fbb = *(
float*)bb;
53 unsigned char minval,
unsigned char maxval)
55 return medianval >= minval && medianval <= maxval;
60 const unsigned char *median,
const float *stddev,
61 const unsigned char *monochromatic)
67 const unsigned char MINBLANKMEDIAN = 1;
68 const unsigned char MAXBLANKMEDIAN = 96;
69 const float MEDIANPCTILE = 0.95;
70 const float STDDEVPCTILE = 0.85;
72 long long frameno = 1;
78 long long nblanks = 0;
79 for (frameno = 0; frameno < nframes; frameno++)
81 if (monochromatic[frameno] &&
pickmedian(median[frameno],
82 MINBLANKMEDIAN, MAXBLANKMEDIAN))
89 LOG(VB_COMMFLAG, LOG_INFO,
90 "BlankFrameDetector::computeBlankMap: No blank frames.");
96 auto *blankmedian =
new unsigned char[nblanks];
97 auto *blankstddev =
new float[nblanks];
98 long long blankno = 0;
99 for (frameno = 0; frameno < nframes; frameno++)
101 if (monochromatic[frameno] &&
pickmedian(median[frameno],
102 MINBLANKMEDIAN, MAXBLANKMEDIAN))
104 blankmedian[blankno] = median[frameno];
105 blankstddev[blankno] = stddev[frameno];
111 blankno = std::min(nblanks - 1, (
long long)roundf(nblanks * MEDIANPCTILE));
114 delete []blankmedian;
115 delete []blankstddev;
116 LOG(VB_COMMFLAG, LOG_INFO,
117 "BlankFrameDetector::computeBlankMap: No blank frames. (2)");
120 uchar maxmedian = blankmedian[blankno];
123 long long stddevno = std::min(nblanks - 1, (
long long)roundf(nblanks * STDDEVPCTILE));
124 float maxstddev = blankstddev[stddevno];
128 long long blankno1 = blankno;
129 long long blankno2 = blankno;
130 while (blankno1 > 0 && blankmedian[blankno1] == maxmedian)
132 if (blankmedian[blankno1] != maxmedian)
134 while (blankno2 < nblanks && blankmedian[blankno2] == maxmedian)
136 if (blankno2 == nblanks)
139 long long stddevno1 = stddevno;
140 long long stddevno2 = stddevno;
141 while (stddevno1 > 0 && blankstddev[stddevno1] == maxstddev)
143 if (blankstddev[stddevno1] != maxstddev)
145 while (stddevno2 < nblanks && blankstddev[stddevno2] == maxstddev)
147 if (stddevno2 == nblanks)
150 LOG(VB_COMMFLAG, LOG_INFO,
151 QString(
"Blanks selecting median<=%1 (%2-%3%), stddev<=%4 (%5-%6%)")
153 .arg(blankno1 * 100 / nblanks).arg(blankno2 * 100 / nblanks)
155 .arg(stddevno1 * 100 / nblanks).arg(stddevno2 * 100 / nblanks));
157 delete []blankmedian;
158 delete []blankstddev;
161 if (monochromatic[0] &&
isBlank(median[0], stddev[0], maxmedian, maxstddev))
169 blankMap->insert(0, 0);
173 for (frameno = 1; frameno < nframes; frameno++)
175 if (monochromatic[frameno] &&
isBlank(median[frameno], stddev[frameno],
176 maxmedian, maxstddev))
179 if (sege < frameno - 1)
191 else if (sege == frameno - 1)
194 long long seglen = frameno - segb;
195 blankMap->insert(segb, seglen);
198 if (sege == frameno - 1)
201 long long seglen = frameno - segb;
202 blankMap->insert(segb, seglen);
205 FrameAnalyzer::FrameMap::Iterator iiblank = blankMap->end();
207 if (iiblank.key() + *iiblank < nframes)
213 blankMap->insert(nframes - 1, 0);
228 std::chrono::seconds m_len;
229 std::chrono::seconds m_delta;
231 static constexpr std::array<const breakType,4> kBreakType {{
245 static const int kMinContentLen = (int)roundf(10 * fps);
248 for (FrameAnalyzer::FrameMap::const_iterator iiblank = blankMap->begin();
249 iiblank != blankMap->end();
252 long long brkb = iiblank.key();
253 long long iilen = *iiblank;
254 long long start = brkb + (iilen / 2);
256 for (
const auto&
type : kBreakType)
259 FrameAnalyzer::FrameMap::const_iterator jjblank = iiblank;
260 for (++jjblank; jjblank != blankMap->end(); ++jjblank)
262 long long brke = jjblank.key();
263 long long jjlen = *jjblank;
264 long long end = brke + (jjlen / 2);
266 auto testlen = std::chrono::seconds(lroundf((end - start) / fps));
267 if (testlen >
type.m_len +
type.m_delta)
270 std::chrono::seconds delta = testlen -
type.m_len;
271 delta = std::chrono::abs(delta);
273 if (delta >
type.m_delta)
277 bool inserted =
false;
278 for (
unsigned int jj = 0;; jj++)
280 long long newbrkb = brkb + jj;
283 LOG(VB_COMMFLAG, LOG_INFO,
284 QString(
"BF [%1,%2] ran out of slots")
285 .arg(brkb).arg(brke - 1));
288 if (!breakMap->contains(newbrkb))
290 breakMap->insert(newbrkb, brke - newbrkb);
304 LOG(VB_COMMFLAG, LOG_INFO,
305 "BF coalescing overlapping/nearby breaks ...");
314 bool coalesced =
false;
315 FrameAnalyzer::FrameMap::iterator iibreak = breakMap->begin();
316 while (iibreak != breakMap->end())
318 long long iib = iibreak.key();
319 long long iie = iib + *iibreak;
321 FrameAnalyzer::FrameMap::iterator jjbreak = iibreak;
323 if (jjbreak == breakMap->end())
326 long long jjb = jjbreak.key();
327 long long jje = jjb + *jjbreak;
336 if (iie + kMinContentLen < jjb)
346 breakMap->remove(iib);
347 breakMap->insert(iib, jje - iib);
349 breakMap->erase(jjbreak);
351 iibreak = breakMap->find(iib);
358 FrameAnalyzer::FrameMap::iterator iibreak = breakMap->begin();
359 while (iibreak != breakMap->end())
361 long long iib = iibreak.key();
362 long long iie = iib + *iibreak;
363 iibreak = breakMap->erase(iibreak);
366 auto iter = blankMap->find(iib);
367 if (iter == blankMap->end())
369 long long addb = *iter;
374 iter = blankMap->find(iib);
375 if (iter == blankMap->end())
377 long long adde = *iter;
379 long long sube = adde / 2;
382 breakMap->insert(iib, iie - iib);
389 const QString &debugdir)
390 : m_histogramAnalyzer(
std::move(ha))
401 QString(
"BlankFrameDetector debugLevel %1").arg(
m_debugLevel));
414 LOG(VB_COMMFLAG, LOG_INFO,
415 QString(
"BlankFrameDetector::MythPlayerInited %1x%2")
416 .arg(video_disp_dim.width())
417 .arg(video_disp_dim.height()));
424 long long *pNextFrame)
432 LOG(VB_COMMFLAG, LOG_INFO,
433 QString(
"BlankFrameDetector::analyzeFrame error at frame %1")
444 LOG(VB_COMMFLAG, LOG_INFO, QString(
"BlankFrameDetector::finished(%1)")
469 const int MAXBLANKADJUSTMENT = (int)roundf(5 *
m_fps);
471 LOG(VB_COMMFLAG, LOG_INFO,
"BlankFrameDetector adjusting for logo surplus");
477 for (FrameAnalyzer::FrameMap::const_iterator ii =
478 logoBreakMap->constBegin();
479 ii != logoBreakMap->constEnd();
483 long long iikey = ii.key();
484 long long iibb = iikey - MAXBLANKADJUSTMENT;
485 long long iiee = iikey + MAXBLANKADJUSTMENT;
486 FrameAnalyzer::FrameMap::Iterator jjfound =
m_blankMap.end();
491 long long jjbb = jj.key();
492 long long jjee = jjbb + *jj;
514 long long jjee = jjfound.key() + *jjfound;
530 long long kkkey = ii.key() + *ii;
531 long long kkbb = kkkey - MAXBLANKADJUSTMENT;
532 long long kkee = kkkey + MAXBLANKADJUSTMENT;
533 FrameAnalyzer::FrameMap::Iterator mmfound =
m_blankMap.end();
538 long long mmbb = mm.key();
539 long long mmee = mmbb + *mm;
548 if (mmee < kkkey || mmfound ==
m_blankMap.end())
557 long long mmbb = mmfound.key();
583 for (FrameAnalyzer::FrameMap::const_iterator ii =
584 logoBreakMap->constBegin();
585 ii != logoBreakMap->constEnd();
588 long long iibb = ii.key();
589 long long iiee = iibb + *ii;
590 bool overlap =
false;
594 long long jjbb = jj.key();
595 long long jjee = jjbb + *jj;
607 if (iibb < jjbb && jjbb < iiee)
612 m_breakMap.insert(iibb, std::max(iiee, jjee) - iibb);
615 if (jjbb < iibb && iibb < jjee)
638 LOG(VB_COMMFLAG, LOG_INFO,
"BlankFrameDetector adjusting for "
639 "too little logo coverage (unimplemented)");
655 breaks->insert(bb.key(), *bb);
static constexpr int64_t MAX_BLANK_FRAMES
int computeBreaks(FrameMap *breaks)
int reportTime(void) const override
FrameAnalyzer::FrameMap m_blankMap
std::shared_ptr< HistogramAnalyzer > m_histogramAnalyzer
FrameAnalyzer::FrameMap m_breakMap
enum analyzeFrameResult MythPlayerInited(MythPlayer *player, long long nframes) override
int finished(long long nframes, bool final) override
BlankFrameDetector(std::shared_ptr< HistogramAnalyzer > ha, const QString &debugdir)
int computeForLogoSurplus(const TemplateMatcher *templateMatcher)
static int computeForLogoDeficit(const TemplateMatcher *templateMatcher)
enum analyzeFrameResult analyzeFrame(const MythVideoFrame *frame, long long frameno, long long *pNextFrame) override
static const long long kNextFrame
QMap< long long, long long > FrameMap
int GetNumSetting(const QString &key, int defaultval=0)
QSize GetVideoSize(void) const
float GetFrameRate(void) const
const FrameAnalyzer::FrameMap * getBreaks(void) const
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
void computeBreakMap(FrameAnalyzer::FrameMap *breakMap, const FrameAnalyzer::FrameMap *blankMap, float fps, int debugLevel)
int sort_ascending_float(const void *aa, const void *bb)
bool pickmedian(const unsigned char medianval, unsigned char minval, unsigned char maxval)
void computeBlankMap(FrameAnalyzer::FrameMap *blankMap, long long nframes, const unsigned char *median, const float *stddev, const unsigned char *monochromatic)
bool isBlank(unsigned char median, float stddev, unsigned char maxmedian, float maxstddev)
int sort_ascending_uchar(const void *aa, const void *bb)
void createDebugDirectory(const QString &dirname, const QString &comment)
void frameAnalyzerReportMapms(const FrameAnalyzer::FrameMap *frameMap, float fps, const char *comment)
void frameAnalyzerReportMap(const FrameAnalyzer::FrameMap *frameMap, float fps, const char *comment)