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