27#include "libavutil/imgutils.h"
37 const int width = pict->linesize[0];
38 const int size = height * width;
41 for (
int ii = 0; ii < size; ii++)
42 if (pict->data[0][ii])
49 int r2min = std::max(0, rr - radius);
50 int r2max = std::min(height, rr + radius);
52 int c2min = std::max(0, cc - radius);
53 int c2max = std::min(width, cc + radius);
55 for (
int r2 = r2min; r2 <= r2max; r2++)
57 for (
int c2 = c2min; c2 <= c2max; c2++)
59 if (test->data[0][(r2 * width) + c2])
67 int radius,
unsigned short *pscore)
70 const int width = tmpl->linesize[0];
72 if (width != test->linesize[0])
74 LOG(VB_COMMFLAG, LOG_ERR,
75 QString(
"pgm_match widths don't match: %1 != %2")
76 .arg(width).arg(test->linesize[0]));
81 for (
int rr = 0; rr < height; rr++)
83 for (
int cc = 0; cc < width; cc++)
85 if (!tmpl->data[0][(rr * width) + cc])
97 QByteArray fname =
filename.toLocal8Bit();
98 FILE *fp = fopen(fname.constData(),
"r");
102 auto close_fp = [&](
FILE *fp2) {
103 if (fclose(fp2) == 0)
105 LOG(VB_COMMFLAG, LOG_ERR, QString(
"Error closing %1: %2")
108 std::unique_ptr<
FILE,
decltype(close_fp)>
cleanup { fp, close_fp };
110 for (
long long frameno = 0; frameno < nframes; frameno++)
112 int nitems = fscanf(fp,
"%20hu", &matches[frameno]);
115 LOG(VB_COMMFLAG, LOG_ERR,
116 QString(
"Not enough data in %1: frame %2")
126 QByteArray fname =
filename.toLocal8Bit();
127 FILE *fp = fopen(fname.constData(),
"w");
131 for (
long long frameno = 0; frameno < nframes; frameno++)
132 (
void)fprintf(fp,
"%hu\n", matches[frameno]);
135 LOG(VB_COMMFLAG, LOG_ERR, QString(
"Error closing %1: %2")
141 const unsigned char *match)
143 ushort score = matches[0];
146 long long startframe = 0;
148 for (
long long frameno = 1; frameno < nframes; frameno++)
150 score = matches[frameno];
151 if (match[frameno - 1] == match[frameno])
153 low = std::min(score, low);
154 high = std::max(score, high);
158 LOG(VB_COMMFLAG, LOG_INFO, QString(
"Frame %1-%2: %3 L-H: %4-%5 (%6)")
159 .arg(startframe, 6).arg(frameno - 1, 6)
160 .arg(match[frameno - 1] ?
"logo " :
" no-logo")
161 .arg(low, 4).arg(high, 4).arg(frameno - startframe, 5));
165 startframe = frameno;
173 return *(
unsigned short*)aa - *(
unsigned short*)bb;
176long long matchspn(
long long nframes,
const unsigned char *match,
long long frameno,
177 unsigned char acceptval)
183 while (frameno < nframes && match[frameno] == acceptval)
192 const unsigned short width = end - start;
196 for (ushort matchcnt = start; matchcnt < end; matchcnt++)
200 sum +=
freq[matchcnt];
206 return width * sum / nsamples;
238 static constexpr float kLeftWidth = 0.04;
239 static constexpr float kMiddleWidth = 0.04;
240 static constexpr float kRightWidth = 0.04;
242 static constexpr float kMatchStart = 0.20;
243 static constexpr float kMatchEnd = 0.80;
245 auto *sorted =
new unsigned short[nframes];
246 memcpy(sorted, matches, nframes *
sizeof(*matches));
248 ushort minmatch = sorted[0];
249 ushort maxmatch = sorted[nframes - 1];
250 ushort matchrange = maxmatch - minmatch;
253 auto leftwidth = (
unsigned short)(kLeftWidth * matchrange);
254 auto middlewidth = (
unsigned short)(kMiddleWidth * matchrange);
255 auto rightwidth = (
unsigned short)(kRightWidth * matchrange);
257 int nfreq = maxmatch + 1;
258 auto *
freq =
new unsigned short[nfreq];
259 memset(
freq, 0, nfreq *
sizeof(*
freq));
260 for (
long long frameno = 0; frameno < nframes; frameno++)
261 freq[matches[frameno]]++;
263 ushort matchstart = minmatch + (
unsigned short)(kMatchStart * matchrange);
264 ushort matchend = minmatch + (
unsigned short)(kMatchEnd * matchrange);
266 int local_minimum = matchstart;
268 for (
int matchcnt = matchstart + leftwidth + (middlewidth / 2);
269 matchcnt < matchend - rightwidth - (middlewidth / 2);
272 ushort p0 = matchcnt - leftwidth - (middlewidth / 2);
273 ushort
p1 = p0 + leftwidth;
274 ushort
p2 =
p1 + middlewidth;
275 ushort p3 =
p2 + rightwidth;
280 if (middlescore < leftscore && middlescore < rightscore)
282 unsigned int delta = (leftscore - middlescore) +
283 (rightscore - middlescore);
284 if (delta > maxdelta)
286 local_minimum = matchcnt;
292 LOG(VB_COMMFLAG, LOG_INFO,
293 QString(
"pick_mintmpledges minmatch=%1 maxmatch=%2"
294 " matchstart=%3 matchend=%4 widths=%5,%6,%7 local_minimum=%8")
295 .arg(minmatch).arg(maxmatch).arg(matchstart).arg(matchend)
296 .arg(leftwidth).arg(middlewidth).arg(rightwidth)
297 .arg(local_minimum));
301 return local_minimum;
307 std::shared_ptr<EdgeDetector> ed,
309 m_pgmConverter(
std::move(pgmc)),
310 m_edgeDetector(
std::move(ed)), m_templateFinder(tf),
311 m_debugDir(debugdir),
313 m_debugData(debugdir +
"/TemplateMatcher-pgm.txt")
315 m_debugData(debugdir +
"/TemplateMatcher-yuv.txt")
329 QString(
"TemplateMatcher debugLevel %1").arg(
m_debugLevel));
340 av_freep(
reinterpret_cast<void*
>(&
m_cropped.data[0]));
354 LOG(VB_COMMFLAG, LOG_ERR,
355 QString(
"TemplateMatcher::MythPlayerInited: no template"));
362 LOG(VB_COMMFLAG, LOG_ERR,
363 QString(
"TemplateMatcher::MythPlayerInited "
364 "av_image_alloc cropped (%1x%2) failed")
371 av_freep(
reinterpret_cast<void*
>(&
m_cropped.data[0]));
378 m_match =
new unsigned char[nframes];
384 LOG(VB_COMMFLAG, LOG_INFO,
385 QString(
"TemplateMatcher::MythPlayerInited read %1")
399 long long *pNextFrame)
416 const int FRAMESGMPCTILE = 70;
434 const int JITTER_RADIUS = 0;
436 const AVFrame *edges =
nullptr;
439 std::chrono::microseconds start {0us};
440 std::chrono::microseconds end {0us};
450 start = nowAsDuration<std::chrono::microseconds>();
457 if (edges ==
nullptr)
463 end = nowAsDuration<std::chrono::microseconds>();
470 LOG(VB_COMMFLAG, LOG_ERR,
471 QString(
"TemplateMatcher::analyzeFrame error at frame %1, step %2")
472 .arg(frameno).arg(e));
489 const int MINBREAKLEN = (int)roundf(45 *
m_fps);
490 const int MINSEGLEN = (int)roundf(105 *
m_fps);
500 LOG(VB_COMMFLAG, LOG_INFO,
501 QString(
"TemplateMatcher::finished wrote %1") .arg(
m_debugData));
509 LOG(VB_COMMFLAG, LOG_INFO,
510 QString(
"TemplateMatcher::finished %1x%2@(%3,%4),"
511 " %5 edge pixels, want %6")
513 .arg(tmpledges).arg(mintmpledges));
515 for (
long long ii = 0; ii < nframes; ii++)
529 while (brkb < nframes)
533 long long brklen = brke - brkb;
547 if (minbreaklen <= MINBREAKLEN)
553 if (minseglen <= MINSEGLEN)
559 if (minbreaklen > MINBREAKLEN && minseglen > MINSEGLEN)
579 LOG(VB_COMMFLAG, LOG_INFO, QString(
"TM Time: analyze=%1s")
598 static const int64_t MINBREAKS { nframes * 20 / 100 };
599 static const int64_t MAXBREAKS { nframes * 45 / 100 };
602 const bool good = brklen >= MINBREAKS && brklen <= MAXBREAKS;
608 LOG(VB_COMMFLAG, LOG_ERR,
609 QString(
"TemplateMatcher: no template (wanted %2-%3%)")
610 .arg(100 * MINBREAKS / nframes)
611 .arg(100 * MAXBREAKS / nframes));
615 LOG(VB_COMMFLAG, LOG_ERR,
616 QString(
"TemplateMatcher has %1% breaks (real-time flagging)")
617 .arg(100 * brklen / nframes));
621 LOG(VB_COMMFLAG, LOG_INFO, QString(
"TemplateMatcher has %1% breaks")
622 .arg(100 * brklen / nframes));
626 LOG(VB_COMMFLAG, LOG_INFO,
627 QString(
"TemplateMatcher has %1% breaks (wanted %2-%3%)")
628 .arg(100 * brklen / nframes)
629 .arg(100 * MINBREAKS / nframes)
630 .arg(100 * MAXBREAKS / nframes));
637 if (brklen < MINBREAKS)
639 if (brklen <= MAXBREAKS)
717 const int BLANK_NEARBY = (int)roundf(0.5F *
m_fps);
718 const int TEMPLATE_DISAPPEARS_EARLY = (int)roundf(25 *
m_fps);
719 const int TEMPLATE_DISAPPEARS_LATE = (int)roundf(0 *
m_fps);
720 const int TEMPLATE_REAPPEARS_LATE = (int)roundf(35 *
m_fps);
721 const int TEMPLATE_REAPPEARS_EARLY = (int)roundf(1.5F *
m_fps);
723 LOG(VB_COMMFLAG, LOG_INFO, QString(
"TemplateMatcher adjusting for blanks"));
725 FrameAnalyzer::FrameMap::Iterator ii =
m_breakMap.begin();
726 long long prevbrke = 0;
729 FrameAnalyzer::FrameMap::Iterator iinext = ii;
737 const long long brkb = ii.key();
738 const long long brke = brkb + *ii;
739 FrameAnalyzer::FrameMap::const_iterator jj = blankMap->constEnd();
744 brkb - std::max(BLANK_NEARBY, TEMPLATE_DISAPPEARS_LATE)),
746 brkb + std::max(BLANK_NEARBY, TEMPLATE_DISAPPEARS_EARLY)));
748 long long newbrkb = brkb;
749 if (jj != blankMap->constEnd())
752 long long adj = *jj / 2;
764 brke - std::max(BLANK_NEARBY, TEMPLATE_REAPPEARS_LATE)),
765 std::min(iinext ==
m_breakMap.end() ? nframes : iinext.key(),
766 brke + std::max(BLANK_NEARBY, TEMPLATE_REAPPEARS_EARLY)));
767 long long newbrke = brke;
768 if (kk != blankMap->constEnd())
781 long long newbrklen = newbrke - newbrkb;
785 if (newbrkb < nframes && newbrklen)
788 else if (newbrke != brke)
820 breaks->insert(bb.key(), *bb);
static constexpr int64_t MAX_BLANK_FRAMES
#define PGM_CONVERT_GREYSCALE
const FrameAnalyzer::FrameMap * getBlanks(void) const
static const long long kNextFrame
QMap< long long, long long > FrameMap
int GetNumSetting(const QString &key, int defaultval=0)
float GetFrameRate(void) const
const struct AVFrame * getTemplate(int *prow, int *pcol, int *pwidth, int *pheight) const
std::chrono::microseconds m_analyzeTime
int reportTime(void) const override
int computeBreaks(FrameMap *breaks)
TemplateMatcher(std::shared_ptr< PGMConverter > pgmc, std::shared_ptr< EdgeDetector > ed, TemplateFinder *tf, const QString &debugdir)
enum analyzeFrameResult analyzeFrame(const MythVideoFrame *frame, long long frameno, long long *pNextFrame) override
const struct AVFrame * m_tmpl
unsigned short * m_matches
TemplateFinder * m_templateFinder
FrameAnalyzer::FrameMap m_breakMap
int templateCoverage(long long nframes, bool final) const
std::shared_ptr< PGMConverter > m_pgmConverter
int adjustForBlanks(const BlankFrameDetector *blankFrameDetector, long long nframes)
std::shared_ptr< EdgeDetector > m_edgeDetector
enum analyzeFrameResult MythPlayerInited(MythPlayer *player, long long nframes) override
int finished(long long nframes, bool final) override
~TemplateMatcher(void) override
static const std::array< const uint32_t, 4 > freq
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
unsigned int range_area(const unsigned short *freq, unsigned short start, unsigned short end)
int finishedDebug(long long nframes, const unsigned short *matches, const unsigned char *match)
int pgm_match(const AVFrame *tmpl, const AVFrame *test, int height, int radius, unsigned short *pscore)
int pgm_set(const AVFrame *pict, int height)
unsigned short pick_mintmpledges(const unsigned short *matches, long long nframes)
long long matchspn(long long nframes, const unsigned char *match, long long frameno, unsigned char acceptval)
bool readMatches(const QString &filename, unsigned short *matches, long long nframes)
int pgm_match_inner_loop(const AVFrame *test, int rr, int cc, int radius, int height, int width)
bool writeMatches(const QString &filename, unsigned short *matches, long long nframes)
int sort_ascending(const void *aa, const void *bb)
void createDebugDirectory(const QString &dirname, const QString &comment)
QString strftimeval(std::chrono::microseconds usecs)
long long frameAnalyzerMapSum(const FrameAnalyzer::FrameMap *frameMap)
FrameAnalyzer::FrameMap::const_iterator frameMapSearchForwards(const FrameAnalyzer::FrameMap *frameMap, long long mark, long long markend)
bool removeShortSegments(FrameAnalyzer::FrameMap *breakMap, long long nframes, float fps, int minseglen, bool verbose)
void frameAnalyzerReportMap(const FrameAnalyzer::FrameMap *frameMap, float fps, const char *comment)
FrameAnalyzer::FrameMap::const_iterator frameMapSearchBackwards(const FrameAnalyzer::FrameMap *frameMap, long long markbegin, long long mark)
bool removeShortBreaks(FrameAnalyzer::FrameMap *breakMap, float fps, int minbreaklen, bool verbose)
int pgm_crop(AVFrame *dst, const AVFrame *src, int srcheight, int srcrow, int srccol, int cropwidth, int cropheight)
static QString cleanup(const QString &str)