12#include "libmythbase/mythconfig.h"
28#include "libavutil/imgutils.h"
38 const int width = pict->linesize[0];
39 const int size = height * width;
42 for (
int ii = 0; ii < size; ii++)
43 if (pict->data[0][ii])
50 int r2min = std::max(0, rr - radius);
51 int r2max = std::min(height, rr + radius);
53 int c2min = std::max(0, cc - radius);
54 int c2max = std::min(width, cc + radius);
56 for (
int r2 = r2min; r2 <= r2max; r2++)
58 for (
int c2 = c2min; c2 <= c2max; c2++)
60 if (test->data[0][(r2 * width) + c2])
68 int radius,
unsigned short *pscore)
71 const int width = tmpl->linesize[0];
73 if (width != test->linesize[0])
75 LOG(VB_COMMFLAG, LOG_ERR,
76 QString(
"pgm_match widths don't match: %1 != %2")
77 .arg(width).arg(test->linesize[0]));
82 for (
int rr = 0; rr < height; rr++)
84 for (
int cc = 0; cc < width; cc++)
86 if (!tmpl->data[0][(rr * width) + cc])
98 QByteArray fname =
filename.toLocal8Bit();
99 FILE *fp = fopen(fname.constData(),
"r");
103 auto close_fp = [&](
FILE *fp2) {
104 if (fclose(fp2) == 0)
106 LOG(VB_COMMFLAG, LOG_ERR, QString(
"Error closing %1: %2")
109 std::unique_ptr<
FILE,
decltype(close_fp)>
cleanup { fp, close_fp };
111 for (
long long frameno = 0; frameno < nframes; frameno++)
113 int nitems = fscanf(fp,
"%20hu", &matches[frameno]);
116 LOG(VB_COMMFLAG, LOG_ERR,
117 QString(
"Not enough data in %1: frame %2")
127 QByteArray fname =
filename.toLocal8Bit();
128 FILE *fp = fopen(fname.constData(),
"w");
132 for (
long long frameno = 0; frameno < nframes; frameno++)
133 (
void)fprintf(fp,
"%hu\n", matches[frameno]);
136 LOG(VB_COMMFLAG, LOG_ERR, QString(
"Error closing %1: %2")
142 const unsigned char *match)
144 ushort score = matches[0];
147 long long startframe = 0;
149 for (
long long frameno = 1; frameno < nframes; frameno++)
151 score = matches[frameno];
152 if (match[frameno - 1] == match[frameno])
154 low = std::min(score, low);
155 high = std::max(score, high);
159 LOG(VB_COMMFLAG, LOG_INFO, QString(
"Frame %1-%2: %3 L-H: %4-%5 (%6)")
160 .arg(startframe, 6).arg(frameno - 1, 6)
161 .arg(match[frameno - 1] ?
"logo " :
" no-logo")
162 .arg(low, 4).arg(high, 4).arg(frameno - startframe, 5));
166 startframe = frameno;
174 return *(
unsigned short*)aa - *(
unsigned short*)bb;
177long long matchspn(
long long nframes,
const unsigned char *match,
long long frameno,
178 unsigned char acceptval)
184 while (frameno < nframes && match[frameno] == acceptval)
193 const unsigned short width = end - start;
197 for (ushort matchcnt = start; matchcnt < end; matchcnt++)
201 sum +=
freq[matchcnt];
207 return width * sum / nsamples;
239 static constexpr float kLeftWidth = 0.04;
240 static constexpr float kMiddleWidth = 0.04;
241 static constexpr float kRightWidth = 0.04;
243 static constexpr float kMatchStart = 0.20;
244 static constexpr float kMatchEnd = 0.80;
246 auto *sorted =
new unsigned short[nframes];
247 memcpy(sorted, matches, nframes *
sizeof(*matches));
249 ushort minmatch = sorted[0];
250 ushort maxmatch = sorted[nframes - 1];
251 ushort matchrange = maxmatch - minmatch;
254 auto leftwidth = (
unsigned short)(kLeftWidth * matchrange);
255 auto middlewidth = (
unsigned short)(kMiddleWidth * matchrange);
256 auto rightwidth = (
unsigned short)(kRightWidth * matchrange);
258 int nfreq = maxmatch + 1;
259 auto *
freq =
new unsigned short[nfreq];
260 memset(
freq, 0, nfreq *
sizeof(*
freq));
261 for (
long long frameno = 0; frameno < nframes; frameno++)
262 freq[matches[frameno]]++;
264 ushort matchstart = minmatch + (
unsigned short)(kMatchStart * matchrange);
265 ushort matchend = minmatch + (
unsigned short)(kMatchEnd * matchrange);
267 int local_minimum = matchstart;
269 for (
int matchcnt = matchstart + leftwidth + (middlewidth / 2);
270 matchcnt < matchend - rightwidth - (middlewidth / 2);
273 ushort p0 = matchcnt - leftwidth - (middlewidth / 2);
274 ushort
p1 = p0 + leftwidth;
275 ushort
p2 =
p1 + middlewidth;
276 ushort p3 =
p2 + rightwidth;
281 if (middlescore < leftscore && middlescore < rightscore)
283 unsigned int delta = (leftscore - middlescore) +
284 (rightscore - middlescore);
285 if (delta > maxdelta)
287 local_minimum = matchcnt;
293 LOG(VB_COMMFLAG, LOG_INFO,
294 QString(
"pick_mintmpledges minmatch=%1 maxmatch=%2"
295 " matchstart=%3 matchend=%4 widths=%5,%6,%7 local_minimum=%8")
296 .arg(minmatch).arg(maxmatch).arg(matchstart).arg(matchend)
297 .arg(leftwidth).arg(middlewidth).arg(rightwidth)
298 .arg(local_minimum));
302 return local_minimum;
308 std::shared_ptr<EdgeDetector> ed,
310 m_pgmConverter(
std::move(pgmc)),
311 m_edgeDetector(
std::move(ed)), m_templateFinder(tf),
312 m_debugDir(debugdir),
314 m_debugData(debugdir +
"/TemplateMatcher-pgm.txt")
316 m_debugData(debugdir +
"/TemplateMatcher-yuv.txt")
330 QString(
"TemplateMatcher debugLevel %1").arg(
m_debugLevel));
341 av_freep(
reinterpret_cast<void*
>(&
m_cropped.data[0]));
355 LOG(VB_COMMFLAG, LOG_ERR,
356 QString(
"TemplateMatcher::MythPlayerInited: no template"));
363 LOG(VB_COMMFLAG, LOG_ERR,
364 QString(
"TemplateMatcher::MythPlayerInited "
365 "av_image_alloc cropped (%1x%2) failed")
372 av_freep(
reinterpret_cast<void*
>(&
m_cropped.data[0]));
379 m_match =
new unsigned char[nframes];
385 LOG(VB_COMMFLAG, LOG_INFO,
386 QString(
"TemplateMatcher::MythPlayerInited read %1")
400 long long *pNextFrame)
417 const int FRAMESGMPCTILE = 70;
435 const int JITTER_RADIUS = 0;
437 const AVFrame *edges =
nullptr;
440 std::chrono::microseconds start {0us};
441 std::chrono::microseconds end {0us};
451 start = nowAsDuration<std::chrono::microseconds>();
458 if (edges ==
nullptr)
464 end = nowAsDuration<std::chrono::microseconds>();
471 LOG(VB_COMMFLAG, LOG_ERR,
472 QString(
"TemplateMatcher::analyzeFrame error at frame %1, step %2")
473 .arg(frameno).arg(e));
490 const int MINBREAKLEN = (int)roundf(45 *
m_fps);
491 const int MINSEGLEN = (int)roundf(105 *
m_fps);
501 LOG(VB_COMMFLAG, LOG_INFO,
502 QString(
"TemplateMatcher::finished wrote %1") .arg(
m_debugData));
510 LOG(VB_COMMFLAG, LOG_INFO,
511 QString(
"TemplateMatcher::finished %1x%2@(%3,%4),"
512 " %5 edge pixels, want %6")
514 .arg(tmpledges).arg(mintmpledges));
516 for (
long long ii = 0; ii < nframes; ii++)
530 while (brkb < nframes)
534 long long brklen = brke - brkb;
548 if (minbreaklen <= MINBREAKLEN)
554 if (minseglen <= MINSEGLEN)
560 if (minbreaklen > MINBREAKLEN && minseglen > MINSEGLEN)
580 LOG(VB_COMMFLAG, LOG_INFO, QString(
"TM Time: analyze=%1s")
599 static const int64_t MINBREAKS { nframes * 20 / 100 };
600 static const int64_t MAXBREAKS { nframes * 45 / 100 };
603 const bool good = brklen >= MINBREAKS && brklen <= MAXBREAKS;
609 LOG(VB_COMMFLAG, LOG_ERR,
610 QString(
"TemplateMatcher: no template (wanted %2-%3%)")
611 .arg(100 * MINBREAKS / nframes)
612 .arg(100 * MAXBREAKS / nframes));
616 LOG(VB_COMMFLAG, LOG_ERR,
617 QString(
"TemplateMatcher has %1% breaks (real-time flagging)")
618 .arg(100 * brklen / nframes));
622 LOG(VB_COMMFLAG, LOG_INFO, QString(
"TemplateMatcher has %1% breaks")
623 .arg(100 * brklen / nframes));
627 LOG(VB_COMMFLAG, LOG_INFO,
628 QString(
"TemplateMatcher has %1% breaks (wanted %2-%3%)")
629 .arg(100 * brklen / nframes)
630 .arg(100 * MINBREAKS / nframes)
631 .arg(100 * MAXBREAKS / nframes));
638 if (brklen < MINBREAKS)
640 if (brklen <= MAXBREAKS)
718 const int BLANK_NEARBY = (int)roundf(0.5F *
m_fps);
719 const int TEMPLATE_DISAPPEARS_EARLY = (int)roundf(25 *
m_fps);
720 const int TEMPLATE_DISAPPEARS_LATE = (int)roundf(0 *
m_fps);
721 const int TEMPLATE_REAPPEARS_LATE = (int)roundf(35 *
m_fps);
722 const int TEMPLATE_REAPPEARS_EARLY = (int)roundf(1.5F *
m_fps);
724 LOG(VB_COMMFLAG, LOG_INFO, QString(
"TemplateMatcher adjusting for blanks"));
726 FrameAnalyzer::FrameMap::Iterator ii =
m_breakMap.begin();
727 long long prevbrke = 0;
730 FrameAnalyzer::FrameMap::Iterator iinext = ii;
738 const long long brkb = ii.key();
739 const long long brke = brkb + *ii;
740 FrameAnalyzer::FrameMap::const_iterator jj = blankMap->constEnd();
745 brkb - std::max(BLANK_NEARBY, TEMPLATE_DISAPPEARS_LATE)),
747 brkb + std::max(BLANK_NEARBY, TEMPLATE_DISAPPEARS_EARLY)));
749 long long newbrkb = brkb;
750 if (jj != blankMap->constEnd())
753 long long adj = *jj / 2;
765 brke - std::max(BLANK_NEARBY, TEMPLATE_REAPPEARS_LATE)),
766 std::min(iinext ==
m_breakMap.end() ? nframes : iinext.key(),
767 brke + std::max(BLANK_NEARBY, TEMPLATE_REAPPEARS_EARLY)));
768 long long newbrke = brke;
769 if (kk != blankMap->constEnd())
782 long long newbrklen = newbrke - newbrkb;
786 if (newbrkb < nframes && newbrklen)
789 else if (newbrke != brke)
821 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)