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