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