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  if (m_tmpl == nullptr)
351  {
352  LOG(VB_COMMFLAG, LOG_ERR,
353  QString("TemplateMatcher::MythPlayerInited: no template"));
354  return ANALYZE_FATAL;
355  }
356 
357  if (av_image_alloc(m_cropped.data, m_cropped.linesize,
358  m_tmplWidth, m_tmplHeight, AV_PIX_FMT_GRAY8, IMAGE_ALIGN))
359  {
360  LOG(VB_COMMFLAG, LOG_ERR,
361  QString("TemplateMatcher::MythPlayerInited "
362  "av_image_alloc cropped (%1x%2) failed")
363  .arg(m_tmplWidth).arg(m_tmplHeight));
364  return ANALYZE_FATAL;
365  }
366 
367  if (m_pgmConverter->MythPlayerInited(m_player))
368  goto free_cropped;
369 
370  m_matches = new unsigned short[nframes];
371  memset(m_matches, 0, nframes * sizeof(*m_matches));
372 
373  m_match = new unsigned char[nframes];
374 
375  if (m_debugMatches)
376  {
377  if (readMatches(m_debugData, m_matches, nframes))
378  {
379  LOG(VB_COMMFLAG, LOG_INFO,
380  QString("TemplateMatcher::MythPlayerInited read %1")
381  .arg(m_debugData));
382  m_matchesDone = true;
383  }
384  }
385 
386  if (m_matchesDone)
387  return ANALYZE_FINISHED;
388 
389  return ANALYZE_OK;
390 
391 free_cropped:
392  av_freep(&m_cropped.data[0]);
393  return ANALYZE_FATAL;
394 }
395 
397 TemplateMatcher::analyzeFrame(const MythVideoFrame *frame, long long frameno,
398  long long *pNextFrame)
399 {
400  /*
401  * TUNABLE:
402  *
403  * The matching area should be a lot smaller than the area used by
404  * TemplateFinder, so use a smaller percentile than the TemplateFinder
405  * (intensity requirements to be considered an "edge" are lower, because
406  * there should be less pollution from non-template edges).
407  *
408  * Higher values mean fewer edge pixels in the candidate template area;
409  * occluded or faint templates might be missed.
410  *
411  * Lower values mean more edge pixels in the candidate template area;
412  * non-template edges can be picked up and cause false identification of
413  * matches.
414  */
415  const int FRAMESGMPCTILE = 70;
416 
417  /*
418  * TUNABLE:
419  *
420  * The per-pixel search radius for matching the template. Meant to deal
421  * with template edges that might minimally slide around (such as for
422  * animated lighting effects).
423  *
424  * Higher values will pick up more pixels as matching the template
425  * (possibly false matches).
426  *
427  * Lower values will require more exact template matches, possibly missing
428  * true matches.
429  *
430  * The TemplateMatcher accumulates all its state in the "matches" array to
431  * be processed later by TemplateMatcher::finished.
432  */
433  const int JITTER_RADIUS = 0;
434 
435  const AVFrame *edges = nullptr;
436  int pgmwidth = 0;
437  int pgmheight = 0;
438  std::chrono::microseconds start {0us};
439  std::chrono::microseconds end {0us};
440 
441  *pNextFrame = kNextFrame;
442 
443  const AVFrame *pgm = m_pgmConverter->getImage(frame, frameno, &pgmwidth, &pgmheight);
444  if (pgm == nullptr)
445  goto error;
446 
447  start = nowAsDuration<std::chrono::microseconds>();
448 
449  if (pgm_crop(&m_cropped, pgm, pgmheight, m_tmplRow, m_tmplCol,
451  goto error;
452 
453  edges = m_edgeDetector->detectEdges(&m_cropped, m_tmplHeight, FRAMESGMPCTILE);
454  if (edges == nullptr)
455  goto error;
456 
457  if (pgm_match(m_tmpl, edges, m_tmplHeight, JITTER_RADIUS, &m_matches[frameno]))
458  goto error;
459 
460  end = nowAsDuration<std::chrono::microseconds>();
461  m_analyzeTime += (end - start);
462 
463  return ANALYZE_OK;
464 
465 error:
466  LOG(VB_COMMFLAG, LOG_ERR,
467  QString("TemplateMatcher::analyzeFrame error at frame %1 of %2")
468  .arg(frameno));
469  return ANALYZE_ERROR;
470 }
471 
472 int
473 TemplateMatcher::finished(long long nframes, bool final)
474 {
475  /*
476  * TUNABLE:
477  *
478  * Eliminate false negatives and false positives by eliminating breaks and
479  * segments shorter than these periods, subject to maximum bounds.
480  *
481  * Higher values could eliminate real breaks or segments entirely.
482  * Lower values can yield more false "short" breaks or segments.
483  */
484  const int MINBREAKLEN = (int)roundf(45 * m_fps); /* frames */
485  const int MINSEGLEN = (int)roundf(105 * m_fps); /* frames */
486 
487  int minbreaklen = 1;
488  int minseglen = 1;
489  long long brkb = 0;
490 
492  {
493  if (final && writeMatches(m_debugData, m_matches, nframes))
494  {
495  LOG(VB_COMMFLAG, LOG_INFO,
496  QString("TemplateMatcher::finished wrote %1") .arg(m_debugData));
497  m_matchesDone = true;
498  }
499  }
500 
501  int tmpledges = pgm_set(m_tmpl, m_tmplHeight);
502  int mintmpledges = pick_mintmpledges(m_matches, nframes);
503 
504  LOG(VB_COMMFLAG, LOG_INFO,
505  QString("TemplateMatcher::finished %1x%2@(%3,%4),"
506  " %5 edge pixels, want %6")
507  .arg(m_tmplWidth).arg(m_tmplHeight).arg(m_tmplCol).arg(m_tmplRow)
508  .arg(tmpledges).arg(mintmpledges));
509 
510  for (long long ii = 0; ii < nframes; ii++)
511  m_match[ii] = m_matches[ii] >= mintmpledges ? 1 : 0;
512 
513  if (m_debugLevel >= 2)
514  {
515  if (final && finishedDebug(nframes, m_matches, m_match))
516  goto error;
517  }
518 
519  /*
520  * Construct breaks; find the first logo break.
521  */
522  m_breakMap.clear();
523  brkb = m_match[0] ? matchspn(nframes, m_match, 0, m_match[0]) : 0;
524  while (brkb < nframes)
525  {
526  /* Skip over logo-less frames; find the next logo frame (brke). */
527  long long brke = matchspn(nframes, m_match, brkb, m_match[brkb]);
528  long long brklen = brke - brkb;
529  m_breakMap.insert(brkb, brklen);
530 
531  /* Find the next logo break. */
532  brkb = matchspn(nframes, m_match, brke, m_match[brke]);
533  }
534 
535  /* Clean up the "noise". */
536  minbreaklen = 1;
537  minseglen = 1;
538  for (;;)
539  {
540  bool f1 = false;
541  bool f2 = false;
542  if (minbreaklen <= MINBREAKLEN)
543  {
544  f1 = removeShortBreaks(&m_breakMap, m_fps, minbreaklen,
546  minbreaklen++;
547  }
548  if (minseglen <= MINSEGLEN)
549  {
550  f2 = removeShortSegments(&m_breakMap, nframes, m_fps, minseglen,
552  minseglen++;
553  }
554  if (minbreaklen > MINBREAKLEN && minseglen > MINSEGLEN)
555  break;
556  if (m_debugRemoveRunts && (f1 || f2))
557  frameAnalyzerReportMap(&m_breakMap, m_fps, "** TM Break");
558  }
559 
560  /*
561  * Report breaks.
562  */
563  frameAnalyzerReportMap(&m_breakMap, m_fps, "TM Break");
564 
565  return 0;
566 
567 error:
568  return -1;
569 }
570 
571 int
573 {
574  if (m_pgmConverter->reportTime())
575  return -1;
576 
577  LOG(VB_COMMFLAG, LOG_INFO, QString("TM Time: analyze=%1s")
578  .arg(strftimeval(m_analyzeTime)));
579  return 0;
580 }
581 
582 int
583 TemplateMatcher::templateCoverage(long long nframes, bool final) const
584 {
585  /*
586  * Return <0 for too little logo coverage (some content has no logo), 0 for
587  * "correct" coverage, and >0 for too much coverage (some commercials have
588  * logos).
589  */
590 
591  /*
592  * TUNABLE:
593  *
594  * Expect 20%-45% of total length to be commercials.
595  */
596  static const int64_t MINBREAKS { nframes * 20 / 100 };
597  static const int64_t MAXBREAKS { nframes * 45 / 100 };
598 
599  const int64_t brklen = frameAnalyzerMapSum(&m_breakMap);
600  const bool good = brklen >= MINBREAKS && brklen <= MAXBREAKS;
601 
602  if (m_debugLevel >= 1)
603  {
604  if (!m_tmpl)
605  {
606  LOG(VB_COMMFLAG, LOG_ERR,
607  QString("TemplateMatcher: no template (wanted %2-%3%)")
608  .arg(100 * MINBREAKS / nframes)
609  .arg(100 * MAXBREAKS / nframes));
610  }
611  else if (!final)
612  {
613  LOG(VB_COMMFLAG, LOG_ERR,
614  QString("TemplateMatcher has %1% breaks (real-time flagging)")
615  .arg(100 * brklen / nframes));
616  }
617  else if (good)
618  {
619  LOG(VB_COMMFLAG, LOG_INFO, QString("TemplateMatcher has %1% breaks")
620  .arg(100 * brklen / nframes));
621  }
622  else
623  {
624  LOG(VB_COMMFLAG, LOG_INFO,
625  QString("TemplateMatcher has %1% breaks (wanted %2-%3%)")
626  .arg(100 * brklen / nframes)
627  .arg(100 * MINBREAKS / nframes)
628  .arg(100 * MAXBREAKS / nframes));
629  }
630  }
631 
632  if (!final)
633  return 0; /* real-time flagging */
634 
635  return brklen < MINBREAKS ? 1 : brklen <= MAXBREAKS ? 0 : -1;
636 }
637 
638 int
640  long long nframes)
641 {
642  const FrameAnalyzer::FrameMap *blankMap = blankFrameDetector->getBlanks();
643 
644  /*
645  * TUNABLE:
646  *
647  * Tune the search for blank frames near appearances and disappearances of
648  * the template.
649  *
650  * TEMPLATE_DISAPPEARS_EARLY: Handle the scenario where the template can
651  * disappear "early" before switching to commercial. Delay the beginning of
652  * the commercial break by searching forwards to find a blank frame.
653  *
654  * Setting this value too low can yield false positives. If template
655  * does indeed disappear "early" (common in US broadcast), the end of
656  * the broadcast segment can be misidentified as part of the beginning
657  * of the commercial break.
658  *
659  * Setting this value too high can yield or worsen false negatives. If
660  * the template presence extends at all into the commercial break,
661  * immediate cuts to commercial without any intervening blank frames
662  * can cause the broadcast to "continue" even further into the
663  * commercial break.
664  *
665  * TEMPLATE_DISAPPEARS_LATE: Handle the scenario where the template
666  * disappears "late" after having already switched to commercial (template
667  * presence extends into commercial break). Accelerate the beginning of the
668  * commercial break by searching backwards to find a blank frame.
669  *
670  * Setting this value too low can yield false negatives. If the
671  * template does extend deep into the commercial break, the first part
672  * of the commercial break can be misidentifed as part of the
673  * broadcast.
674  *
675  * Setting this value too high can yield or worsen false positives. If
676  * the template disappears extremely early (too early for
677  * TEMPLATE_DISAPPEARS_EARLY), blank frames before the template
678  * disappears can cause even more of the end of the broadcast segment
679  * to be misidentified as the first part of the commercial break.
680  *
681  * TEMPLATE_REAPPEARS_LATE: Handle the scenario where the template
682  * reappears "late" after having already returned to the broadcast.
683  * Accelerate the beginning of the broadcast by searching backwards to find
684  * a blank frame.
685  *
686  * Setting this value too low can yield false positives. If the
687  * template does indeed reappear "late" (common in US broadcast), the
688  * beginning of the broadcast segment can be misidentified as the end
689  * of the commercial break.
690  *
691  * Setting this value too high can yield or worsen false negatives. If
692  * the template actually reappears early, blank frames before the
693  * template reappears can cause even more of the end of the commercial
694  * break to be misidentified as the first part of the broadcast break.
695  *
696  * TEMPLATE_REAPPEARS_EARLY: Handle the scenario where the template
697  * reappears "early" before resuming the broadcast. Delay the beginning of
698  * the broadcast by searching forwards to find a blank frame.
699  *
700  * Setting this value too low can yield false negatives. If the
701  * template does reappear "early", the the last part of the commercial
702  * break can be misidentified as part of the beginning of the
703  * following broadcast segment.
704  *
705  * Setting this value too high can yield or worsen false positives. If
706  * the template reappears extremely late (too late for
707  * TEMPLATE_REAPPEARS_LATE), blank frames after the template reappears
708  * can cause even more of the broadcast segment can be misidentified
709  * as part of the end of the commercial break.
710  */
711  const int BLANK_NEARBY = (int)roundf(0.5F * m_fps);
712  const int TEMPLATE_DISAPPEARS_EARLY = (int)roundf(25 * m_fps);
713  const int TEMPLATE_DISAPPEARS_LATE = (int)roundf(0 * m_fps);
714  const int TEMPLATE_REAPPEARS_LATE = (int)roundf(35 * m_fps);
715  const int TEMPLATE_REAPPEARS_EARLY = (int)roundf(1.5F * m_fps);
716 
717  LOG(VB_COMMFLAG, LOG_INFO, QString("TemplateMatcher adjusting for blanks"));
718 
719  FrameAnalyzer::FrameMap::Iterator ii = m_breakMap.begin();
720  long long prevbrke = 0;
721  while (ii != m_breakMap.end())
722  {
723  FrameAnalyzer::FrameMap::Iterator iinext = ii;
724  ++iinext;
725 
726  /*
727  * Where the template disappears, look for nearby blank frames. Only
728  * adjust beginning-of-breaks that are not at the very beginning. Do
729  * not search into adjacent breaks.
730  */
731  const long long brkb = ii.key();
732  const long long brke = brkb + *ii;
733  FrameAnalyzer::FrameMap::const_iterator jj = blankMap->constEnd();
734  if (brkb > 0)
735  {
736  jj = frameMapSearchForwards(blankMap,
737  std::max(prevbrke,
738  brkb - std::max(BLANK_NEARBY, TEMPLATE_DISAPPEARS_LATE)),
739  std::min(brke,
740  brkb + std::max(BLANK_NEARBY, TEMPLATE_DISAPPEARS_EARLY)));
741  }
742  long long newbrkb = brkb;
743  if (jj != blankMap->constEnd())
744  {
745  newbrkb = jj.key();
746  long long adj = *jj / 2;
747  if (adj > MAX_BLANK_FRAMES)
748  adj = MAX_BLANK_FRAMES;
749  newbrkb += adj;
750  }
751 
752  /*
753  * Where the template reappears, look for nearby blank frames. Do not
754  * search into adjacent breaks.
755  */
756  FrameAnalyzer::FrameMap::const_iterator kk = frameMapSearchBackwards(
757  blankMap,
758  std::max(newbrkb,
759  brke - std::max(BLANK_NEARBY, TEMPLATE_REAPPEARS_LATE)),
760  std::min(iinext == m_breakMap.end() ? nframes : iinext.key(),
761  brke + std::max(BLANK_NEARBY, TEMPLATE_REAPPEARS_EARLY)));
762  long long newbrke = brke;
763  if (kk != blankMap->constEnd())
764  {
765  newbrke = kk.key();
766  long long adj = *kk;
767  newbrke += adj;
768  adj /= 2;
769  if (adj > MAX_BLANK_FRAMES)
770  adj = MAX_BLANK_FRAMES;
771  newbrke -= adj;
772  }
773 
774  /*
775  * Adjust breakMap for blank frames.
776  */
777  long long newbrklen = newbrke - newbrkb;
778  if (newbrkb != brkb)
779  {
780  m_breakMap.erase(ii);
781  if (newbrkb < nframes && newbrklen)
782  m_breakMap.insert(newbrkb, newbrklen);
783  }
784  else if (newbrke != brke)
785  {
786  if (newbrklen)
787  {
788  m_breakMap.remove(newbrkb);
789  m_breakMap.insert(newbrkb, newbrklen);
790  }
791  else
792  m_breakMap.erase(ii);
793  }
794 
795  prevbrke = newbrke;
796  ii = iinext;
797  }
798 
799  /*
800  * Report breaks.
801  */
802  frameAnalyzerReportMap(&m_breakMap, m_fps, "TM Break");
803  return 0;
804 }
805 
806 int
808 {
809  breaks->clear();
810  for (auto bb = m_breakMap.begin();
811  bb != m_breakMap.end();
812  ++bb)
813  {
814  breaks->insert(bb.key(), *bb);
815  }
816  return 0;
817 }
818 
819 /* 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:583
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:397
TemplateMatcher::finished
int finished(long long nframes, bool final) override
Definition: TemplateMatcher.cpp:473
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:807
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:572
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:639
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:1046
TemplateMatcher::m_tmplWidth
int m_tmplWidth
Definition: TemplateMatcher.h:65
TemplateMatcher::m_match
unsigned char * m_match
Definition: TemplateMatcher.h:70