MythTV  master
TemplateMatcher.cpp
Go to the documentation of this file.
1 // C++ headers
2 #include <algorithm>
3 #include <cmath>
4 #include <cstdlib>
5 
6 // Qt headers
7 #include <QFile>
8 #include <QFileInfo>
9 #include <utility>
10 
11 // MythTV headers
14 #include "libmythtv/mythplayer.h"
15 
16 // Commercial Flagging headers
17 #include "BlankFrameDetector.h"
18 #include "CommDetector2.h"
19 #include "EdgeDetector.h"
20 #include "FrameAnalyzer.h"
21 #include "PGMConverter.h"
22 #include "TemplateFinder.h"
23 #include "TemplateMatcher.h"
24 #include "pgm.h"
25 
26 extern "C" {
27 #include "libavutil/imgutils.h"
28 }
29 
30 using namespace commDetector2;
31 using namespace frameAnalyzer;
32 
33 namespace {
34 
35 int pgm_set(const AVFrame *pict, int height)
36 {
37  const int width = pict->linesize[0];
38  const int size = height * width;
39 
40  int score = 0;
41  for (int ii = 0; ii < size; ii++)
42  if (pict->data[0][ii])
43  score++;
44  return score;
45 }
46 
47 int pgm_match(const AVFrame *tmpl, const AVFrame *test, int height,
48  int radius, unsigned short *pscore)
49 {
50  /* Return the number of matching "edge" and non-edge pixels. */
51  const int width = tmpl->linesize[0];
52 
53  if (width != test->linesize[0])
54  {
55  LOG(VB_COMMFLAG, LOG_ERR,
56  QString("pgm_match widths don't match: %1 != %2")
57  .arg(width).arg(test->linesize[0]));
58  return -1;
59  }
60 
61  int score = 0;
62  for (int rr = 0; rr < height; rr++)
63  {
64  for (int cc = 0; cc < width; cc++)
65  {
66  if (!tmpl->data[0][rr * width + cc])
67  continue;
68 
69  int r2min = std::max(0, rr - radius);
70  int r2max = std::min(height, rr + radius);
71 
72  int c2min = std::max(0, cc - radius);
73  int c2max = std::min(width, cc + radius);
74 
75  for (int r2 = r2min; r2 <= r2max; r2++)
76  {
77  for (int c2 = c2min; c2 <= c2max; c2++)
78  {
79  if (test->data[0][r2 * width + c2])
80  {
81  score++;
82  goto next_pixel;
83  }
84  }
85  }
86 next_pixel:
87  ;
88  }
89  }
90 
91  *pscore = score;
92  return 0;
93 }
94 
95 bool readMatches(const QString& filename, unsigned short *matches, long long nframes)
96 {
97  QByteArray fname = filename.toLocal8Bit();
98  FILE *fp = fopen(fname.constData(), "r");
99  if (fp == nullptr)
100  return false;
101 
102  for (long long frameno = 0; frameno < nframes; frameno++)
103  {
104  int nitems = fscanf(fp, "%20hu", &matches[frameno]);
105  if (nitems != 1)
106  {
107  LOG(VB_COMMFLAG, LOG_ERR,
108  QString("Not enough data in %1: frame %2")
109  .arg(filename).arg(frameno));
110  goto error;
111  }
112  }
113 
114  if (fclose(fp))
115  LOG(VB_COMMFLAG, LOG_ERR, QString("Error closing %1: %2")
116  .arg(filename, strerror(errno)));
117  return true;
118 
119 error:
120  if (fclose(fp))
121  LOG(VB_COMMFLAG, LOG_ERR, QString("Error closing %1: %2")
122  .arg(filename, strerror(errno)));
123  return false;
124 }
125 
126 bool writeMatches(const QString& filename, unsigned short *matches, long long nframes)
127 {
128  QByteArray fname = filename.toLocal8Bit();
129  FILE *fp = fopen(fname.constData(), "w");
130  if (fp == nullptr)
131  return false;
132 
133  for (long long frameno = 0; frameno < nframes; frameno++)
134  (void)fprintf(fp, "%hu\n", matches[frameno]);
135 
136  if (fclose(fp))
137  LOG(VB_COMMFLAG, LOG_ERR, QString("Error closing %1: %2")
138  .arg(filename, strerror(errno)));
139  return true;
140 }
141 
142 int finishedDebug(long long nframes, const unsigned short *matches,
143  const unsigned char *match)
144 {
145  ushort score = matches[0];
146  ushort low = score;
147  ushort high = score;
148  long long startframe = 0;
149 
150  for (long long frameno = 1; frameno < nframes; frameno++)
151  {
152  score = matches[frameno];
153  if (match[frameno - 1] == match[frameno])
154  {
155  if (score < low)
156  low = score;
157  if (score > high)
158  high = score;
159  continue;
160  }
161 
162  LOG(VB_COMMFLAG, LOG_INFO, QString("Frame %1-%2: %3 L-H: %4-%5 (%6)")
163  .arg(startframe, 6).arg(frameno - 1, 6)
164  .arg(match[frameno - 1] ? "logo " : " no-logo")
165  .arg(low, 4).arg(high, 4).arg(frameno - startframe, 5));
166 
167  low = score;
168  high = score;
169  startframe = frameno;
170  }
171 
172  return 0;
173 }
174 
175 int sort_ascending(const void *aa, const void *bb)
176 {
177  return *(unsigned short*)aa - *(unsigned short*)bb;
178 }
179 
180 long long matchspn(long long nframes, const unsigned char *match, long long frameno,
181  unsigned char acceptval)
182 {
183  /*
184  * strspn(3)-style interface: return the first frame number that does not
185  * match "acceptval".
186  */
187  while (frameno < nframes && match[frameno] == acceptval)
188  frameno++;
189  return frameno;
190 }
191 
192 unsigned int range_area(const unsigned short *freq, unsigned short start,
193  unsigned short end)
194 {
195  /* Return the integrated area under the curve of the plotted histogram. */
196  const unsigned short width = end - start;
197 
198  uint sum = 0;
199  uint nsamples = 0;
200  for (ushort matchcnt = start; matchcnt < end; matchcnt++)
201  {
202  if (freq[matchcnt])
203  {
204  sum += freq[matchcnt];
205  nsamples++;
206  }
207  }
208  if (!nsamples)
209  return 0;
210  return width * sum / nsamples;
211 }
212 
213 unsigned short pick_mintmpledges(const unsigned short *matches,
214  long long nframes)
215 {
216  /*
217  * Most frames either match the template very well, or don't match
218  * very well at all. This allows us to assume a bimodal
219  * distribution of the values in a frequency histogram of the
220  * "matches" array.
221  *
222  * Return a local minima between the two modes representing the
223  * threshold value that decides whether or not a frame matches the
224  * template.
225  *
226  * See "mythcommflag-analyze" and its output.
227  */
228 
229  /*
230  * TUNABLE:
231  *
232  * Given a point to test, require the integrated area to the left
233  * of the point to be greater than some (larger) area to the right
234  * of the point.
235  */
236  static constexpr float kLeftWidth = 0.04;
237  static constexpr float kMiddleWidth = 0.04;
238  static constexpr float kRightWidth = 0.04;
239 
240  static constexpr float kMatchStart = 0.20;
241  static constexpr float kMatchEnd = 0.80;
242 
243  auto *sorted = new unsigned short[nframes];
244  memcpy(sorted, matches, nframes * sizeof(*matches));
245  qsort(sorted, nframes, sizeof(*sorted), sort_ascending);
246  ushort minmatch = sorted[0];
247  ushort maxmatch = sorted[nframes - 1];
248  ushort matchrange = maxmatch - minmatch;
249  /* degenerate minmatch==maxmatch case is gracefully handled */
250 
251  auto leftwidth = (unsigned short)(kLeftWidth * matchrange);
252  auto middlewidth = (unsigned short)(kMiddleWidth * matchrange);
253  auto rightwidth = (unsigned short)(kRightWidth * matchrange);
254 
255  int nfreq = maxmatch + 1;
256  auto *freq = new unsigned short[nfreq];
257  memset(freq, 0, nfreq * sizeof(*freq));
258  for (long long frameno = 0; frameno < nframes; frameno++)
259  freq[matches[frameno]]++; /* freq[<matchcnt>] = <framecnt> */
260 
261  ushort matchstart = minmatch + (unsigned short)(kMatchStart * matchrange);
262  ushort matchend = minmatch + (unsigned short)(kMatchEnd * matchrange);
263 
264  int local_minimum = matchstart;
265  uint maxdelta = 0;
266  for (int matchcnt = matchstart + leftwidth + middlewidth / 2;
267  matchcnt < matchend - rightwidth - middlewidth / 2;
268  matchcnt++)
269  {
270  ushort p0 = matchcnt - leftwidth - middlewidth / 2;
271  ushort p1 = p0 + leftwidth;
272  ushort p2 = p1 + middlewidth;
273  ushort p3 = p2 + rightwidth;
274 
275  uint leftscore = range_area(freq, p0, p1);
276  uint middlescore = range_area(freq, p1, p2);
277  uint rightscore = range_area(freq, p2, p3);
278  if (middlescore < leftscore && middlescore < rightscore)
279  {
280  unsigned int delta = (leftscore - middlescore) +
281  (rightscore - middlescore);
282  if (delta > maxdelta)
283  {
284  local_minimum = matchcnt;
285  maxdelta = delta;
286  }
287  }
288  }
289 
290  LOG(VB_COMMFLAG, LOG_INFO,
291  QString("pick_mintmpledges minmatch=%1 maxmatch=%2"
292  " matchstart=%3 matchend=%4 widths=%5,%6,%7 local_minimum=%8")
293  .arg(minmatch).arg(maxmatch).arg(matchstart).arg(matchend)
294  .arg(leftwidth).arg(middlewidth).arg(rightwidth)
295  .arg(local_minimum));
296 
297  delete []freq;
298  delete []sorted;
299  return local_minimum;
300 }
301 
302 }; /* namespace */
303 
304 TemplateMatcher::TemplateMatcher(std::shared_ptr<PGMConverter> pgmc,
305  std::shared_ptr<EdgeDetector> ed,
306  TemplateFinder *tf, const QString& debugdir) :
307  m_pgmConverter(std::move(pgmc)),
308  m_edgeDetector(std::move(ed)), m_templateFinder(tf),
309  m_debugDir(debugdir),
311  m_debugData(debugdir + "/TemplateMatcher-pgm.txt")
312 #else /* !PGM_CONVERT_GREYSCALE */
313  m_debugData(debugdir + "/TemplateMatcher-yuv.txt")
314 #endif /* !PGM_CONVERT_GREYSCALE */
315 {
316  /*
317  * debugLevel:
318  * 0: no debugging
319  * 1: cache frame edge counts into debugdir [1 file]
320  * 2: extra verbosity [O(nframes)]
321  */
322  m_debugLevel = gCoreContext->GetNumSetting("TemplateMatcherDebugLevel", 0);
323 
324  if (m_debugLevel >= 1)
325  {
327  QString("TemplateMatcher debugLevel %1").arg(m_debugLevel));
328  m_debugMatches = true;
329  if (m_debugLevel >= 2)
330  m_debugRemoveRunts = true;
331  }
332 }
333 
335 {
336  delete []m_matches;
337  delete []m_match;
338  av_freep(&m_cropped.data[0]);
339 }
340 
343  long long nframes)
344 {
345  m_player = _player;
347 
350  {
351  LOG(VB_COMMFLAG, LOG_ERR,
352  QString("TemplateMatcher::MythPlayerInited: no template"));
353  return ANALYZE_FATAL;
354  }
355 
356  if (av_image_alloc(m_cropped.data, m_cropped.linesize,
357  m_tmplWidth, m_tmplHeight, AV_PIX_FMT_GRAY8, IMAGE_ALIGN))
358  {
359  LOG(VB_COMMFLAG, LOG_ERR,
360  QString("TemplateMatcher::MythPlayerInited "
361  "av_image_alloc cropped (%1x%2) failed")
362  .arg(m_tmplWidth).arg(m_tmplHeight));
363  return ANALYZE_FATAL;
364  }
365 
366  if (m_pgmConverter->MythPlayerInited(m_player))
367  goto free_cropped;
368 
369  m_matches = new unsigned short[nframes];
370  memset(m_matches, 0, nframes * sizeof(*m_matches));
371 
372  m_match = new unsigned char[nframes];
373 
374  if (m_debugMatches)
375  {
376  if (readMatches(m_debugData, m_matches, nframes))
377  {
378  LOG(VB_COMMFLAG, LOG_INFO,
379  QString("TemplateMatcher::MythPlayerInited read %1")
380  .arg(m_debugData));
381  m_matchesDone = true;
382  }
383  }
384 
385  if (m_matchesDone)
386  return ANALYZE_FINISHED;
387 
388  return ANALYZE_OK;
389 
390 free_cropped:
391  av_freep(&m_cropped.data[0]);
392  return ANALYZE_FATAL;
393 }
394 
396 TemplateMatcher::analyzeFrame(const MythVideoFrame *frame, long long frameno,
397  long long *pNextFrame)
398 {
399  /*
400  * TUNABLE:
401  *
402  * The matching area should be a lot smaller than the area used by
403  * TemplateFinder, so use a smaller percentile than the TemplateFinder
404  * (intensity requirements to be considered an "edge" are lower, because
405  * there should be less pollution from non-template edges).
406  *
407  * Higher values mean fewer edge pixels in the candidate template area;
408  * occluded or faint templates might be missed.
409  *
410  * Lower values mean more edge pixels in the candidate template area;
411  * non-template edges can be picked up and cause false identification of
412  * matches.
413  */
414  const int FRAMESGMPCTILE = 70;
415 
416  /*
417  * TUNABLE:
418  *
419  * The per-pixel search radius for matching the template. Meant to deal
420  * with template edges that might minimally slide around (such as for
421  * animated lighting effects).
422  *
423  * Higher values will pick up more pixels as matching the template
424  * (possibly false matches).
425  *
426  * Lower values will require more exact template matches, possibly missing
427  * true matches.
428  *
429  * The TemplateMatcher accumulates all its state in the "matches" array to
430  * be processed later by TemplateMatcher::finished.
431  */
432  const int JITTER_RADIUS = 0;
433 
434  const AVFrame *edges = nullptr;
435  int pgmwidth = 0;
436  int pgmheight = 0;
437  std::chrono::microseconds start {0us};
438  std::chrono::microseconds end {0us};
439 
440  *pNextFrame = kNextFrame;
441 
442  const AVFrame *pgm = m_pgmConverter->getImage(frame, frameno, &pgmwidth, &pgmheight);
443  if (pgm == nullptr)
444  goto error;
445 
446  start = nowAsDuration<std::chrono::microseconds>();
447 
448  if (pgm_crop(&m_cropped, pgm, pgmheight, m_tmplRow, m_tmplCol,
450  goto error;
451 
452  if (!(edges = m_edgeDetector->detectEdges(&m_cropped, m_tmplHeight,
453  FRAMESGMPCTILE)))
454  goto error;
455 
456  if (pgm_match(m_tmpl, edges, m_tmplHeight, JITTER_RADIUS, &m_matches[frameno]))
457  goto error;
458 
459  end = nowAsDuration<std::chrono::microseconds>();
460  m_analyzeTime += (end - start);
461 
462  return ANALYZE_OK;
463 
464 error:
465  LOG(VB_COMMFLAG, LOG_ERR,
466  QString("TemplateMatcher::analyzeFrame error at frame %1 of %2")
467  .arg(frameno));
468  return ANALYZE_ERROR;
469 }
470 
471 int
472 TemplateMatcher::finished(long long nframes, bool final)
473 {
474  /*
475  * TUNABLE:
476  *
477  * Eliminate false negatives and false positives by eliminating breaks and
478  * segments shorter than these periods, subject to maximum bounds.
479  *
480  * Higher values could eliminate real breaks or segments entirely.
481  * Lower values can yield more false "short" breaks or segments.
482  */
483  const int MINBREAKLEN = (int)roundf(45 * m_fps); /* frames */
484  const int MINSEGLEN = (int)roundf(105 * m_fps); /* frames */
485 
486  int minbreaklen = 1;
487  int minseglen = 1;
488  long long brkb = 0;
489 
491  {
492  if (final && writeMatches(m_debugData, m_matches, nframes))
493  {
494  LOG(VB_COMMFLAG, LOG_INFO,
495  QString("TemplateMatcher::finished wrote %1") .arg(m_debugData));
496  m_matchesDone = true;
497  }
498  }
499 
500  int tmpledges = pgm_set(m_tmpl, m_tmplHeight);
501  int mintmpledges = pick_mintmpledges(m_matches, nframes);
502 
503  LOG(VB_COMMFLAG, LOG_INFO,
504  QString("TemplateMatcher::finished %1x%2@(%3,%4),"
505  " %5 edge pixels, want %6")
506  .arg(m_tmplWidth).arg(m_tmplHeight).arg(m_tmplCol).arg(m_tmplRow)
507  .arg(tmpledges).arg(mintmpledges));
508 
509  for (long long ii = 0; ii < nframes; ii++)
510  m_match[ii] = m_matches[ii] >= mintmpledges ? 1 : 0;
511 
512  if (m_debugLevel >= 2)
513  {
514  if (final && finishedDebug(nframes, m_matches, m_match))
515  goto error;
516  }
517 
518  /*
519  * Construct breaks; find the first logo break.
520  */
521  m_breakMap.clear();
522  brkb = m_match[0] ? matchspn(nframes, m_match, 0, m_match[0]) : 0;
523  while (brkb < nframes)
524  {
525  /* Skip over logo-less frames; find the next logo frame (brke). */
526  long long brke = matchspn(nframes, m_match, brkb, m_match[brkb]);
527  long long brklen = brke - brkb;
528  m_breakMap.insert(brkb, brklen);
529 
530  /* Find the next logo break. */
531  brkb = matchspn(nframes, m_match, brke, m_match[brke]);
532  }
533 
534  /* Clean up the "noise". */
535  minbreaklen = 1;
536  minseglen = 1;
537  for (;;)
538  {
539  bool f1 = false;
540  bool f2 = false;
541  if (minbreaklen <= MINBREAKLEN)
542  {
543  f1 = removeShortBreaks(&m_breakMap, m_fps, minbreaklen,
545  minbreaklen++;
546  }
547  if (minseglen <= MINSEGLEN)
548  {
549  f2 = removeShortSegments(&m_breakMap, nframes, m_fps, minseglen,
551  minseglen++;
552  }
553  if (minbreaklen > MINBREAKLEN && minseglen > MINSEGLEN)
554  break;
555  if (m_debugRemoveRunts && (f1 || f2))
556  frameAnalyzerReportMap(&m_breakMap, m_fps, "** TM Break");
557  }
558 
559  /*
560  * Report breaks.
561  */
562  frameAnalyzerReportMap(&m_breakMap, m_fps, "TM Break");
563 
564  return 0;
565 
566 error:
567  return -1;
568 }
569 
570 int
572 {
573  if (m_pgmConverter->reportTime())
574  return -1;
575 
576  LOG(VB_COMMFLAG, LOG_INFO, QString("TM Time: analyze=%1s")
577  .arg(strftimeval(m_analyzeTime)));
578  return 0;
579 }
580 
581 int
582 TemplateMatcher::templateCoverage(long long nframes, bool final) const
583 {
584  /*
585  * Return <0 for too little logo coverage (some content has no logo), 0 for
586  * "correct" coverage, and >0 for too much coverage (some commercials have
587  * logos).
588  */
589 
590  /*
591  * TUNABLE:
592  *
593  * Expect 20%-45% of total length to be commercials.
594  */
595  static const int64_t MINBREAKS { nframes * 20 / 100 };
596  static const int64_t MAXBREAKS { nframes * 45 / 100 };
597 
598  const int64_t brklen = frameAnalyzerMapSum(&m_breakMap);
599  const bool good = brklen >= MINBREAKS && brklen <= MAXBREAKS;
600 
601  if (m_debugLevel >= 1)
602  {
603  if (!m_tmpl)
604  {
605  LOG(VB_COMMFLAG, LOG_ERR,
606  QString("TemplateMatcher: no template (wanted %2-%3%)")
607  .arg(100 * MINBREAKS / nframes)
608  .arg(100 * MAXBREAKS / nframes));
609  }
610  else if (!final)
611  {
612  LOG(VB_COMMFLAG, LOG_ERR,
613  QString("TemplateMatcher has %1% breaks (real-time flagging)")
614  .arg(100 * brklen / nframes));
615  }
616  else if (good)
617  {
618  LOG(VB_COMMFLAG, LOG_INFO, QString("TemplateMatcher has %1% breaks")
619  .arg(100 * brklen / nframes));
620  }
621  else
622  {
623  LOG(VB_COMMFLAG, LOG_INFO,
624  QString("TemplateMatcher has %1% breaks (wanted %2-%3%)")
625  .arg(100 * brklen / nframes)
626  .arg(100 * MINBREAKS / nframes)
627  .arg(100 * MAXBREAKS / nframes));
628  }
629  }
630 
631  if (!final)
632  return 0; /* real-time flagging */
633 
634  return brklen < MINBREAKS ? 1 : brklen <= MAXBREAKS ? 0 : -1;
635 }
636 
637 int
639  long long nframes)
640 {
641  const FrameAnalyzer::FrameMap *blankMap = blankFrameDetector->getBlanks();
642 
643  /*
644  * TUNABLE:
645  *
646  * Tune the search for blank frames near appearances and disappearances of
647  * the template.
648  *
649  * TEMPLATE_DISAPPEARS_EARLY: Handle the scenario where the template can
650  * disappear "early" before switching to commercial. Delay the beginning of
651  * the commercial break by searching forwards to find a blank frame.
652  *
653  * Setting this value too low can yield false positives. If template
654  * does indeed disappear "early" (common in US broadcast), the end of
655  * the broadcast segment can be misidentified as part of the beginning
656  * of the commercial break.
657  *
658  * Setting this value too high can yield or worsen false negatives. If
659  * the template presence extends at all into the commercial break,
660  * immediate cuts to commercial without any intervening blank frames
661  * can cause the broadcast to "continue" even further into the
662  * commercial break.
663  *
664  * TEMPLATE_DISAPPEARS_LATE: Handle the scenario where the template
665  * disappears "late" after having already switched to commercial (template
666  * presence extends into commercial break). Accelerate the beginning of the
667  * commercial break by searching backwards to find a blank frame.
668  *
669  * Setting this value too low can yield false negatives. If the
670  * template does extend deep into the commercial break, the first part
671  * of the commercial break can be misidentifed as part of the
672  * broadcast.
673  *
674  * Setting this value too high can yield or worsen false positives. If
675  * the template disappears extremely early (too early for
676  * TEMPLATE_DISAPPEARS_EARLY), blank frames before the template
677  * disappears can cause even more of the end of the broadcast segment
678  * to be misidentified as the first part of the commercial break.
679  *
680  * TEMPLATE_REAPPEARS_LATE: Handle the scenario where the template
681  * reappears "late" after having already returned to the broadcast.
682  * Accelerate the beginning of the broadcast by searching backwards to find
683  * a blank frame.
684  *
685  * Setting this value too low can yield false positives. If the
686  * template does indeed reappear "late" (common in US broadcast), the
687  * beginning of the broadcast segment can be misidentified as the end
688  * of the commercial break.
689  *
690  * Setting this value too high can yield or worsen false negatives. If
691  * the template actually reappears early, blank frames before the
692  * template reappears can cause even more of the end of the commercial
693  * break to be misidentified as the first part of the broadcast break.
694  *
695  * TEMPLATE_REAPPEARS_EARLY: Handle the scenario where the template
696  * reappears "early" before resuming the broadcast. Delay the beginning of
697  * the broadcast by searching forwards to find a blank frame.
698  *
699  * Setting this value too low can yield false negatives. If the
700  * template does reappear "early", the the last part of the commercial
701  * break can be misidentified as part of the beginning of the
702  * following broadcast segment.
703  *
704  * Setting this value too high can yield or worsen false positives. If
705  * the template reappears extremely late (too late for
706  * TEMPLATE_REAPPEARS_LATE), blank frames after the template reappears
707  * can cause even more of the broadcast segment can be misidentified
708  * as part of the end of the commercial break.
709  */
710  const int BLANK_NEARBY = (int)roundf(0.5F * m_fps);
711  const int TEMPLATE_DISAPPEARS_EARLY = (int)roundf(25 * m_fps);
712  const int TEMPLATE_DISAPPEARS_LATE = (int)roundf(0 * m_fps);
713  const int TEMPLATE_REAPPEARS_LATE = (int)roundf(35 * m_fps);
714  const int TEMPLATE_REAPPEARS_EARLY = (int)roundf(1.5F * m_fps);
715 
716  LOG(VB_COMMFLAG, LOG_INFO, QString("TemplateMatcher adjusting for blanks"));
717 
718  FrameAnalyzer::FrameMap::Iterator ii = m_breakMap.begin();
719  long long prevbrke = 0;
720  while (ii != m_breakMap.end())
721  {
722  FrameAnalyzer::FrameMap::Iterator iinext = ii;
723  ++iinext;
724 
725  /*
726  * Where the template disappears, look for nearby blank frames. Only
727  * adjust beginning-of-breaks that are not at the very beginning. Do
728  * not search into adjacent breaks.
729  */
730  const long long brkb = ii.key();
731  const long long brke = brkb + *ii;
732  FrameAnalyzer::FrameMap::const_iterator jj = blankMap->constEnd();
733  if (brkb > 0)
734  {
735  jj = frameMapSearchForwards(blankMap,
736  std::max(prevbrke,
737  brkb - std::max(BLANK_NEARBY, TEMPLATE_DISAPPEARS_LATE)),
738  std::min(brke,
739  brkb + std::max(BLANK_NEARBY, TEMPLATE_DISAPPEARS_EARLY)));
740  }
741  long long newbrkb = brkb;
742  if (jj != blankMap->constEnd())
743  {
744  newbrkb = jj.key();
745  long long adj = *jj / 2;
746  if (adj > MAX_BLANK_FRAMES)
747  adj = MAX_BLANK_FRAMES;
748  newbrkb += adj;
749  }
750 
751  /*
752  * Where the template reappears, look for nearby blank frames. Do not
753  * search into adjacent breaks.
754  */
755  FrameAnalyzer::FrameMap::const_iterator kk = frameMapSearchBackwards(
756  blankMap,
757  std::max(newbrkb,
758  brke - std::max(BLANK_NEARBY, TEMPLATE_REAPPEARS_LATE)),
759  std::min(iinext == m_breakMap.end() ? nframes : iinext.key(),
760  brke + std::max(BLANK_NEARBY, TEMPLATE_REAPPEARS_EARLY)));
761  long long newbrke = brke;
762  if (kk != blankMap->constEnd())
763  {
764  newbrke = kk.key();
765  long long adj = *kk;
766  newbrke += adj;
767  adj /= 2;
768  if (adj > MAX_BLANK_FRAMES)
769  adj = MAX_BLANK_FRAMES;
770  newbrke -= adj;
771  }
772 
773  /*
774  * Adjust breakMap for blank frames.
775  */
776  long long newbrklen = newbrke - newbrkb;
777  if (newbrkb != brkb)
778  {
779  m_breakMap.erase(ii);
780  if (newbrkb < nframes && newbrklen)
781  m_breakMap.insert(newbrkb, newbrklen);
782  }
783  else if (newbrke != brke)
784  {
785  if (newbrklen)
786  {
787  m_breakMap.remove(newbrkb);
788  m_breakMap.insert(newbrkb, newbrklen);
789  }
790  else
791  m_breakMap.erase(ii);
792  }
793 
794  prevbrke = newbrke;
795  ii = iinext;
796  }
797 
798  /*
799  * Report breaks.
800  */
801  frameAnalyzerReportMap(&m_breakMap, m_fps, "TM Break");
802  return 0;
803 }
804 
805 int
807 {
808  breaks->clear();
809  for (auto bb = m_breakMap.begin();
810  bb != m_breakMap.end();
811  ++bb)
812  {
813  breaks->insert(bb.key(), *bb);
814  }
815  return 0;
816 }
817 
818 /* vim: set expandtab tabstop=4 shiftwidth=4: */
TemplateMatcher::m_tmplCol
int m_tmplCol
Definition: TemplateMatcher.h:64
TemplateMatcher.h
frameAnalyzer::removeShortBreaks
bool removeShortBreaks(FrameAnalyzer::FrameMap *breakMap, float fps, int minbreaklen, bool verbose)
Definition: FrameAnalyzer.cpp:96
error
static void error(const char *str,...)
Definition: vbi.cpp:36
TemplateMatcher::m_player
MythPlayer * m_player
Definition: TemplateMatcher.h:80
TemplateMatcher::m_fps
float m_fps
Definition: TemplateMatcher.h:72
FrameAnalyzer::ANALYZE_OK
@ ANALYZE_OK
Definition: FrameAnalyzer.h:37
CommDetector2.h
freq
static const std::array< const uint32_t, 4 > freq
Definition: element.cpp:45
TemplateMatcher::m_edgeDetector
std::shared_ptr< EdgeDetector > m_edgeDetector
Definition: TemplateMatcher.h:60
cc
Definition: cc.h:9
FrameAnalyzer::FrameMap
QMap< long long, long long > FrameMap
Definition: FrameAnalyzer.h:45
TemplateMatcher::m_tmplRow
int m_tmplRow
Definition: TemplateMatcher.h:63
TemplateMatcher::m_debugLevel
int m_debugLevel
Definition: TemplateMatcher.h:77
MythPlayer::GetFrameRate
float GetFrameRate(void) const
Definition: mythplayer.h:134
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
mythplayer.h
MythPlayer
Definition: mythplayer.h:83
commDetector2::createDebugDirectory
void createDebugDirectory(const QString &dirname, const QString &comment)
Definition: CommDetector2.cpp:225
mythburn.FILE
int FILE
Definition: mythburn.py:139
TemplateMatcher::m_breakMap
FrameAnalyzer::FrameMap m_breakMap
Definition: TemplateMatcher.h:74
frameAnalyzer::removeShortSegments
bool removeShortSegments(FrameAnalyzer::FrameMap *breakMap, long long nframes, float fps, int minseglen, bool verbose)
Definition: FrameAnalyzer.cpp:143
frameAnalyzer::frameMapSearchBackwards
FrameAnalyzer::FrameMap::const_iterator frameMapSearchBackwards(const FrameAnalyzer::FrameMap *frameMap, long long markbegin, long long mark)
Definition: FrameAnalyzer.cpp:280
AVFrame
struct AVFrame AVFrame
Definition: BorderDetector.h:15
TemplateFinder
Definition: TemplateFinder.h:30
edgeDetector::sort_ascending
static int sort_ascending(const void *aa, const void *bb)
Definition: EdgeDetector.cpp:66
mythlogging.h
pgm.h
PGM_CONVERT_GREYSCALE
#define PGM_CONVERT_GREYSCALE
Definition: PGMConverter.h:28
PGMConverter.h
TemplateMatcher::m_templateFinder
TemplateFinder * m_templateFinder
Definition: TemplateMatcher.h:61
TemplateMatcher::m_matchesDone
bool m_matchesDone
Definition: TemplateMatcher.h:83
BlankFrameDetector.h
p2
static guint32 * p2
Definition: goom_core.cpp:26
TemplateMatcher::m_debugMatches
bool m_debugMatches
Definition: TemplateMatcher.h:81
TemplateMatcher::templateCoverage
int templateCoverage(long long nframes, bool final) const
Definition: TemplateMatcher.cpp:582
pgm_crop
int pgm_crop(AVFrame *dst, const AVFrame *src, [[maybe_unused]] int srcheight, int srcrow, int srccol, int cropwidth, int cropheight)
Definition: pgm.cpp:168
TemplateMatcher::TemplateMatcher
TemplateMatcher(std::shared_ptr< PGMConverter > pgmc, std::shared_ptr< EdgeDetector > ed, TemplateFinder *tf, const QString &debugdir)
Definition: TemplateMatcher.cpp:304
TemplateMatcher::m_debugDir
QString m_debugDir
Definition: TemplateMatcher.h:78
FrameAnalyzer::kNextFrame
static const long long kNextFrame
Definition: FrameAnalyzer.h:59
FrameAnalyzer::analyzeFrameResult
analyzeFrameResult
Definition: FrameAnalyzer.h:36
FrameAnalyzer.h
TemplateMatcher::m_pgmConverter
std::shared_ptr< PGMConverter > m_pgmConverter
Definition: TemplateMatcher.h:59
FrameAnalyzer::ANALYZE_ERROR
@ ANALYZE_ERROR
Definition: FrameAnalyzer.h:38
uint
unsigned int uint
Definition: compat.h:81
TemplateMatcher::m_cropped
AVFrame m_cropped
Definition: TemplateMatcher.h:73
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:55
TemplateMatcher::m_debugData
QString m_debugData
Definition: TemplateMatcher.h:79
commDetector2::strftimeval
QString strftimeval(std::chrono::microseconds usecs)
Definition: CommDetector2.cpp:263
EdgeDetector.h
p1
static guint32 * p1
Definition: goom_core.cpp:26
MythCoreContext::GetNumSetting
int GetNumSetting(const QString &key, int defaultval=0)
Definition: mythcorecontext.cpp:912
TemplateMatcher::m_matches
unsigned short * m_matches
Definition: TemplateMatcher.h:69
BlankFrameDetector
Definition: BlankFrameDetector.h:15
frameAnalyzer::frameAnalyzerMapSum
long long frameAnalyzerMapSum(const FrameAnalyzer::FrameMap *frameMap)
Definition: FrameAnalyzer.cpp:89
BlankFrameDetector::getBlanks
const FrameAnalyzer::FrameMap * getBlanks(void) const
Definition: BlankFrameDetector.h:34
frameAnalyzer::frameAnalyzerReportMap
void frameAnalyzerReportMap(const FrameAnalyzer::FrameMap *frameMap, float fps, const char *comment)
Definition: FrameAnalyzer.cpp:17
mythcorecontext.h
frameAnalyzer::frameMapSearchForwards
FrameAnalyzer::FrameMap::const_iterator frameMapSearchForwards(const FrameAnalyzer::FrameMap *frameMap, long long mark, long long markend)
Definition: FrameAnalyzer.cpp:253
std
Definition: mythchrono.h:23
TemplateMatcher::m_debugRemoveRunts
bool m_debugRemoveRunts
Definition: TemplateMatcher.h:82
TemplateMatcher::analyzeFrame
enum analyzeFrameResult analyzeFrame(const MythVideoFrame *frame, long long frameno, long long *pNextFrame) override
Definition: TemplateMatcher.cpp:396
TemplateMatcher::finished
int finished(long long nframes, bool final) override
Definition: TemplateMatcher.cpp:472
FrameAnalyzer::ANALYZE_FATAL
@ ANALYZE_FATAL
Definition: FrameAnalyzer.h:40
commDetector2
Definition: CommDetector2.cpp:189
FrameAnalyzer::ANALYZE_FINISHED
@ ANALYZE_FINISHED
Definition: FrameAnalyzer.h:39
TemplateMatcher::m_tmpl
const struct AVFrame * m_tmpl
Definition: TemplateMatcher.h:62
TemplateMatcher::~TemplateMatcher
~TemplateMatcher(void) override
Definition: TemplateMatcher.cpp:334
TemplateMatcher::computeBreaks
int computeBreaks(FrameMap *breaks)
Definition: TemplateMatcher.cpp:806
frameAnalyzer
Definition: FrameAnalyzer.cpp:7
MAX_BLANK_FRAMES
static constexpr int64_t MAX_BLANK_FRAMES
Definition: CommDetectorBase.h:11
TemplateMatcher::reportTime
int reportTime(void) const override
Definition: TemplateMatcher.cpp:571
TemplateFinder.h
TemplateMatcher::m_tmplHeight
int m_tmplHeight
Definition: TemplateMatcher.h:66
MythVideoFrame
Definition: mythframe.h:88
TemplateMatcher::MythPlayerInited
enum analyzeFrameResult MythPlayerInited(MythPlayer *player, long long nframes) override
Definition: TemplateMatcher.cpp:342
TemplateMatcher::m_analyzeTime
std::chrono::microseconds m_analyzeTime
Definition: TemplateMatcher.h:84
TemplateMatcher::adjustForBlanks
int adjustForBlanks(const BlankFrameDetector *blankFrameDetector, long long nframes)
Definition: TemplateMatcher.cpp:638
build_compdb.filename
filename
Definition: build_compdb.py:21
TemplateFinder::getTemplate
const struct AVFrame * getTemplate(int *prow, int *pcol, int *pwidth, int *pheight) const
Definition: TemplateFinder.cpp:1041
TemplateMatcher::m_tmplWidth
int m_tmplWidth
Definition: TemplateMatcher.h:65
TemplateMatcher::m_match
unsigned char * m_match
Definition: TemplateMatcher.h:70