Ticket #1877: mythtv-commflag.diff

File mythtv-commflag.diff, 125.0 KB (added by Robert Tsai, 15 years ago)

some minor tweakage

  • libs/libmythtv/dbcheck.cpp

     
    1010#include "mythdbcon.h"
    1111
    1212/// This is the DB schema version expected by the running MythTV instance.
    13 const QString currentDatabaseVersion = "1141";
     13const QString currentDatabaseVersion = "1142";
    1414
    1515static bool UpdateDBVersionNumber(const QString &newnumber);
    1616static bool performActualUpdate(const QString updates[], QString version,
     
    22512251    }
    22522252
    22532253
     2254    if (dbver == "1141")
     2255    {
     2256        const QString updates[] = {
     2257"ALTER TABLE recorded ADD COLUMN template VARCHAR(128) NOT NULL default '';",
     2258"ALTER TABLE recorded ADD COLUMN template_row INT UNSIGNED NOT NULL default 0;",
     2259"ALTER TABLE recorded ADD COLUMN template_col INT UNSIGNED NOT NULL default 0;",
     2260"ALTER TABLE recorded ADD COLUMN template_width INT UNSIGNED NOT NULL default 0;",
     2261"ALTER TABLE recorded ADD COLUMN template_height INT UNSIGNED NOT NULL default 0;",
     2262"",
     2263};
     2264
     2265        if (!performActualUpdate(updates, "1142", dbver))
     2266            return false;
     2267    }
     2268
    22542269//"ALTER TABLE capturecard DROP COLUMN dvb_recordts;" in 0.21
    22552270//"ALTER TABLE capturecard DROP COLUMN dvb_hw_decoder;" in 0.21
    22562271//"ALTER TABLE cardinput DROP COLUMN  preference;" in 0.22
  • programs/mythcommflag/EdgeDetector.cpp

     
     1#include "NuppelVideoPlayer.h"
     2#include "avcodec.h"        // AVPicture
     3#include "frame.h"          // VideoFrame
     4
     5#include "EdgeDetector.h"
     6
     7namespace edgeDetector {
     8
     9unsigned int *
     10sgm_init(unsigned int *sgm, const AVPicture *src, int srcheight)
     11{
     12    /*
     13     * Squared Gradient Magnitude (SGM) calculations: use a 45-degree rotated
     14     * set of axes.
     15     *
     16     * Intuitively, the SGM of a pixel is a measure of the "edge intensity" of
     17     * that pixel: how much it differs from its neighbors.
     18     */
     19    const int       srcwidth = src->linesize[0];
     20    int             rr, cc, dx, dy;
     21    unsigned char   *rr0, *rr1;
     22
     23    (void)srcheight;    /* gcc */
     24    for (rr = 0; rr < srcheight - 1; rr++)
     25    {
     26        for (cc = 0; cc < srcwidth - 1; cc++)
     27        {
     28            rr0 = src->data[0] + rr * srcwidth + cc;
     29            rr1 = src->data[0] + (rr + 1) * srcwidth + cc;
     30            dx = rr1[1] - rr0[0];   /* southeast - northwest */
     31            dy = rr1[0] - rr0[1];   /* southwest - northeast */
     32            sgm[rr * srcwidth + cc] = dx * dx + dy * dy;
     33        }
     34    }
     35    return sgm;
     36}
     37
     38int
     39sort_ascending(const void *aa, const void *bb)
     40{
     41    return *(unsigned int*)aa - *(unsigned int*)bb;
     42}
     43
     44int
     45edge_mark(AVPicture *dst, int dstheight,
     46        int extratop, int extraright, int extrabottom, int extraleft,
     47        const unsigned int *sgm, unsigned int *sgmsorted,
     48        int percentile)
     49{
     50    /*
     51     * TUNABLE:
     52     *
     53     * Conventionally, the Canny edge detector should select for intensities at
     54     * the 95th percentile or higher. In case the requested percentile actually
     55     * yields something lower (degenerate cases), pick the next unique
     56     * intensity, to try to salvage useful data.
     57     */
     58    static const int    MINTHRESHOLDPCT = 95;
     59
     60    const int           dstwidth = dst->linesize[0];
     61    const int           padded_width = extraleft + dstwidth + extraright;
     62    unsigned int        thresholdval;
     63    int                 nn, ii, rr, cc, first, last;
     64
     65    /*
     66     * sgm: SGM values of padded (convolved) image
     67     *
     68     * sgmsorted: SGM values of unpadded image (same dimensions as "dst"), then
     69     * with all values sorted.
     70     */
     71    for (rr = 0; rr < dstheight; rr++)
     72    {
     73        memcpy(sgmsorted + rr * dstwidth,
     74                sgm + (extratop + rr) * padded_width + extraleft,
     75                dstwidth * sizeof(*sgmsorted));
     76    }
     77    nn = dstheight * dstwidth;
     78    qsort(sgmsorted, nn, sizeof(*sgmsorted), sort_ascending);
     79
     80    ii = percentile * nn / 100;
     81    thresholdval = sgmsorted[ii];
     82
     83    memset(dst->data[0], 0, nn * sizeof(*dst->data[0]));
     84
     85    /*
     86     * Try not to pick up too many edges, and eliminate degenerate edge-less
     87     * cases.
     88     */
     89    for (first = ii; first > 0 && sgmsorted[first] == thresholdval; first--) ;
     90    if (sgmsorted[first] != thresholdval)
     91        first++;
     92    if (first * 100 / nn < MINTHRESHOLDPCT)
     93    {
     94        unsigned int    newthresholdval;
     95
     96        for (last = ii; last < nn - 1 && sgmsorted[last] == thresholdval;
     97                last++) ;
     98        if (sgmsorted[last] != thresholdval)
     99            last--;
     100
     101        newthresholdval = sgmsorted[min(last + 1, nn - 1)];
     102        if (thresholdval == newthresholdval)
     103        {
     104            /* Degenerate case; no edges (e.g., blank frame). */
     105            return 0;
     106        }
     107
     108        thresholdval = newthresholdval;
     109    }
     110
     111    /* sgm is a padded matrix; dst is the unpadded matrix. */
     112    for (rr = 0; rr < dstheight; rr++)
     113    {
     114        for (cc = 0; cc < dstwidth; cc++)
     115        {
     116            if (sgm[(extratop + rr) * padded_width + extraleft + cc] >=
     117                    thresholdval)
     118                dst->data[0][rr * dstwidth + cc] = UCHAR_MAX;
     119        }
     120    }
     121
     122    (void)extrabottom;  /* gcc */
     123    return 0;
     124}
     125
     126int edge_mark_uniform(AVPicture *dst, int dstheight, int extramargin,
     127        const unsigned int *sgm, unsigned int *sgmsorted,
     128        int percentile)
     129{
     130    return edge_mark(dst, dstheight,
     131            extramargin, extramargin, extramargin, extramargin,
     132            sgm, sgmsorted, percentile);
     133}
     134
     135};  /* namespace */
     136
     137EdgeDetector::~EdgeDetector(void)
     138{
     139}
     140
     141/* vim: set expandtab tabstop=4 shiftwidth=4: */
  • programs/mythcommflag/TemplateFinder.cpp

     
     1#include <qdir.h>
     2#include <qfile.h>
     3#include <qfileinfo.h>
     4#include <math.h>
     5
     6#include "NuppelVideoPlayer.h"
     7#include "avcodec.h"        /* AVPicture */
     8#include "mythcontext.h"    /* gContext */
     9#include "frame.h"          /* VideoFrame */
     10
     11#include "pgm.h"
     12#include "PGMConverter.h"
     13#include "BorderDetector.h"
     14#include "EdgeDetector.h"
     15#include "TemplateFinder.h"
     16
     17namespace {
     18
     19int
     20pgm_scorepixels(unsigned int *scores, int width, int row, int col,
     21        const AVPicture *src, int srcheight)
     22{
     23    /* Every time a pixel is an edge, give it a point. */
     24    const int   srcwidth = src->linesize[0];
     25    int         rr, cc;
     26
     27    for (rr = 0; rr < srcheight; rr++)
     28    {
     29        for (cc = 0; cc < srcwidth; cc++)
     30        {
     31            if (src->data[0][rr * srcwidth + cc])
     32                scores[(row + rr) * width + col + cc]++;
     33        }
     34    }
     35
     36    return 0;
     37}
     38
     39int
     40sort_ascending(const void *aa, const void *bb)
     41{
     42    return *(unsigned int*)aa - *(unsigned int*)bb;
     43}
     44
     45unsigned int
     46bounding_score(const AVPicture *img, int row, int col, int width, int height)
     47{
     48    /* Return a value scaled to [0..1000000 (100M)] */
     49    const int       imgwidth = img->linesize[0];
     50    unsigned int    score;
     51    int             rr, cc;
     52
     53    score = 0;
     54    for (rr = row; rr < row + height; rr++)
     55    {
     56        for (cc = col; cc < col + width; cc++)
     57        {
     58            if (img->data[0][rr * imgwidth + cc])
     59                score++;
     60        }
     61    }
     62    return score * 1000000 / (width * height);
     63}
     64
     65int
     66bounding_box(const AVPicture *img,
     67        int minrow, int maxrow, int mincol, int maxcol,
     68        int *prow, int *pcol, int *pwidth, int *pheight)
     69{
     70    /*
     71     * TUNABLE:
     72     *
     73     * Maximum logo size, expressed as a percentage of the content area
     74     * (adjusting for letterboxing and pillarboxing).
     75     */
     76    static const int    MAXWIDTHPCT = 20;
     77    static const int    MAXHEIGHTPCT = 20;
     78
     79    int                 maxwidth, maxheight;
     80    int                 width, height, row, col;
     81
     82    maxwidth = (maxcol - mincol + 1) * MAXWIDTHPCT / 100;
     83    maxheight = (maxrow - minrow + 1) * MAXHEIGHTPCT / 100;
     84
     85    row = minrow;
     86    col = mincol;
     87    width = maxcol - mincol + 1;
     88    height = maxrow - minrow + 1;
     89
     90    for (;;)
     91    {
     92        unsigned int    score, newscore;
     93        int             ii, newrow, newcol, newright, newbottom;
     94        bool            improved = false;
     95
     96        VERBOSE(VB_COMMFLAG, QString("bounding_box %1x%2@(%3,%4)")
     97                .arg(width).arg(height).arg(col).arg(row));
     98
     99        /* Chop top. */
     100        score = bounding_score(img, row, col, width, height);
     101        newrow = row;
     102        for (ii = 1; ii < height; ii++)
     103        {
     104            if ((newscore = bounding_score(img, row + ii, col,
     105                            width, height - ii)) < score)
     106                break;
     107            score = newscore;
     108            newrow = row + ii;
     109            improved = true;
     110        }
     111
     112        /* Chop left. */
     113        score = bounding_score(img, row, col, width, height);
     114        newcol = col;
     115        for (ii = 1; ii < width; ii++)
     116        {
     117            if ((newscore = bounding_score(img, row, col + ii,
     118                            width - ii, height)) < score)
     119                break;
     120            score = newscore;
     121            newcol = col + ii;
     122            improved = true;
     123        }
     124
     125        /* Chop bottom. */
     126        score = bounding_score(img, row, col, width, height);
     127        newbottom = row + height;
     128        for (ii = 1; ii < height; ii++)
     129        {
     130            if ((newscore = bounding_score(img, row, col,
     131                            width, height - ii)) < score)
     132                break;
     133            score = newscore;
     134            newbottom = row + height - ii;
     135            improved = true;
     136        }
     137
     138        /* Chop right. */
     139        score = bounding_score(img, row, col, width, height);
     140        newright = col + width;
     141        for (ii = 1; ii < width; ii++)
     142        {
     143            if ((newscore = bounding_score(img, row, col,
     144                            width - ii, height)) < score)
     145                break;
     146            score = newscore;
     147            newright = col + width - ii;
     148            improved = true;
     149        }
     150
     151        if (!improved)
     152            break;
     153
     154        row = newrow;
     155        col = newcol;
     156        width = newright - newcol;
     157        height = newbottom - newrow;
     158
     159        /*
     160         * Noise edge pixels in the frequency template can sometimes stretch
     161         * the template area to be larger than it should be.
     162         *
     163         * However, noise needs to be distinguished from a uniform distribution
     164         * of noise pixels (e.g., no real statically-located template). So if
     165         * the template area is too "large", then some quadrant must have a
     166         * clear majority of the edge pixels; otherwise we declare failure (no
     167         * template found).
     168         *
     169         * Intuitively, we should simply repeat until a single bounding box is
     170         * converged upon. However, this requires a more sophisticated
     171         * bounding_score function that I don't feel like figuring out.
     172         * Indefinitely repeating with the present bounding_score function will
     173         * tend to chop off too much. Instead, simply do some sanity checks on
     174         * the candidate template's size, and prune the template area and
     175         * repeat if it is too "large".
     176         */
     177
     178        if (width > maxwidth)
     179        {
     180            /* Too wide; test left and right portions. */
     181            int             chop = width / 3;
     182            int             chopwidth = width - chop;
     183            unsigned int    left, right, minscore, maxscore;
     184
     185            left = bounding_score(img, row, col, chopwidth, height);
     186            right = bounding_score(img, row, col + chop, chopwidth, height);
     187            VERBOSE(VB_COMMFLAG, QString(
     188                        "bounding_box too wide (%1 > %2); left=%3, right=%4")
     189                    .arg(width).arg(maxwidth).arg(left).arg(right));
     190            minscore = min(left, right);
     191            maxscore = max(left, right);
     192            if (maxscore < 3 * minscore / 2)
     193            {
     194                /*
     195                 * Edge pixel distribution too uniform; give up.
     196                 *
     197                 * XXX: also fails for left-right centered templates ...
     198                 */
     199                VERBOSE(VB_COMMFLAG, "bounding_box giving up"
     200                        " (edge pixel distribution too uniform)");
     201                return -1;
     202            }
     203
     204            if (left < right)
     205                col += chop;
     206            width -= chop;
     207            continue;
     208        }
     209
     210        if (height > maxheight)
     211        {
     212            /* Too tall; test upper and lower portions. */
     213            int             chop = height / 3;
     214            int             chopheight = height - chop;
     215            unsigned int    upper, lower, minscore, maxscore;
     216
     217            upper = bounding_score(img, row, col, width, chopheight);
     218            lower = bounding_score(img, row + chop, col, width, chopheight);
     219            VERBOSE(VB_COMMFLAG, QString(
     220                        "bounding_box too tall (%1 > %2); upper=%3, lower=%4")
     221                    .arg(height).arg(maxheight).arg(upper).arg(lower));
     222            minscore = min(upper, lower);
     223            maxscore = max(upper, lower);
     224            if (maxscore < 3 * minscore / 2)
     225            {
     226                /*
     227                 * Edge pixel distribution too uniform; give up.
     228                 *
     229                 * XXX: also fails for upper-lower centered templates ...
     230                 */
     231                VERBOSE(VB_COMMFLAG, "bounding_box giving up"
     232                        " (edge pixel distribution too uniform)");
     233                return -1;
     234            }
     235
     236            if (upper < lower)
     237                row += chop;
     238            height -= chop;
     239            continue;
     240        }
     241
     242        break;
     243    }
     244
     245    VERBOSE(VB_COMMFLAG, QString("bounding_box %1x%2@(%3,%4)")
     246            .arg(width).arg(height).arg(col).arg(row));
     247
     248    *prow = row;
     249    *pcol = col;
     250    *pwidth = width;
     251    *pheight = height;
     252    return 0;
     253}
     254
     255int
     256template_alloc(const unsigned int *scores, int width, int height,
     257        int minrow, int maxrow, int mincol, int maxcol, AVPicture *tmpl,
     258        int *ptmplrow, int *ptmplcol, int *ptmplwidth, int *ptmplheight,
     259        int debugLevel, QString debugdir)
     260{
     261    /*
     262     * TUNABLE:
     263     *
     264     * Higher values select for "stronger" pixels to be in the template, but
     265     * weak pixels might be missed.
     266     *
     267     * Lower values allow more pixels to be included as part of the template,
     268     * but strong non-template pixels might be included.
     269     */
     270    static const float      MINSCOREPCTILE = 0.998;
     271
     272    const int               nn = width * height;
     273    int                     ii, first, last;
     274    unsigned int            *sortedscores, threshscore;
     275    AVPicture               thresh;
     276
     277    if (avpicture_alloc(&thresh, PIX_FMT_GRAY8, width, height))
     278    {
     279        VERBOSE(VB_COMMFLAG, QString("template_alloc "
     280                "avpicture_alloc thresh (%1x%2) failed")
     281                .arg(width).arg(height));
     282        return -1;
     283    }
     284
     285    sortedscores = new unsigned int[nn];
     286    memcpy(sortedscores, scores, nn * sizeof(*sortedscores));
     287    qsort(sortedscores, nn, sizeof(*sortedscores), sort_ascending);
     288
     289    if (sortedscores[0] == sortedscores[nn - 1])
     290    {
     291        /* All pixels in the template area look the same; no template. */
     292        VERBOSE(VB_COMMFLAG, QString("template_alloc: pixels all identical!"));
     293        goto free_thresh;
     294    }
     295
     296    /* Threshold the edge frequences. */
     297
     298    ii = (int)roundf(nn * MINSCOREPCTILE);
     299    threshscore = sortedscores[ii];
     300    for (first = ii; first > 0 && sortedscores[first] == threshscore; first--)
     301        ;
     302    if (sortedscores[first] != threshscore)
     303        first++;
     304    for (last = ii; last < nn - 1 && sortedscores[last] == threshscore; last++)
     305        ;
     306    if (sortedscores[last] != threshscore)
     307        last--;
     308
     309    VERBOSE(VB_COMMFLAG, QString("template_alloc wanted %1, got %2-%3")
     310            .arg(MINSCOREPCTILE, 0, 'f', 6)
     311            .arg((float)first / nn, 0, 'f', 6)
     312            .arg((float)last / nn, 0, 'f', 6));
     313
     314    for (ii = 0; ii < nn; ii++)
     315        thresh.data[0][ii] = scores[ii] >= threshscore ? UCHAR_MAX : 0;
     316
     317    if (debugLevel >= 1)
     318    {
     319        QString convertfmt("convert -quality 50 -resize 192x144 %1 %2");
     320        QString base, pgmfile, jpgfile;
     321        QFile tfile;
     322
     323        base = debugdir + "/tf-edgecounts";
     324        pgmfile = base + ".pgm";
     325        jpgfile = base + ".jpg";
     326        if (pgm_write(thresh.data[0], width, height, pgmfile.ascii()))
     327            goto free_thresh;
     328        if (myth_system(convertfmt.arg(pgmfile).arg(jpgfile)))
     329            goto free_thresh;
     330        tfile.setName(pgmfile);
     331        if (!tfile.remove())
     332        {
     333            VERBOSE(VB_COMMFLAG, QString(
     334                        "template_alloc error removing %1 (%2)")
     335                    .arg(tfile.name()).arg(strerror(errno)));
     336            goto free_thresh;
     337        }
     338    }
     339
     340    /* Crop to a minimal bounding box. */
     341
     342    if (bounding_box(&thresh, minrow, maxrow, mincol, maxcol,
     343                ptmplrow, ptmplcol, ptmplwidth, ptmplheight))
     344        goto free_thresh;
     345
     346    if (*ptmplwidth * *ptmplheight > USHRT_MAX)
     347    {
     348        /* Max value of data type of TemplateMatcher::edgematch */
     349        VERBOSE(VB_COMMFLAG, QString(
     350                    "template_alloc bounding_box too big (%1x%2)")
     351                .arg(*ptmplwidth).arg(*ptmplheight));
     352        goto free_thresh;
     353    }
     354
     355    if (avpicture_alloc(tmpl, PIX_FMT_GRAY8, *ptmplwidth, *ptmplheight))
     356    {
     357        VERBOSE(VB_COMMFLAG, QString("template_alloc "
     358                "avpicture_alloc tmpl (%1x%2) failed")
     359                .arg(*ptmplwidth).arg(*ptmplheight));
     360        goto free_thresh;
     361    }
     362
     363    if (pgm_crop(tmpl, &thresh, height, *ptmplrow, *ptmplcol,
     364                *ptmplwidth, *ptmplheight))
     365        goto free_thresh;
     366
     367    delete []sortedscores;
     368    avpicture_free(&thresh);
     369    return 0;
     370
     371free_thresh:
     372    delete []sortedscores;
     373    avpicture_free(&thresh);
     374    return -1;
     375}
     376
     377int
     378analyzeFrameDebug(long long frameno, const AVPicture *pgm, int pgmheight,
     379        const AVPicture *cropped, const AVPicture *edges, int cropheight,
     380        int croprow, int cropcol, QString debugdir)
     381{
     382    static const int    delta = 24;
     383    static int          lastrow, lastcol, lastwidth, lastheight;
     384    const int           pgmwidth = pgm->linesize[0];
     385    const int           cropwidth = cropped->linesize[0];
     386    int                 rowsame, colsame, widthsame, heightsame;
     387
     388    rowsame = abs(lastrow - croprow) <= delta ? 1 : 0;
     389    colsame = abs(lastcol - cropcol) <= delta ? 1 : 0;
     390    widthsame = abs(lastwidth - cropwidth) <= delta ? 1 : 0;
     391    heightsame = abs(lastheight - cropheight) <= delta ? 1 : 0;
     392    if (frameno > 0 && rowsame + colsame + widthsame + heightsame >= 3)
     393        return 0;
     394
     395    VERBOSE(VB_COMMFLAG, QString("TemplateFinder Frame %1: %2x%3@(%4,%5)")
     396            .arg(frameno, 5)
     397            .arg(cropwidth).arg(cropheight)
     398            .arg(cropcol).arg(croprow));
     399    lastrow = croprow;
     400    lastcol = cropcol;
     401    lastwidth = cropwidth;
     402    lastheight = cropheight;
     403
     404    QString convertfmt("convert -quality 50 -resize 192x144 %1 %2");
     405    QString base, pgmfile, jpgfile;
     406    QFile tfile;
     407    base.sprintf("%s/tf-%05lld", debugdir.ascii(), frameno);
     408
     409    pgmfile = base + ".pgm";
     410    jpgfile = base + ".jpg";
     411    if (pgm_write(pgm->data[0], pgmwidth, pgmheight, pgmfile.ascii()))
     412        goto error;
     413    if (myth_system(convertfmt.arg(pgmfile).arg(jpgfile)))
     414        goto error;
     415    tfile.setName(pgmfile);
     416    if (!tfile.remove())
     417    {
     418        VERBOSE(VB_COMMFLAG, QString("TemplateFinder.analyzeFrameDebug"
     419                    " error removing %1 (%2)")
     420                .arg(tfile.name()).arg(strerror(errno)));
     421        goto error;
     422    }
     423
     424    pgmfile = base + "-cropped.pgm";
     425    jpgfile = base + "-cropped.jpg";
     426    if (pgm_write(cropped->data[0], cropwidth, cropheight, pgmfile.ascii()))
     427        goto error;
     428    if (myth_system(convertfmt.arg(pgmfile).arg(jpgfile)))
     429        goto error;
     430    tfile.setName(pgmfile);
     431    if (!tfile.remove())
     432    {
     433        VERBOSE(VB_COMMFLAG, QString("TemplateFinder.analyzeFrameDebug"
     434                    " error removing %1 (%2)")
     435                .arg(tfile.name()).arg(strerror(errno)));
     436        goto error;
     437    }
     438
     439    pgmfile = base + "-edges.pgm";
     440    jpgfile = base + "-edges.jpg";
     441    if (pgm_write(edges->data[0], cropwidth, cropheight, pgmfile.ascii()))
     442        goto error;
     443    if (myth_system(convertfmt.arg(pgmfile).arg(jpgfile)))
     444        goto error;
     445    tfile.setName(pgmfile);
     446    if (!tfile.remove())
     447    {
     448        VERBOSE(VB_COMMFLAG, QString("TemplateFinder.analyzeFrameDebug"
     449                    " error removing %1 (%2)")
     450                .arg(tfile.name()).arg(strerror(errno)));
     451        goto error;
     452    }
     453
     454    return 0;
     455
     456error:
     457    return -1;
     458}
     459
     460};  /* namespace */
     461
     462TemplateFinder::TemplateFinder(PGMConverter *pgmc, BorderDetector *bd,
     463        EdgeDetector *ed, NuppelVideoPlayer *nvp, int chanid,
     464        const QDateTime &recstartts_in, QString debugdir)
     465    : FrameAnalyzer()
     466    , pgmConverter(pgmc)
     467    , borderDetector(bd)
     468    , edgeDetector(ed)
     469    , nextFrame(0)
     470    , width(-1)
     471    , height(-1)
     472    , scores(NULL)
     473    , mincontentrow(INT_MAX)
     474    , maxcontentrow(INT_MIN)
     475    , mincontentcol(INT_MAX)
     476    , maxcontentcol(INT_MIN)
     477    , tmplrow(-1)
     478    , tmplcol(-1)
     479    , tmplwidth(-1)
     480    , tmplheight(-1)
     481    , tmpldone(false)
     482    , cwidth(-1)
     483    , cheight(-1)
     484    , chanid(chanid)
     485    , recstartts(recstartts_in)
     486    , debugLevel(0)
     487    , debugdir(debugdir)
     488{
     489    /*
     490     * TUNABLE:
     491     *
     492     * The number of frames desired for sampling to build the template.
     493     *
     494     * Higher values should yield a more accurate template, but requires more
     495     * time.
     496     */
     497    unsigned int        samplesNeeded = 300;
     498
     499    /*
     500     * TUNABLE:
     501     *
     502     * The leading amount of time (in seconds) to sample frames for building up
     503     * the possible template, and the interval between frames for analysis.
     504     * This affects how soon flagging can start after a recording has begun
     505     * (a.k.a. "real-time flagging").
     506     */
     507    sampleTime = 20 * 60;   /* Sample first <n> minutes. */
     508
     509    const float         fps = nvp->GetFrameRate();
     510
     511    if (samplesNeeded > UINT_MAX)
     512    {
     513        /* Max value of "scores" data type */
     514        samplesNeeded = UINT_MAX;
     515    }
     516
     517    frameInterval = (int)roundf(sampleTime * fps / samplesNeeded);
     518    endFrame = 0 + frameInterval * samplesNeeded - 1;
     519
     520    VERBOSE(VB_COMMFLAG, QString("TemplateFinder: sampleTime=%1s"
     521                ", samplesNeeded=%2")
     522            .arg(sampleTime).arg(samplesNeeded));
     523
     524    memset(&cropped, 0, sizeof(cropped));
     525    memset(&tmpl, 0, sizeof(tmpl));
     526
     527    /*
     528     * debugLevel:
     529     *      0: no debugging
     530     *      1: extra verbosity, dump edgecounts into debugdir [1 file]
     531     *      2: dump frames into debugdir [O(nframes)]
     532     */
     533    debugLevel = gContext->GetNumSetting("TemplateFinderDebugLevel", 0);
     534    if (debugLevel > 0)
     535    {
     536        QDir qdir(debugdir);
     537        if (qdir.exists())
     538        {
     539            VERBOSE(VB_COMMFLAG, QString("TemplateFinder debugLevel %1"
     540                        " using debug directory \"%2\"")
     541                    .arg(debugLevel).arg(debugdir));
     542        }
     543        else
     544        {
     545            if (qdir.mkdir(debugdir))
     546            {
     547                VERBOSE(VB_COMMFLAG, QString("TemplateFinder debugLevel %1"
     548                            " created debug directory \"%1\"")
     549                        .arg(debugLevel).arg(debugdir));
     550            }
     551            else
     552            {
     553                VERBOSE(VB_COMMFLAG, QString("TemplateFinder debugLevel %1"
     554                            " failed to create \"%2\": %3")
     555                        .arg(debugLevel).arg(debugdir).arg(strerror(errno)));
     556            }
     557        }
     558    }
     559}
     560
     561TemplateFinder::~TemplateFinder(void)
     562{
     563    if (scores)
     564        delete []scores;
     565    avpicture_free(&tmpl);
     566    avpicture_free(&cropped);
     567}
     568
     569int
     570TemplateFinder::extraBuffer(int preroll) const
     571{
     572    return max(0, preroll) + sampleTime;
     573}
     574
     575enum FrameAnalyzer::analyzeFrameResult
     576TemplateFinder::nuppelVideoPlayerInited(NuppelVideoPlayer *nvp)
     577{
     578    /*
     579     * Only detect edges in portions of the frame where we expect to find
     580     * a template. This serves two purposes:
     581     *
     582     *  - Speed: reduce search space.
     583     *  - Correctness (insofar as the assumption of template location is
     584     *    correct): don't "pollute" the set of candidate template edges with
     585     *    the "content" edges in the non-template portions of the frame.
     586     */
     587    MSqlQuery   query(MSqlQuery::InitCon());
     588
     589    width = nvp->GetVideoWidth();
     590    height = nvp->GetVideoHeight();
     591
     592    query.prepare("SELECT basename"
     593            ", template"
     594            ", template_row"
     595            ", template_col"
     596            ", template_width"
     597            ", template_height"
     598            " FROM recorded"
     599            " WHERE chanid = :CHANID"
     600            "   AND starttime = :STARTTIME"
     601            ";");
     602    query.bindValue(":CHANID", chanid);
     603    query.bindValue(":STARTTIME", recstartts);
     604    query.exec();
     605    if (!query.isActive())
     606    {
     607        MythContext::DBError("Error in TemplateFinder::nuppelVideoPlayerInited",
     608                query);
     609        return ANALYZE_FATAL;
     610    }
     611
     612    if (query.size() <= 0 || !query.next())
     613    {
     614        VERBOSE(VB_COMMFLAG, "Error in SQL query");
     615        return ANALYZE_FATAL;
     616    }
     617
     618    QString basename = query.value(0).toString();
     619    tmplfile = query.value(1).toString();
     620    tmplrow = query.value(2).toInt();
     621    tmplcol = query.value(3).toInt();
     622    tmplwidth = query.value(4).toInt();
     623    tmplheight = query.value(5).toInt();
     624    if (tmplfile.isEmpty())
     625    {
     626        QFileInfo fileinfo(basename);
     627        tmplfile = gContext->GetSetting("RecordFilePrefix") + "/" +
     628            fileinfo.baseName(TRUE) + "-template.pgm";
     629    }
     630    else
     631    {
     632        tmplfile = gContext->GetSetting("RecordFilePrefix") + "/" +
     633            tmplfile;
     634    }
     635
     636    if (QFile::exists(tmplfile))
     637    {
     638        if (avpicture_alloc(&tmpl, PIX_FMT_GRAY8, tmplwidth, tmplheight))
     639        {
     640            VERBOSE(VB_COMMFLAG, QString(
     641                        "TemplateFinder::nuppelVideoPlayerInited "
     642                        "avpicture_alloc tmpl (%1x%2) failed")
     643                    .arg(tmplwidth).arg(tmplheight));
     644            return ANALYZE_FATAL;
     645        }
     646
     647        if (pgm_read(tmpl.data[0], tmplwidth, tmplheight, tmplfile.ascii()))
     648            avpicture_free(&tmpl);
     649        else
     650            tmpldone = true;
     651    }
     652
     653    if (pgmConverter->nuppelVideoPlayerInited(nvp))
     654        goto free_tmpl;
     655
     656    if (tmpldone)
     657    {
     658        VERBOSE(VB_COMMFLAG, QString("TemplateFinder::nuppelVideoPlayerInited"
     659                    " %1x%2@(%3,%4) of %5x%6 (%7)")
     660                .arg(tmplwidth).arg(tmplheight)
     661                .arg(tmplcol).arg(tmplrow)
     662                .arg(width).arg(height).arg(tmplfile));
     663        return ANALYZE_FINISHED;
     664    }
     665
     666    VERBOSE(VB_COMMFLAG, QString("TemplateFinder::nuppelVideoPlayerInited"
     667                " framesize %1x%2")
     668            .arg(width).arg(height));
     669    scores = new unsigned int[width * height];
     670
     671    return ANALYZE_OK;
     672
     673free_tmpl:
     674    avpicture_free(&tmpl);
     675    return ANALYZE_FATAL;
     676}
     677
     678int
     679TemplateFinder::resetBuffers(int newwidth, int newheight)
     680{
     681    if (cwidth == newwidth && cheight == newheight)
     682        return 0;
     683
     684    avpicture_free(&cropped);
     685
     686    if (avpicture_alloc(&cropped, PIX_FMT_GRAY8, newwidth, newheight))
     687    {
     688        VERBOSE(VB_COMMFLAG, QString(
     689                    "TemplateFinder::resetBuffers "
     690                    "avpicture_alloc cropped (%1x%2) failed")
     691                .arg(newwidth).arg(newheight));
     692        return -1;
     693    }
     694
     695    cwidth = newwidth;
     696    cheight = newheight;
     697    return 0;
     698}
     699
     700enum FrameAnalyzer::analyzeFrameResult
     701TemplateFinder::analyzeFrame(const VideoFrame *frame, long long frameno,
     702        long long *pNextFrame)
     703{
     704    /*
     705     * TUNABLE:
     706     *
     707     * When looking for edges in frames, select some percentile of
     708     * squared-gradient magnitudes (intensities) as candidate edges. (This
     709     * number conventionally should not go any lower than the 95th percentile;
     710     * see edge_mark.)
     711     *
     712     * Higher values result in fewer edges; faint logos might not be picked up.
     713     * Lower values result in more edges; non-logo edges might be picked up.
     714     *
     715     * The TemplateFinder accumulates all its state in the "scores" array to
     716     * be processed later by TemplateFinder::finished.
     717     */
     718    const int           FRAMESGMPCTILE = 97;
     719
     720    const AVPicture     *pgm, *edges;
     721    int                 pgmwidth, pgmheight;
     722    int                 croprow, cropcol, cropwidth, cropheight;
     723
     724    if (frameno < nextFrame)
     725    {
     726        *pNextFrame = nextFrame;
     727        return ANALYZE_SKIP;
     728    }
     729
     730    nextFrame = frameno + frameInterval;
     731    *pNextFrame = min(endFrame, nextFrame);
     732
     733    if (frameno >= frameInterval && frameno * 10 / endFrame !=
     734            (frameno - frameInterval) * 10 / endFrame || nextFrame > endFrame)
     735    {
     736        /* Log something every 10%. */
     737        VERBOSE(VB_COMMFLAG, QString(
     738                    "TemplateFinder::analyzeFrame %1 of %2 (%3%)")
     739                .arg(frameno).arg(endFrame).arg(frameno * 100 / endFrame));
     740    }
     741
     742    if (!(pgm = pgmConverter->getImage(frame, frameno, &pgmwidth, &pgmheight)))
     743        goto error;
     744
     745    if (!borderDetector->getDimensions(pgm, pgmheight, frameno,
     746                &croprow, &cropcol, &cropwidth, &cropheight))
     747    {
     748        /* Not a blank frame. */
     749
     750        if (croprow < mincontentrow)
     751            mincontentrow = croprow;
     752        if (croprow + cropheight - 1 > maxcontentrow)
     753            maxcontentrow = croprow + cropheight - 1;
     754
     755        if (cropcol < mincontentcol)
     756            mincontentcol = cropcol;
     757        if (cropcol + cropwidth - 1 > maxcontentcol)
     758            maxcontentcol = cropcol + cropwidth - 1;
     759
     760        if (resetBuffers(cropwidth, cropheight))
     761            goto error;
     762
     763        if (pgm_crop(&cropped, pgm, pgmheight, croprow, cropcol,
     764                    cropwidth, cropheight))
     765            goto error;
     766
     767        if (!(edges = edgeDetector->detectEdges(&cropped, cropheight,
     768                        FRAMESGMPCTILE)))
     769            goto error;
     770
     771        if (pgm_scorepixels(scores, pgmwidth, croprow, cropcol,
     772                    edges, cropheight))
     773            goto error;
     774
     775        if (debugLevel >= 2)
     776        {
     777            if (analyzeFrameDebug(frameno, pgm, pgmheight, &cropped, edges,
     778                        cropheight, croprow, cropcol, debugdir))
     779                goto error;
     780        }
     781    }
     782
     783    if (nextFrame > endFrame)
     784        return ANALYZE_FINISHED;
     785
     786    return ANALYZE_OK;
     787
     788error:
     789    VERBOSE(VB_COMMFLAG,
     790            QString("TemplateFinder::analyzeFrame error at frame %1")
     791            .arg(frameno));
     792
     793    if (nextFrame > endFrame)
     794        return ANALYZE_FINISHED;
     795
     796    return ANALYZE_ERROR;
     797}
     798
     799int
     800TemplateFinder::finished(void)
     801{
     802    if (!tmpldone)
     803    {
     804        if (template_alloc(scores, width, height,
     805                    mincontentrow, maxcontentrow, mincontentcol, maxcontentcol,
     806                    &tmpl, &tmplrow, &tmplcol, &tmplwidth, &tmplheight,
     807                    debugLevel, debugdir))
     808            return -1;
     809
     810        if (pgm_write(tmpl.data[0], tmplwidth, tmplheight, tmplfile.ascii()))
     811            goto free_tmpl;
     812
     813        VERBOSE(VB_COMMFLAG, QString("Template %1x%2@(%3,%4) saved to %5")
     814                .arg(tmplwidth).arg(tmplheight).arg(tmplcol).arg(tmplrow)
     815                .arg(tmplfile));
     816
     817        MSqlQuery query(MSqlQuery::InitCon());
     818        query.prepare("UPDATE recorded"
     819                " SET template = :TEMPLATE"
     820                ", template_row = :TEMPLATE_ROW"
     821                ", template_col = :TEMPLATE_COL"
     822                ", template_width = :TEMPLATE_WIDTH"
     823                ", template_height = :TEMPLATE_HEIGHT"
     824                " WHERE chanid = :CHANID"
     825                "   AND starttime = :STARTTIME"
     826                ";");
     827        query.bindValue(":TEMPLATE", QFileInfo(tmplfile).fileName());
     828        query.bindValue(":TEMPLATE_ROW", tmplrow);
     829        query.bindValue(":TEMPLATE_COL", tmplcol);
     830        query.bindValue(":TEMPLATE_WIDTH", tmplwidth);
     831        query.bindValue(":TEMPLATE_HEIGHT", tmplheight);
     832        query.bindValue(":CHANID", chanid);
     833        query.bindValue(":STARTTIME", recstartts);
     834        query.exec();
     835        if (!query.isActive())
     836        {
     837            MythContext::DBError("Error in TemplateFinder::analyzeFrame",
     838                    query);
     839            goto free_tmpl;
     840        }
     841
     842        tmpldone = true;
     843    }
     844
     845    return 0;
     846
     847free_tmpl:
     848    avpicture_free(&tmpl);
     849    return -1;
     850}
     851
     852const struct AVPicture *
     853TemplateFinder::getTemplate(int *prow, int *pcol, int *pwidth, int *pheight)
     854    const
     855{
     856    if (tmpldone)
     857    {
     858        *prow = tmplrow;
     859        *pcol = tmplcol;
     860        *pwidth = tmplwidth;
     861        *pheight = tmplheight;
     862        return &tmpl;
     863    }
     864    return NULL;
     865}
     866
     867/* vim: set expandtab tabstop=4 shiftwidth=4: */
  • programs/mythcommflag/EdgeDetector.h

     
     1/*
     2 * EdgeDetector
     3 *
     4 * Declare routines that are likely to be useful for any edge-detection
     5 * implementation.
     6 */
     7
     8#ifndef __EDGEDETECTOR_H__
     9#define __EDGEDETECTOR_H__
     10
     11typedef struct AVPicture AVPicture;
     12
     13namespace edgeDetector {
     14
     15unsigned int *sgm_init(unsigned int *sgm, const AVPicture *src, int srcheight);
     16int edge_mark_uniform(AVPicture *dst, int dstheight, int extramargin,
     17        const unsigned int *sgm, unsigned int *sgmsorted,
     18        int percentile);
     19
     20};  /* namespace */
     21
     22class EdgeDetector
     23{
     24public:
     25    virtual ~EdgeDetector(void);
     26
     27    /* Detect edges in "pgm" image. */
     28    virtual const AVPicture *detectEdges(const AVPicture *pgm, int pgmheight,
     29            int percentile) = 0;
     30};
     31
     32#endif  /* !__EDGEDETECTOR_H__ */
     33
     34/* vim: set expandtab tabstop=4 shiftwidth=4: */
  • programs/mythcommflag/FrameAnalyzer.h

     
     1/*
     2 * FrameAnalyzer
     3 *
     4 * Provide a generic interface for plugging in frame analysis algorithms.
     5 */
     6
     7#ifndef __FRAMEANALYZER_H__
     8#define __FRAMEANALYZER_H__
     9
     10/* Base class for commercial flagging frame analyzers. */
     11
     12typedef struct VideoFrame_ VideoFrame;
     13class NuppelVideoPlayer;
     14
     15class FrameAnalyzer
     16{
     17public:
     18    virtual ~FrameAnalyzer(void) { }
     19
     20    /* return extra buffer time (seconds) needed for "real-time" flagging. */
     21    virtual int extraBuffer(int preroll) const {
     22        (void)preroll;
     23        return 0;
     24    }
     25
     26    /* Analyze a frame. */
     27    enum analyzeFrameResult {
     28        ANALYZE_OK,         /* Analysis OK */
     29        ANALYZE_SKIP,       /* Skipped analysis of frame for some reason */
     30        ANALYZE_FINISHED,   /* Analysis complete, don't need more frames. */
     31        ANALYZE_ERROR,      /* Recoverable error */
     32        ANALYZE_FATAL,      /* Don't use this analyzer anymore. */
     33    };
     34
     35    virtual enum analyzeFrameResult nuppelVideoPlayerInited(
     36            NuppelVideoPlayer *nvp) {
     37        (void)nvp;
     38        return ANALYZE_OK;
     39    };
     40
     41    /*
     42     * Populate *pNextFrame with the next frame number desired by this
     43     * analyzer.
     44     */
     45    static const long long ANYFRAME = LONG_LONG_MAX;
     46    static const long long NEXTFRAME = -1;
     47    virtual enum analyzeFrameResult analyzeFrame(const VideoFrame *frame,
     48            long long frameno, long long *pNextFrame) = 0;
     49
     50    virtual int finished(void) { return 0; }
     51
     52    virtual bool isContent(long long frameno) const {
     53        (void)frameno;
     54        return false;
     55    }
     56};
     57
     58#endif  /* !__FRAMEANALYZER_H__ */
     59
     60/* vim: set expandtab tabstop=4 shiftwidth=4: */
  • programs/mythcommflag/mythcommflag.pro

     
    1010QMAKE_CLEAN += $(TARGET)
    1111
    1212# Input
    13 SOURCES += main.cpp ClassicCommDetector.cpp CommDetectorFactory.cpp CommDetectorBase.cpp
    14 HEADERS += CommDetectorBase.h ClassicCommDetector.h SlotRelayer.h CustomEventRelayer.h CommDetectorFactory.h
     13
     14SOURCES += main.cpp CommDetectorFactory.cpp CommDetectorBase.cpp
     15SOURCES += ClassicCommDetector.cpp
     16SOURCES += CommDetector2.cpp
     17SOURCES += pgm.cpp
     18SOURCES += EdgeDetector.cpp CannyEdgeDetector.cpp
     19SOURCES += PGMConverter.cpp BorderDetector.cpp
     20SOURCES += TemplateFinder.cpp TemplateMatcher.cpp
     21
     22HEADERS += SlotRelayer.h CustomEventRelayer.h
     23HEADERS += CommDetectorFactory.h CommDetectorBase.h
     24HEADERS += ClassicCommDetector.h
     25HEADERS += CommDetector2.h
     26HEADERS += pgm.h
     27HEADERS += EdgeDetector.h CannyEdgeDetector.h
     28HEADERS += FrameAnalyzer.h
     29HEADERS += PGMConverter.h BorderDetector.h
     30HEADERS += TemplateFinder.h TemplateMatcher.h
  • programs/mythcommflag/BorderDetector.cpp

     
     1#include <limits.h>
     2
     3#include "avcodec.h"        /* AVPicture */
     4#include "mythcontext.h"    /* gContext */
     5
     6#include "BorderDetector.h"
     7
     8BorderDetector::BorderDetector(void)
     9    : frameno(-1)
     10    , debugLevel(0)
     11{
     12    debugLevel = gContext->GetNumSetting("BorderDetectorDebugLevel", 0);
     13}
     14
     15BorderDetector::~BorderDetector(void)
     16{
     17}
     18
     19int
     20BorderDetector::getDimensions(const AVPicture *pgm, int pgmheight,
     21        long long _frameno, int *prow, int *pcol, int *pwidth, int *pheight)
     22{
     23    /*
     24     * TUNABLE:
     25     *
     26     * The maximum range of values allowed for letterboxing/pillarboxing bars.
     27     * Usually the bars are black (0x00), but sometimes they are grey (0x71).
     28     * Sometimes the letterboxing and pillarboxing (when one is embedded inside
     29     * the other) are different colors.
     30     *
     31     * Higher values mean more tolerance for noise. This means that
     32     * letterboxing/pillarboxing might be over-cropped, which might cause faint
     33     * template edges to also be cropped out of consideration.
     34     *
     35     * Lower values mean less tolerance for noise, which means that
     36     * letterboxing/pillarboxing might not be detected. If this happens, the
     37     * letterboxing/pillarboxing edges will make their way into the
     38     * edge-detection phase where they are likely to take edge pixels away from
     39     * possible template edges.
     40     */
     41    static const unsigned char  MAXRANGE = 32;
     42
     43    /*
     44     * TUNABLE:
     45     *
     46     * The maximum number of consecutive rows or columns with too many outlier
     47     * points that may be scanned before declaring the existence of a border.
     48     *
     49     * Higher values mean more tolerance for noise, but content might be
     50     * cropped.
     51     *
     52     * Lower values mean less tolerance for noise, but noise might interfere
     53     * with letterbox/pillarbox detection.
     54     */
     55    static const int        MAXLINES = 2;
     56
     57    const int               pgmwidth = pgm->linesize[0];
     58
     59    /*
     60     * TUNABLE:
     61     *
     62     * The maximum number of outlier points in a single row or column with grey
     63     * values outside of MAXRANGE before declaring the existence of a border.
     64     *
     65     * Higher values mean more tolerance for noise, but content might be
     66     * cropped.
     67     *
     68     * Lower values mean less tolerance for noise, but noise might interfere
     69     * with letterbox/pillarbox detection.
     70     */
     71    const int               MAXOUTLIERS = pgmwidth * 25 / 1000;
     72
     73    /*
     74     * TUNABLE:
     75     *
     76     * Margins to avoid noise at the extreme edges of the signal (VBI?).
     77     */
     78    const int               VERTMARGIN = max(2, pgmheight * 1 / 60);
     79    const int               HORIZMARGIN = max(2, pgmwidth * 125 / 10000);
     80
     81    /*
     82     * TUNABLE:
     83     *
     84     * Slop to accommodate any jagged letterboxing/pillarboxing edges.
     85     */
     86    const int               VERTSLOP = max(MAXLINES, pgmheight * 1 / 120);
     87    const int               HORIZSLOP = max(MAXLINES, pgmwidth * 125 / 20000);
     88
     89    unsigned char           minval, maxval, val;
     90    int                     rr, cc, minrow, maxrow, mincol, maxcol;
     91    int                     newrow, newcol, newwidth, newheight;
     92    bool                    top, bottom, left, right;
     93    int                     range, outliers, lines;
     94
     95    if (_frameno != UNCACHED && _frameno == frameno)
     96        goto done;
     97
     98    top = false;
     99    bottom = false;
     100    left = false;
     101    right = false;
     102
     103    minrow = VERTMARGIN;
     104    maxrow = pgmheight - VERTMARGIN - 1;
     105
     106    mincol = HORIZMARGIN;
     107    maxcol = pgmwidth - HORIZMARGIN - 1;
     108
     109    for (;;)
     110    {
     111        /* Find left edge. */
     112        minval = UCHAR_MAX;
     113        maxval = 0;
     114        lines = 0;
     115        for (cc = mincol; cc <= maxcol; cc++)
     116        {
     117            outliers = 0;
     118            for (rr = minrow; rr <= maxrow; rr++)
     119            {
     120                val = pgm->data[0][rr * pgmwidth + cc];
     121                range = max(maxval, val) - min(minval, val);
     122                if (range > MAXRANGE)
     123                {
     124                    if (outliers++ < MAXOUTLIERS)
     125                        continue;
     126                    if (lines++ < MAXLINES)
     127                        break;
     128                    if (cc != mincol)
     129                        left = true;
     130                    goto found_left;
     131                }
     132                if (val < minval)
     133                    minval = val;
     134                if (val > maxval)
     135                    maxval = val;
     136            }
     137        }
     138found_left:
     139        newcol = cc + HORIZSLOP;
     140        if (newcol > maxcol)
     141            goto blank_frame;
     142
     143        /* Find right edge (keep same minval/maxval as left edge). */
     144        lines = 0;
     145        for (cc = maxcol; cc > newcol; cc--)
     146        {
     147            outliers = 0;
     148            for (rr = minrow; rr <= maxrow; rr++)
     149            {
     150                val = pgm->data[0][rr * pgmwidth + cc];
     151                range = max(maxval, val) - min(minval, val);
     152                if (range > MAXRANGE)
     153                {
     154                    if (outliers++ < MAXOUTLIERS)
     155                        continue;
     156                    if (lines++ < MAXLINES)
     157                        break;
     158                    if (cc != maxcol)
     159                        right = true;
     160                    goto found_right;
     161                }
     162                if (val < minval)
     163                    minval = val;
     164                if (val > maxval)
     165                    maxval = val;
     166            }
     167        }
     168found_right:
     169        newwidth = cc - HORIZSLOP - newcol + 1;
     170
     171        if (newwidth <= 0)
     172            goto blank_frame;
     173
     174        if (top || bottom)
     175            break;  /* No need to repeat letterboxing check. */
     176
     177        /* Find top edge. */
     178        minval = UCHAR_MAX;
     179        maxval = 0;
     180        lines = 0;
     181        for (rr = minrow; rr <= maxrow; rr++)
     182        {
     183            outliers = 0;
     184            for (cc = newcol; cc < newcol + newwidth; cc++)
     185            {
     186                val = pgm->data[0][rr * pgmwidth + cc];
     187                range = max(maxval, val) - min(minval, val);
     188                if (range > MAXRANGE)
     189                {
     190                    if (outliers++ < MAXOUTLIERS)
     191                        continue;
     192                    if (lines++ < MAXLINES)
     193                        break;
     194                    if (rr != minrow)
     195                        top = true;
     196                    goto found_top;
     197                }
     198                if (val < minval)
     199                    minval = val;
     200                if (val > maxval)
     201                    maxval = val;
     202            }
     203        }
     204found_top:
     205        newrow = rr + VERTSLOP;
     206
     207        /* Find bottom edge (keep same minval/maxval as top edge). */
     208        lines = 0;
     209        for (rr = maxrow; rr > newrow; rr--)
     210        {
     211            outliers = 0;
     212            for (cc = newcol; cc < newcol + newwidth; cc++)
     213            {
     214                val = pgm->data[0][rr * pgmwidth + cc];
     215                range = max(maxval, val) - min(minval, val);
     216                if (range > MAXRANGE)
     217                {
     218                    if (outliers++ < MAXOUTLIERS)
     219                        continue;
     220                    if (lines++ < MAXLINES)
     221                        break;
     222                    if (rr != maxrow)
     223                        bottom = true;
     224                    goto found_bottom;
     225                }
     226                if (val < minval)
     227                    minval = val;
     228                if (val > maxval)
     229                    maxval = val;
     230            }
     231        }
     232found_bottom:
     233        newheight = rr - VERTSLOP - newrow + 1;
     234
     235        if (newheight <= 0)
     236            goto blank_frame;
     237
     238        if (left || right)
     239            break;  /* No need to repeat pillarboxing check. */
     240
     241        if (top || bottom)
     242        {
     243            /*
     244             * Letterboxing was found; repeat loop to look for embedded
     245             * pillarboxing.
     246             */
     247            minrow = newrow;
     248            maxrow = newrow + newheight - 1;
     249            continue;
     250        }
     251
     252        /* No pillarboxing or letterboxing. */
     253        break;
     254    }
     255
     256    if (debugLevel >= 1)
     257    {
     258        if (row != newrow || col != newcol ||
     259            width != newwidth || height != newheight)
     260        {
     261            VERBOSE(VB_COMMFLAG, QString("Frame %1: %2x%3@(%4,%5)")
     262                    .arg(_frameno, 5)
     263                    .arg(newwidth).arg(newheight)
     264                    .arg(newcol).arg(newrow));
     265        }
     266    }
     267
     268    frameno = _frameno;
     269    row = newrow;
     270    col = newcol;
     271    width = newwidth;
     272    height = newheight;
     273
     274done:
     275    *prow = row;
     276    *pcol = col;
     277    *pwidth = width;
     278    *pheight = height;
     279    return 0;
     280
     281blank_frame:
     282    if (debugLevel >= 1)
     283    {
     284        VERBOSE(VB_COMMFLAG, QString("Frame %1: %2x%3@(%4,%5): blank")
     285                .arg(_frameno, 5)
     286                .arg(maxcol - mincol + 1).arg(maxrow - minrow + 1)
     287                .arg(mincol).arg(minrow));
     288    }
     289    return -1;  /* Blank frame. */
     290}
     291
     292/* vim: set expandtab tabstop=4 shiftwidth=4: */
  • programs/mythcommflag/CommDetector2.cpp

     
     1#include <math.h>
     2#include <qfileinfo.h>
     3
     4#include "NuppelVideoPlayer.h"
     5
     6#include "CommDetector.h"
     7#include "CommDetector2.h"
     8#include "CannyEdgeDetector.h"
     9#include "FrameAnalyzer.h"
     10#include "PGMConverter.h"
     11#include "BorderDetector.h"
     12#include "TemplateFinder.h"
     13#include "TemplateMatcher.h"
     14
     15namespace {
     16
     17bool stopForBreath(bool isrecording, long long frameno)
     18{
     19    return (isrecording && (frameno % 100) == 0) || (frameno % 500) == 0;
     20}
     21
     22bool needToReportState(bool showprogress, bool isrecording, long long frameno)
     23{
     24    return ((showprogress || isrecording) && (frameno % 100) == 0) ||
     25        (frameno % 500) == 0;
     26}
     27
     28void waitForBuffer(const struct timeval *framestart, int minlag, int flaglag,
     29        float fps, bool fullspeed)
     30{
     31    long usperframe = (long)(1000000.0 / fps);
     32    struct timeval now, elapsed;
     33    long sleepus;
     34
     35    (void)gettimeofday(&now, NULL);
     36    timersub(&now, framestart, &elapsed);
     37
     38    // Sleep for one frame's worth of time.
     39    sleepus = usperframe - elapsed.tv_sec * 1000000 - elapsed.tv_usec;
     40    if (sleepus <= 0)
     41        return;
     42
     43    if (flaglag > minlag)
     44    {
     45        // Try to catch up; reduce sleep time.
     46        if (fullspeed)
     47            return;
     48        sleepus /= 4;
     49    }
     50    else if (flaglag < minlag)
     51    {
     52        // Slow down; increase sleep time
     53        sleepus = sleepus * 3 / 2;
     54    }
     55    usleep(sleepus);
     56}
     57
     58int nuppelVideoPlayerInited(QPtrList<FrameAnalyzer> *pass,
     59        QPtrList<FrameAnalyzer> *finishedAnalyzers,
     60        QPtrList<FrameAnalyzer> *deadAnalyzers,
     61        NuppelVideoPlayer *nvp)
     62{
     63    FrameAnalyzer                       *fa;
     64    FrameAnalyzer::analyzeFrameResult   ares;
     65
     66    for (QPtrListIterator<FrameAnalyzer> iifa(*pass);
     67            (fa = iifa.current()); ++iifa)
     68    {
     69        switch (ares = fa->nuppelVideoPlayerInited(nvp)) {
     70
     71        case FrameAnalyzer::ANALYZE_OK:
     72            break;
     73
     74        case FrameAnalyzer::ANALYZE_FINISHED:
     75            pass->remove(fa);
     76            finishedAnalyzers->append(fa);
     77            break;
     78
     79        case FrameAnalyzer::ANALYZE_FATAL:
     80            pass->remove(fa);
     81            deadAnalyzers->append(fa);
     82            break;
     83
     84        default:
     85            return -1;
     86        }
     87    }
     88
     89    return 0;
     90}
     91
     92long long processFrame(QPtrList<FrameAnalyzer> *pass,
     93        QPtrList<FrameAnalyzer> *finishedAnalyzers,
     94        QPtrList<FrameAnalyzer> *deadAnalyzers,
     95        const VideoFrame *frame, long long frameno)
     96{
     97    FrameAnalyzer                       *fa;
     98    FrameAnalyzer::analyzeFrameResult   ares;
     99    long long                           nextFrame, minNextFrame;
     100
     101    minNextFrame = FrameAnalyzer::ANYFRAME;
     102    for (QPtrListIterator<FrameAnalyzer> iifa(*pass);
     103            (fa = iifa.current()); ++iifa)
     104    {
     105        switch (ares = fa->analyzeFrame(frame, frameno, &nextFrame)) {
     106
     107        default:
     108            if (minNextFrame > nextFrame)
     109                minNextFrame = nextFrame;
     110            break;
     111
     112        case FrameAnalyzer::ANALYZE_FINISHED:
     113            pass->remove(fa);
     114            finishedAnalyzers->append(fa);
     115            break;
     116
     117        case FrameAnalyzer::ANALYZE_FATAL:
     118            pass->remove(fa);
     119            deadAnalyzers->append(fa);
     120            break;
     121        }
     122    }
     123
     124    if (minNextFrame == FrameAnalyzer::ANYFRAME)
     125        minNextFrame = FrameAnalyzer::NEXTFRAME;
     126
     127    return minNextFrame;
     128}
     129
     130int passFinished(QPtrList<FrameAnalyzer> *pass)
     131{
     132    FrameAnalyzer       *fa;
     133
     134    for (QPtrListIterator<FrameAnalyzer> iifa(*pass);
     135            (fa = iifa.current()); ++iifa)
     136    {
     137        if (fa->finished())
     138            return -1;
     139    }
     140    return 0;
     141}
     142
     143bool isContent(QPtrList<FrameAnalyzer> *pass, long long frameno)
     144{
     145    /* Compute some function of all analyses on a given frame. */
     146    unsigned int    score;
     147    FrameAnalyzer   *fa;
     148
     149    score = 0;
     150    for (QPtrListIterator<FrameAnalyzer> iifa(*pass);
     151            (fa = iifa.current()); ++iifa)
     152    {
     153        if (fa->isContent(frameno))
     154            score++;
     155    }
     156    return score > 0;
     157}
     158
     159QString frameToTimestamp(long long frameno, float fps)
     160{
     161    int ms, ss, mm, hh;
     162
     163    ms = (int)roundf(frameno / fps * 1000);
     164
     165    ss = ms / 1000;
     166    ms %= 1000;
     167
     168    mm = ss / 60;
     169    ss %= 60;
     170
     171    hh = mm / 60;
     172    mm %= 60;
     173
     174    QString ts;
     175    return ts.sprintf("%d:%02d:%02d", hh, mm, ss);
     176}
     177
     178};  // namespace
     179
     180QString debugDirectory(int chanid, const QDateTime& recstartts)
     181{
     182    /*
     183     * Should deleting a recording also delete its associated debug directory?
     184     */
     185    MSqlQuery query(MSqlQuery::InitCon());
     186    query.prepare("SELECT basename"
     187            " FROM recorded"
     188            " WHERE chanid = :CHANID"
     189            "   AND starttime = :STARTTIME"
     190            ";");
     191    query.bindValue(":CHANID", chanid);
     192    query.bindValue(":STARTTIME", recstartts);
     193    query.exec();
     194    if (query.size() <= 0 || !query.next())
     195    {
     196        MythContext::DBError("Error in CommDetector2::CommDetector2",
     197                query);
     198        return "";
     199    }
     200
     201    QString recordfileprefix = gContext->GetSetting("RecordFilePrefix");
     202    QString basename = query.value(0).toString();
     203    QFileInfo baseinfo(recordfileprefix + "/" + basename);
     204    QString debugdir = recordfileprefix + "/" + baseinfo.baseName(TRUE) +
     205        "-debug";
     206
     207    return debugdir;
     208}
     209
     210CommDetector2::CommDetector2(enum SkipTypes commDetectMethod_in,
     211        bool showProgress_in, bool fullSpeed_in, NuppelVideoPlayer* nvp_in,
     212        int chanid, const QDateTime& startts_in, const QDateTime& endts_in,
     213        const QDateTime& recstartts_in, const QDateTime& recendts_in)
     214    : commDetectMethod((enum SkipTypes)(commDetectMethod_in & ~COMM_DETECT_2))
     215    , showProgress(showProgress_in)
     216    , fullSpeed(fullSpeed_in)
     217    , nvp(nvp_in)
     218    , startts(startts_in)
     219    , endts(endts_in)
     220    , recstartts(recstartts_in)
     221    , recendts(recendts_in)
     222    , isRecording(QDateTime::currentDateTime() < recendts)
     223    , debugdir("")
     224{
     225    QPtrList<FrameAnalyzer> *pass0, *pass1;
     226
     227    debugdir = debugDirectory(chanid, recstartts);
     228
     229    pass0 = NULL;
     230    pass1 = NULL;
     231
     232    /*
     233     * Logo Detection requires two passes. The first pass looks for static
     234     * areas of the screen to look for logos and generate a template. The
     235     * second pass matches the template against the frame and computes the
     236     * closeness of the match.
     237     */
     238    if ((commDetectMethod & COMM_DETECT_2_LOGO))
     239    {
     240        PGMConverter            *pgmConverter = NULL;
     241        BorderDetector          *borderDetector = NULL;
     242        CannyEdgeDetector       *cannyEdgeDetector = NULL;
     243        TemplateFinder          *logoFinder = NULL;
     244        TemplateMatcher         *logoMatcher = NULL;
     245
     246        if (!pass0)
     247            pass0 = new QPtrList<FrameAnalyzer>;
     248        if (!pass1)
     249            pass1 = new QPtrList<FrameAnalyzer>;
     250
     251        if (!pgmConverter)
     252            pgmConverter = new PGMConverter();
     253
     254        if (!borderDetector)
     255            borderDetector = new BorderDetector();
     256
     257        if (!cannyEdgeDetector)
     258            cannyEdgeDetector = new CannyEdgeDetector();
     259
     260        if (!logoFinder)
     261        {
     262            logoFinder = new TemplateFinder(pgmConverter, borderDetector,
     263                    cannyEdgeDetector, nvp, chanid, recstartts, debugdir);
     264            pass0->append(logoFinder);
     265        }
     266
     267        if (!logoMatcher)
     268        {
     269            logoMatcher = new TemplateMatcher(pgmConverter, cannyEdgeDetector,
     270                    logoFinder, debugdir);
     271            pass1->append(logoMatcher);
     272        }
     273    }
     274
     275    /* Aggregate them all together. */
     276    if (pass0)
     277        frameAnalyzers.append(pass0);
     278    if (pass1)
     279        frameAnalyzers.append(pass1);
     280}
     281
     282int CommDetector2::buildBuffer(int minbuffer)
     283{
     284    bool                    wereRecording = isRecording;
     285    int                     buffer = minbuffer;
     286    int                     maxExtra = 0;
     287    int                     preroll = recstartts.secsTo(startts);
     288    QPtrList<FrameAnalyzer> *pass;
     289
     290    for (QPtrListIterator<QPtrList<FrameAnalyzer> > iipass(frameAnalyzers);
     291            (pass = iipass.current()); ++iipass)
     292    {
     293        FrameAnalyzer   *fa;
     294
     295        if (pass->isEmpty())
     296            continue;
     297
     298        for (QPtrListIterator<FrameAnalyzer> iifa(*pass);
     299                (fa = iifa.current()); ++iifa)
     300        {
     301            maxExtra = max(maxExtra, fa->extraBuffer(preroll));
     302        }
     303
     304        buffer += maxExtra;
     305    }
     306
     307    emit statusUpdate("Building Detection Buffer");
     308
     309    int reclen;
     310    while (isRecording && (reclen = recstartts.secsTo(
     311        QDateTime::currentDateTime())) < buffer)
     312    {
     313        emit breathe();
     314        if (m_bStop)
     315            return -1;
     316        sleep(2);
     317    }
     318
     319    // Don't bother flagging short ~realtime recordings
     320    if (wereRecording && !isRecording && reclen < buffer)
     321        return -1;
     322
     323    return 0;
     324}
     325
     326void CommDetector2::reportState(int elapsed_sec, long long frameno,
     327        long long nframes, unsigned int passno, unsigned int npasses)
     328{
     329    float fps = elapsed_sec ? (float)frameno / elapsed_sec : 0;
     330
     331    /* Assume that 0-th pass is negligible in terms of computational cost. */
     332    int percentage = passno == 0 ? 0 :
     333        (passno - 1) * 100 / (npasses - 1) +
     334        min((long long)100, (frameno * 100 / nframes) / (npasses - 1));
     335
     336    if (showProgress)
     337    {
     338        if (nframes)
     339        {
     340            cerr << "\b\b\b\b\b\b\b\b\b\b\b"
     341                 << QString::number(percentage).rightJustify(3, ' ')
     342                 << "%/"
     343                 << QString::number((int)fps).rightJustify(3, ' ')
     344                 << "fps";
     345        }
     346        else
     347        {
     348            cerr << "\b\b\b\b\b\b\b\b\b\b\b\b\b"
     349                 << QString::number(frameno).rightJustify(6, ' ')
     350                 << "/"
     351                 << QString::number((int)fps).rightJustify(3, ' ')
     352                 << "fps";
     353        }
     354        cerr.flush();
     355    }
     356
     357    if (nframes)
     358    {
     359        emit statusUpdate(QObject::tr("%1% Completed @ %2 fps.")
     360                .arg(percentage).arg(fps));
     361    }
     362    else
     363    {
     364        emit statusUpdate(QObject::tr("%1 Frames Completed @ %2 fps.")
     365                .arg(frameno).arg(fps));
     366    }
     367}
     368
     369bool CommDetector2::go(void)
     370{
     371    int minlag = 7; // seconds
     372
     373    nvp->SetNullVideo();
     374
     375    if (buildBuffer(minlag) < 0)
     376        return false;
     377
     378    if (nvp->OpenFile() < 0)
     379        return false;
     380
     381    if (!nvp->InitVideo())
     382    {
     383        VERBOSE(VB_IMPORTANT,
     384                "NVP: Unable to initialize video for FlagCommercials.");
     385        return false;
     386    }
     387
     388    nvp->SetCaptionsEnabled(false);
     389
     390    QTime flagTime;
     391    flagTime.start();
     392   
     393    long long myTotalFrames = recendts < QDateTime::currentDateTime() ?
     394        nvp->GetTotalFrameCount() :
     395        (long long)(nvp->GetFrameRate() * recstartts.secsTo(recendts));
     396
     397    if (showProgress)
     398    {
     399        if (myTotalFrames)
     400            cerr << "  0%/      ";
     401        else
     402            cerr << "     0/      ";
     403        cerr.flush();
     404    }
     405
     406    emit breathe();
     407
     408    unsigned int passno = 0;
     409    unsigned int npasses = frameAnalyzers.count();
     410    long long nframes = nvp->GetTotalFrameCount();
     411    QPtrList<FrameAnalyzer> *pass;
     412    for (QPtrListIterator<QPtrList<FrameAnalyzer> > iipass(frameAnalyzers);
     413            (pass = iipass.current()); ++iipass, passno++)
     414    {
     415        QPtrList<FrameAnalyzer> finishedAnalyzers, deadAnalyzers;
     416        FrameAnalyzer           *fa;
     417
     418        VERBOSE(VB_COMMFLAG, QString(
     419                    "CommDetector2::go pass %1 of %2 (%3 frames)")
     420                .arg(passno + 1).arg(npasses)
     421                .arg(nvp->GetTotalFrameCount()));
     422
     423        if (nuppelVideoPlayerInited(pass, &finishedAnalyzers, &deadAnalyzers,
     424                    nvp))
     425            return false;
     426
     427        /*
     428         * Each pass starts at beginning. VideoFrame.frameNumber appears to be
     429         * a cumulative counter of total frames "played" rather than an actual
     430         * index into the file, so maintain the frame index in
     431         * currentFrameNumber.
     432         */
     433        nvp->DiscardVideoFrame(nvp->GetRawVideoFrame(0));
     434        long long nextFrame = -1;
     435        long long currentFrameNumber = 0;
     436
     437        while (currentFrameNumber < nframes && !pass->isEmpty() &&
     438                !nvp->GetEof())
     439        {
     440            struct timeval startTime;
     441
     442            if (isRecording)
     443                (void)gettimeofday(&startTime, NULL);
     444
     445            VideoFrame *currentFrame = nvp->GetRawVideoFrame(nextFrame);
     446
     447            if (stopForBreath(isRecording, currentFrameNumber))
     448            {
     449                emit breathe();
     450                if (m_bStop)
     451                {
     452                    nvp->DiscardVideoFrame(currentFrame);
     453                    return false;
     454                }
     455            }
     456
     457            while (m_bPaused)
     458            {
     459                emit breathe();
     460                sleep(1);
     461            }
     462
     463            // sleep a little so we don't use all cpu even if we're niced
     464            if (!fullSpeed && !isRecording)
     465                usleep(10000);  // 10ms
     466
     467            if (needToReportState(showProgress, isRecording,
     468                        currentFrameNumber))
     469            {
     470                reportState(flagTime.elapsed() / 1000, currentFrameNumber,
     471                        myTotalFrames, passno, npasses);
     472            }
     473
     474            nextFrame = processFrame(pass, &finishedAnalyzers, &deadAnalyzers,
     475                        currentFrame, currentFrameNumber);
     476
     477            if (isRecording)
     478            {
     479                waitForBuffer(&startTime, minlag,
     480                        recstartts.secsTo(QDateTime::currentDateTime()) -
     481                        flagTime.elapsed() / 1000, nvp->GetFrameRate(),
     482                        fullSpeed);
     483            }
     484
     485            nvp->DiscardVideoFrame(currentFrame);
     486
     487            if (nextFrame == FrameAnalyzer::NEXTFRAME)
     488                currentFrameNumber++;
     489            else
     490                currentFrameNumber = nextFrame;
     491        }
     492
     493        for (QPtrListIterator<FrameAnalyzer> iifa(finishedAnalyzers);
     494                (fa = iifa.current()); ++iifa)
     495        {
     496            finishedAnalyzers.remove(fa);
     497            pass->append(fa);
     498        }
     499
     500        if (passFinished(pass))
     501            return false;
     502    }
     503
     504    if (showProgress)
     505    {
     506        if (myTotalFrames)
     507            cerr << "\b\b\b\b\b\b      \b\b\b\b\b\b";
     508        else
     509            cerr << "\b\b\b\b\b\b\b\b\b\b\b\b\b             "
     510                    "\b\b\b\b\b\b\b\b\b\b\b\b\b";
     511        cerr.flush();
     512    }
     513
     514    return true;
     515}
     516
     517void CommDetector2::getCommercialBreakList(QMap<long long, int> &marks)
     518{
     519    /*
     520     * As a matter of record, the TemplateMatcher is tuned to yield false
     521     * positives (non-commercials marked as commercials); false negatives
     522     * (commercials marked as non-commercials) are expected to be rare.
     523     */
     524    const long long                 nframes = nvp->GetTotalFrameCount();
     525    const float                     fps = nvp->GetFrameRate();
     526    unsigned int                    passno;
     527    bool                            prevContent, thisContent;
     528    QPtrList<FrameAnalyzer>         *pass, *pass1;
     529    QMap<long long, int>::Iterator  iimark;
     530
     531    marks.clear();
     532
     533    /* XXX: Pass #1 (0-based) contains the image analyses. */
     534
     535    passno = 0;
     536    pass1 = NULL;
     537    for (QPtrListIterator<QPtrList<FrameAnalyzer> > iipass(frameAnalyzers);
     538            (pass = iipass.current()); ++iipass, passno++)
     539    {
     540        if (passno == 1)
     541        {
     542            pass1 = pass;
     543            break;
     544        }
     545    }
     546
     547    if (!pass1)
     548        return;
     549
     550    /* Create break list. */
     551
     552    if (!(prevContent = isContent(pass1, 0)))
     553        marks[0] = MARK_COMM_START;
     554    for (long long frameno = 1; frameno < nframes; frameno++)
     555    {
     556        if ((thisContent = isContent(pass1, frameno)) == prevContent)
     557            continue;
     558
     559        if (!thisContent)
     560        {
     561            marks[frameno] = MARK_COMM_START;
     562        }
     563        else
     564        {
     565            if ((iimark = marks.find(frameno - 1)) == marks.end())
     566                marks[frameno - 1] = MARK_COMM_END;
     567            else
     568                marks.remove(iimark);
     569        }
     570        prevContent = thisContent;
     571    }
     572    if (!prevContent)
     573        marks[nframes] = MARK_COMM_END;
     574
     575    /* Report results. */
     576
     577    for (iimark = marks.begin(); iimark != marks.end(); ++iimark)
     578    {
     579        long long   markstart, markend;
     580
     581        markstart = iimark.key();   /* MARK_COMM_BEGIN */
     582        ++iimark;                   /* MARK_COMM_END */
     583
     584        markend = iimark.key();
     585
     586        VERBOSE(VB_COMMFLAG, QString("Break: frame %1-%2 (%3, %4)")
     587                .arg(markstart, 6).arg(markend, 6)
     588                .arg(frameToTimestamp(markstart, fps))
     589                .arg(frameToTimestamp(markend - markstart + 1, fps)));
     590    }
     591}
     592
     593void CommDetector2::recordingFinished(long long totalFileSize)
     594{
     595    CommDetectorBase::recordingFinished(totalFileSize);
     596    isRecording = false;
     597}
     598
     599/* vim: set expandtab tabstop=4 shiftwidth=4: */
  • programs/mythcommflag/test.sh

     
     1#!/bin/sh -ex
     2
     3umask 022
     4ulimit -c unlimited
     5
     6if [ `whoami` = "myth" ]; then
     7        SUDO=""
     8else
     9        SUDO="sudo -H -u myth"
     10fi
     11TOPDIR=/media/myth/rtsai/commflag
     12RECORDPREFIX=/media/myth/recordedtv
     13
     14GDB=0
     15RESET=0
     16COMMFLAG=1
     17CLEAN=1
     18CLEANDB=1
     19while [ $# -gt 0 ]; do
     20        case "$1" in
     21        gdb) GDB=1 ;;
     22        reset) RESET=1 ;;
     23        cleandb)
     24                CLEAN=0
     25                COMMFLAG=0
     26                ;;
     27        *) break ;;
     28        esac
     29        shift
     30done
     31
     32cleandb()
     33{
     34        CHANID="$1"
     35        STARTTIME="$2"
     36
     37        mysql -umythtv -pmythtv mythconverg -e "delete from jobqueue where chanid = $CHANID and starttime = '$STARTTIME' and type = 2"
     38}
     39
     40clean()
     41{
     42        HAVE_TM=0
     43        if [ $RESET -eq 0 -a \
     44                        -f $RECORDPREFIX/$DEBUGDIR/TemplateMatcher.txt ]; then
     45                HAVE_TM=1
     46        fi
     47        if [ $HAVE_TM -gt 0 ]; then
     48                $SUDO mv $RECORDPREFIX/$DEBUGDIR/TemplateMatcher.txt \
     49                        $RECORDPREFIX/TemplateMatcher.txt
     50        fi
     51        $SUDO rm -fr $RECORDPREFIX/$DEBUGDIR
     52        if [ $HAVE_TM -gt 0 ]; then
     53                $SUDO mkdir $RECORDPREFIX/$DEBUGDIR
     54                $SUDO mv $RECORDPREFIX/TemplateMatcher.txt \
     55                        $RECORDPREFIX/$DEBUGDIR/TemplateMatcher.txt
     56        fi
     57}
     58
     59runflag()
     60{
     61        CHANID="$1"
     62        STARTTIME="$2"
     63
     64        TIMESTAMP=`echo $STARTTIME | tr -d 'T:-'`
     65        TEMPLATE="${CHANID}_${TIMESTAMP}-template.pgm"
     66        DEBUGDIR="${CHANID}_${TIMESTAMP}-debug"
     67
     68        ARGS="--quiet -c $CHANID -s '$STARTTIME' -v commflag --method 511"
     69
     70        cleandb $CHANID $STARTTIME
     71
     72        if [ $RESET -gt 0 ]; then
     73                $SUDO rm -f $RECORDPREFIX/$TEMPLATE
     74        fi
     75
     76        if [ $CLEAN -gt 0 ]; then
     77                clean $DEBUGDIR
     78        fi
     79
     80        if [ $COMMFLAG -eq 0 ]; then
     81                return
     82        fi
     83
     84        set +e
     85        if [ $GDB -gt 0 ]; then
     86                cat <<! > mythcommflag.gdb
     87file mythcommflag
     88b BorderDetector.cpp:181
     89cond 1 _frameno == 26280
     90r $ARGS
     91!
     92                $SUDO gdb -x mythcommflag.gdb
     93        else
     94                $SUDO ./mythcommflag $ARGS
     95        fi
     96        set -e
     97
     98        cleandb $CHANID $STARTTIME
     99
     100        finishup
     101
     102        DEBUGDIR=""
     103}
     104
     105make
     106
     107finishup()
     108{
     109        if [ -n "$DEBUGDIR" ]; then
     110                (
     111                cd $RECORDPREFIX/$DEBUGDIR
     112                $SUDO $TOPDIR/convert2jpg.sh
     113                $SUDO $TOPDIR/makehtml
     114                )
     115        fi
     116}
     117
     118trap finishup EXIT
     119
     120while [ $# -gt 0 ]; do
     121        CHANID="$1"
     122        case "$CHANID" in
     123        1071) runflag 1071 "2006-04-05T22:00:00" ;;
     124        1251) runflag 1251 "2006-05-17T20:00:00" ;;
     125        1381) runflag 1381 "2006-05-21T20:00:00" ;;
     126        2041) runflag 2041 "2006-05-21T22:00:00" ;;
     127        2041hd) runflag 2041 "2006-05-24T21:00:00" ;;
     128        2051hd) runflag 2051 "2006-05-24T20:00:00" ;;
     129        2561) runflag 2561 "2006-05-27T08:30:00" ;;
     130        24) runflag 2051 "2006-05-27T19:00:00" ;;       # 24 ABC, no logo
     131        stargate) runflag 1251 "2006-05-28T14:00:00" ;; # Stargate, faint logo
     132        *)
     133                CHANID=1071
     134                runflag 1071 "2006-04-05T22:00:00"
     135                ;;
     136        esac
     137        shift
     138done
  • programs/mythcommflag/TemplateFinder.h

    Property changes on: programs/mythcommflag/test.sh
    ___________________________________________________________________
    Name: svn:executable
       + *
    
     
     1/*
     2 * TemplateFinder
     3 *
     4 * Attempt to infer the existence of a static template across a series of
     5 * images by looking for stable edges. Some ideas are taken from
     6 * http://thomashargrove.com/logo-detection/
     7 *
     8 * By operating on the edges of images rather than the image data itself, both
     9 * opaque and transparent templates are robustly located (if they exist).
     10 * Hopefully the "core" portion of animated templates is sufficiently large and
     11 * stable enough for animated templates to also be discovered.
     12 *
     13 * This TemplateFinder only expects to successfully discover non- or
     14 * minimally-moving templates. Templates that change position during the
     15 * sequence of images will cause this algorithm to fail.
     16 */
     17
     18#ifndef __TEMPLATEFINDER_H__
     19#define __TEMPLATEFINDER_H__
     20
     21#include "FrameAnalyzer.h"
     22
     23typedef struct AVPicture AVPicture;
     24class PGMConverter;
     25class BorderDetector;
     26class EdgeDetector;
     27
     28class TemplateFinder : public FrameAnalyzer
     29{
     30public:
     31    /* Ctor/dtor. */
     32    TemplateFinder(PGMConverter *pgmc, BorderDetector *bd, EdgeDetector *ed,
     33            NuppelVideoPlayer *nvp, int chanid, const QDateTime &recstartts,
     34            QString debugdir);
     35    ~TemplateFinder(void);
     36
     37    /* FrameAnalyzer interface. */
     38    int extraBuffer(int preroll) const;
     39    enum analyzeFrameResult nuppelVideoPlayerInited(NuppelVideoPlayer *nvp);
     40    enum analyzeFrameResult analyzeFrame(const VideoFrame *frame,
     41            long long frameno, long long *pNextFrame);
     42    int finished(void);
     43
     44    /* TemplateFinder implementation. */
     45    const struct AVPicture *getTemplate(int *prow, int *pcol,
     46            int *pwidth, int *pheight) const;
     47
     48private:
     49    int resetBuffers(int newcwidth, int newcheight);
     50
     51    PGMConverter    *pgmConverter;
     52    BorderDetector  *borderDetector;
     53    EdgeDetector    *edgeDetector;
     54
     55    unsigned int    sampleTime;         /* amount of time to analyze */
     56    unsigned int    sampleSpacing;      /* seconds between frames */
     57    int             frameInterval;      /* analyze every <Interval> frames */
     58    long long       endFrame;           /* end of logo detection */
     59    long long       nextFrame;          /* next desired frame */
     60
     61    int             width, height;      /* dimensions of frames */
     62    unsigned int    *scores;            /* pixel "edge" scores */
     63
     64    int             mincontentrow;      /* limits of content area of images */
     65    int             maxcontentrow;
     66    int             mincontentcol;
     67    int             maxcontentcol;
     68
     69    AVPicture       tmpl;               /* logo-matching template */
     70    int             tmplrow, tmplcol;
     71    int             tmplwidth, tmplheight;
     72    bool            tmpldone;
     73
     74    AVPicture       cropped;            /* cropped version of frame */
     75    int             cwidth, cheight;    /* cropped height */
     76
     77    QString         tmplfile;           /* saved logo template */
     78    int             chanid;             /* recorded table lookup keys */
     79    QDateTime       recstartts;
     80
     81    /* Debugging. */
     82    int             debugLevel;
     83    QString         debugdir;
     84};
     85
     86#endif  /* !__TEMPLATEFINDER_H__ */
     87
     88/* vim: set expandtab tabstop=4 shiftwidth=4: */
  • programs/mythcommflag/PGMConverter.cpp

     
     1#include "NuppelVideoPlayer.h"
     2#include "avcodec.h"        /* AVPicture */
     3#include "frame.h"          /* VideoFrame */
     4
     5#include "pgm.h"
     6#include "PGMConverter.h"
     7
     8PGMConverter::PGMConverter(void)
     9    : frameno(-1)
     10{
     11    memset(&pgm, 0, sizeof(pgm));
     12}
     13
     14PGMConverter::~PGMConverter(void)
     15{
     16    avpicture_free(&pgm);
     17    pgm.data[0] = NULL;
     18}
     19
     20int
     21PGMConverter::nuppelVideoPlayerInited(const NuppelVideoPlayer *nvp)
     22{
     23    if (pgm.data[0])
     24        return 0;
     25
     26    width = nvp->GetVideoWidth();
     27    height = nvp->GetVideoHeight();
     28
     29    if (avpicture_alloc(&pgm, PIX_FMT_GRAY8, width, height))
     30    {
     31        VERBOSE(VB_COMMFLAG, QString("PGMConverter::nuppelVideoPlayerInited "
     32                "avpicture_alloc pgm (%1x%2) failed")
     33                .arg(width).arg(height));
     34        return -1;
     35    }
     36
     37    return 0;
     38}
     39
     40const AVPicture *
     41PGMConverter::getImage(const VideoFrame *frame, long long _frameno,
     42        int *pwidth, int *pheight)
     43{
     44    if (frameno == _frameno)
     45        goto out;
     46
     47    if (!frame->buf)
     48    {
     49        VERBOSE(VB_COMMFLAG, "PGMConverter::analyzeFrame no buf");
     50        goto error;
     51    }
     52
     53    if (pgm_fill(&pgm, frame))
     54        goto error;
     55
     56    frameno = _frameno;
     57
     58out:
     59    *pwidth = width;
     60    *pheight = height;
     61    return &pgm;
     62
     63error:
     64    VERBOSE(VB_COMMFLAG,
     65            QString("PGMConverter::analyzeFrame error at frame %1")
     66            .arg(frameno));
     67    return NULL;
     68}
     69
     70/* vim: set expandtab tabstop=4 shiftwidth=4: */
  • programs/mythcommflag/CommDetector2.h

     
     1#ifndef _COMMDETECTOR2_H_
     2#define _COMMDETECTOR2_H_
     3
     4#include <qdatetime.h>
     5#include <qptrlist.h>
     6
     7#include "CommDetectorBase.h"
     8
     9class NuppelVideoPlayer;
     10class EdgeDetector;
     11class FrameAnalyzer;
     12class TemplateMatcher;
     13
     14QString debugDirectory(int chanid, const QDateTime& recstartts);
     15
     16class CommDetector2 : public CommDetectorBase
     17{
     18public:
     19    CommDetector2(enum SkipTypes commDetectMethod,
     20            bool showProgress, bool fullSpeed, NuppelVideoPlayer* nvp,
     21            int chanid, const QDateTime& startts, const QDateTime& endts,
     22            const QDateTime& recstartts, const QDateTime& recendts);
     23    bool go(void);
     24    void getCommercialBreakList(QMap<long long, int> &comms);
     25    void recordingFinished(long long totalFileSize);
     26
     27private:
     28    int     buildBuffer(int minbuffer); // seconds
     29    void    reportState(int elapsed_sec, long long frameno, long long nframes,
     30            unsigned int passno, unsigned int npasses);
     31
     32    enum SkipTypes          commDetectMethod;
     33    bool                    showProgress;
     34    bool                    fullSpeed;
     35    NuppelVideoPlayer       *nvp;
     36    QDateTime               startts, endts, recstartts, recendts;
     37
     38    bool                    isRecording;        /* current state */
     39
     40    QPtrList<QPtrList<
     41        FrameAnalyzer> >    frameAnalyzers;     /* one list per scan of file */
     42
     43    QString                 debugdir;
     44};
     45
     46#endif
     47
     48/* vim: set expandtab tabstop=4 shiftwidth=4: */
  • programs/mythcommflag/BorderDetector.h

     
     1/*
     2 * BorderDetector
     3 *
     4 * Attempt to infer content boundaries of an image by looking for rows and
     5 * columns of pixels whose values fall within some narrow range.
     6 *
     7 * Handle letterboxing, pillarboxing, and combinations of the two. Handle cases
     8 * where letterboxing embedded inside of pillarboxing (or vice versa) uses a
     9 * different filler color.
     10 */
     11
     12#ifndef __BORDERDETECTOR_H__
     13#define __BORDERDETECTOR_H__
     14
     15typedef struct AVPicture AVPicture;
     16
     17class BorderDetector
     18{
     19public:
     20    /* Ctor/dtor. */
     21    BorderDetector(void);
     22    ~BorderDetector(void);
     23
     24    static const long long UNCACHED = -1;
     25    int getDimensions(const AVPicture *pgm, int pgmheight, long long frameno,
     26            int *prow, int *pcol, int *pwidth, int *pheight);
     27
     28private:
     29    long long       frameno;            /* frame number */
     30    int             row, col;           /* content location */
     31    int             width, height;      /* content dimensions */
     32
     33    int             debugLevel;
     34};
     35
     36#endif  /* !__BORDERDETECTOR_H__ */
     37
     38/* vim: set expandtab tabstop=4 shiftwidth=4: */
  • programs/mythcommflag/CommDetectorFactory.cpp

     
    11#include "CommDetectorFactory.h"
    22#include "ClassicCommDetector.h"
     3#include "CommDetector2.h"
    34
    45class NuppelVideoPlayer;
    56class RemoteEncoder;
    67
    78CommDetectorBase*
    8     CommDetectorFactory::makeCommDetector(int commDetectMethod,
     9CommDetectorFactory::makeCommDetector(enum SkipTypes commDetectMethod,
    910                                          bool showProgress, bool fullSpeed,
    1011                                          NuppelVideoPlayer* nvp,
     12                                          int chanid,
    1113                                          const QDateTime& startedAt,
    1214                                          const QDateTime& stopsAt,
    1315                                          const QDateTime& recordingStartedAt,
    1416                                          const QDateTime& recordingStopsAt)
    1517{
    16         switch (commDetectMethod)
    17         {
    18                 //Future different CommDetect implementations will be created here.
    19         default:
    20                         return new ClassicCommDetector(commDetectMethod, showProgress,
    21                                            fullSpeed, nvp, startedAt,
    22                                            stopsAt, recordingStartedAt,
    23                                            recordingStopsAt);
    24         }
    25        
    26         return 0;
     18    if ((commDetectMethod & COMM_DETECT_2))
     19    {
     20        return new CommDetector2(commDetectMethod, showProgress, fullSpeed,
     21                nvp, chanid, startedAt, stopsAt,
     22                recordingStartedAt, recordingStopsAt);
     23    }
     24
     25    return new ClassicCommDetector(commDetectMethod, showProgress, fullSpeed,
     26            nvp, startedAt, stopsAt, recordingStartedAt, recordingStopsAt);
    2727}
    2828
    2929
  • programs/mythcommflag/TemplateMatcher.cpp

     
     1#include <math.h>
     2#include <qdir.h>
     3
     4#include "NuppelVideoPlayer.h"
     5#include "avcodec.h"        /* AVPicture */
     6#include "frame.h"          /* VideoFrame */
     7
     8#include "pgm.h"
     9#include "PGMConverter.h"
     10#include "EdgeDetector.h"
     11#include "TemplateFinder.h"
     12#include "TemplateMatcher.h"
     13
     14/* Debugging */
     15#include <qfile.h>
     16#include <qfileinfo.h>
     17
     18namespace {
     19
     20int
     21pgm_set(const AVPicture *pict, int height)
     22{
     23    const int   width = pict->linesize[0];
     24    const int   size = height * width;
     25    int         score, ii;
     26
     27    score = 0;
     28    for (ii = 0; ii < size; ii++)
     29        if (pict->data[0][ii])
     30            score++;
     31    return score;
     32}
     33
     34int
     35pgm_match(const AVPicture *tmpl, const AVPicture *test, int height,
     36        int radius, unsigned short *pscore)
     37{
     38    /* Return the number of matching "edge" and non-edge pixels. */
     39    const int       width = tmpl->linesize[0];
     40    int             score, rr, cc;
     41
     42    if (width != test->linesize[0])
     43    {
     44        VERBOSE(VB_COMMFLAG, QString("pgm_match widths don't match: %1 != %2")
     45                .arg(width).arg(test->linesize[0]));
     46        return -1;
     47    }
     48
     49    score = 0;
     50    for (rr = 0; rr < height; rr++)
     51    {
     52        for (cc = 0; cc < width; cc++)
     53        {
     54            int r2min, r2max, r2, c2min, c2max, c2;
     55
     56            if (!tmpl->data[0][rr * width + cc])
     57                continue;
     58
     59            r2min = max(0, rr - radius);
     60            r2max = min(height, rr + radius);
     61
     62            c2min = max(0, cc - radius);
     63            c2max = min(width, cc + radius);
     64
     65            for (r2 = r2min; r2 <= r2max; r2++)
     66            {
     67                for (c2 = c2min; c2 <= c2max; c2++)
     68                {
     69                    if (test->data[0][r2 * width + c2])
     70                    {
     71                        score++;
     72                        goto next_pixel;
     73                    }
     74                }
     75            }
     76next_pixel:
     77            ;
     78        }
     79    }
     80
     81    *pscore = score;
     82    return 0;
     83}
     84
     85bool
     86readMatches(QString filename, unsigned short *matches)
     87{
     88    QFile qfile(filename);
     89
     90    if (qfile.exists() && qfile.open(IO_ReadOnly))
     91    {
     92        QTextStream     stream(&qfile);
     93        long long       ii = 0;
     94        bool            ok;
     95
     96        while (!stream.atEnd())
     97        {
     98            matches[ii++] = stream.readLine().toUShort(&ok);
     99            if (!ok)
     100                break;
     101        }
     102        qfile.close();
     103        return ok;
     104    }
     105
     106    return false;
     107}
     108
     109bool
     110writeMatches(QString filename, unsigned short *matches, long long nframes)
     111{
     112    QFile qfile(filename);
     113
     114    if (qfile.open(IO_WriteOnly))
     115    {
     116        QTextStream     stream(&qfile);
     117        long long       ii;
     118
     119        for (ii = 0; ii < nframes; ii++)
     120            stream << matches[ii] << "\n";
     121
     122        qfile.close();
     123        return true;
     124    }
     125    return false;
     126}
     127
     128int
     129finishedDebug(PGMConverter *pgmConverter, EdgeDetector *edgeDetector,
     130        NuppelVideoPlayer *nvp,
     131        long long nframes, const unsigned char *match,
     132        const unsigned short *matches, AVPicture *overlay, AVPicture *cropped,
     133        int tmplrow, int tmplcol, int tmplwidth, int tmplheight,
     134        int debugLevel, QString debugdir)
     135{
     136    static const int    FRAMESGMPCTILE = 70; /* TemplateMatcher::analyzeFrame */
     137    const int           width = overlay->linesize[0];
     138    const int           height = nvp->GetVideoHeight();
     139
     140    long long           frameno, startframe;
     141    unsigned short      score, low, high;
     142
     143    score = matches[0];
     144
     145    low = score;
     146    high = score;
     147    startframe = 0;
     148
     149    for (frameno = 1; frameno < nframes; frameno++)
     150    {
     151        score = matches[frameno];
     152
     153        if (match[frameno - 1] == match[frameno])
     154        {
     155            if (score < low)
     156                low = score;
     157            if (score > high)
     158                high = score;
     159            continue;
     160        }
     161
     162        VERBOSE(VB_COMMFLAG, QString("Frame %1-%2: %3 L-H: %4-%5 (%6)")
     163                .arg(startframe, 6).arg(frameno - 1, 6)
     164                .arg(match[frameno - 1] ? "logo        " : "     no-logo")
     165                .arg(low, 4).arg(high, 4).arg(frameno - startframe, 5));
     166
     167        low = score;
     168        high = score;
     169        startframe = frameno;
     170
     171        if (debugLevel >= 2)
     172        {
     173            VideoFrame          *frame;
     174            const AVPicture     *pgm;
     175            const AVPicture     *edges;
     176            int                 pgmwidth, pgmheight;
     177
     178            frame = nvp->GetRawVideoFrame(frameno);
     179
     180            if (!(pgm = pgmConverter->getImage(frame, frameno,
     181                            &pgmwidth, &pgmheight)))
     182                continue;
     183
     184            if (pgm_crop(cropped, pgm, pgmheight, tmplrow, tmplcol,
     185                        tmplwidth, tmplheight))
     186                continue;
     187
     188            if (!(edges = edgeDetector->detectEdges(cropped, tmplheight,
     189                            FRAMESGMPCTILE)))
     190                continue;
     191
     192            QString basefilename;
     193            basefilename.sprintf("%s/tm-%05lld-%d",
     194                    debugdir.ascii(), frameno, score);
     195            QFile tfile;
     196
     197            /* Compose template over frame. Write out and convert to JPG. */
     198
     199            QString basename = basefilename;
     200            if (!match[frameno])
     201                basename += "-c";
     202
     203            QString jpgfilename(basename + ".jpg");
     204            QFileInfo jpgfi(jpgfilename);
     205            QFile pgmfile(basename + ".pgm");
     206
     207            QString edgefilename(basefilename + "-edges.jpg");
     208            QFileInfo jpgedges(edgefilename);
     209            QFile pgmedges(basefilename + "-edges.pgm");
     210
     211            if (pgm_overlay(overlay, pgm, height, tmplrow, tmplcol,
     212                        edges, tmplheight))
     213                continue;
     214
     215            if (!jpgfi.exists())
     216            {
     217                if (!pgmfile.exists() && pgm_write(overlay->data[0],
     218                            width, height, pgmfile.name().ascii()))
     219                    continue;
     220                if (myth_system(QString(
     221                                "convert -quality 50 -resize 192x144 %1 %2")
     222                            .arg(pgmfile.name()).arg(jpgfi.filePath())))
     223                    continue;
     224            }
     225
     226            if (!jpgedges.exists())
     227            {
     228                if (!pgmedges.exists() && pgm_write(edges->data[0],
     229                            tmplwidth, tmplheight, pgmedges.name().ascii()))
     230                    continue;
     231                if (myth_system(QString(
     232                                "convert -quality 50 -resize 192x144 %1 %2")
     233                            .arg(pgmedges.name()).arg(jpgedges.filePath())))
     234                    continue;
     235            }
     236
     237            /* Delete any existing PGM files to save disk space. */
     238            tfile.setName(basefilename + ".pgm");
     239            if (tfile.exists() && !tfile.remove())
     240            {
     241                VERBOSE(VB_COMMFLAG, QString("Error removing %1 (%2)")
     242                        .arg(tfile.name()).arg(strerror(errno)));
     243                continue;
     244            }
     245            tfile.setName(basefilename + "-c.pgm");
     246            if (tfile.exists() && !tfile.remove())
     247            {
     248                VERBOSE(VB_COMMFLAG, QString("Error removing %1 (%2)")
     249                        .arg(tfile.name()).arg(strerror(errno)));
     250                continue;
     251            }
     252            tfile.setName(basefilename + "-edges.pgm");
     253            if (tfile.exists() && !tfile.remove())
     254            {
     255                VERBOSE(VB_COMMFLAG, QString("Error removing %1 (%2)")
     256                        .arg(tfile.name()).arg(strerror(errno)));
     257                continue;
     258            }
     259        }
     260    }
     261
     262    return 0;
     263}
     264
     265int
     266sort_ascending(const void *aa, const void *bb)
     267{
     268    return *(unsigned short*)aa - *(unsigned short*)bb;
     269}
     270
     271long long
     272matchspn(long long nframes, unsigned char *match, long long frameno,
     273        unsigned char acceptval)
     274{
     275    while (frameno < nframes && match[frameno] == acceptval)
     276        frameno++;
     277    return frameno;
     278}
     279
     280};  /* namespace */
     281
     282TemplateMatcher::TemplateMatcher(PGMConverter *pgmc, EdgeDetector *ed,
     283        TemplateFinder *tf, QString debugdir)
     284    : FrameAnalyzer()
     285    , pgmConverter(pgmc)
     286    , edgeDetector(ed)
     287    , templateFinder(tf)
     288    , matches(NULL)
     289    , match(NULL)
     290    , debugLevel(0)
     291    , debugdir(debugdir)
     292    , debugdata(debugdir + "/TemplateMatcher.txt")
     293    , nvp(NULL)
     294    , matches_done(false)
     295{
     296    memset(&cropped, 0, sizeof(cropped));
     297    memset(&overlay, 0, sizeof(overlay));
     298
     299    /*
     300     * debugLevel:
     301     *      0: no debugging
     302     *      1: extra verbosity, dump frame edge counts into debugdir [1 file]
     303     *      2: dump frames into debugdir [O(nframes)]
     304     */
     305    debugLevel = gContext->GetNumSetting("TemplateMatcherDebugLevel", 0);
     306    if (debugLevel > 0)
     307    {
     308        QDir qdir(debugdir);
     309        if (qdir.exists())
     310        {
     311            VERBOSE(VB_COMMFLAG, QString("TemplateMatcher debugLevel %1"
     312                        " using debug directory \"%2\"")
     313                    .arg(debugLevel).arg(debugdir));
     314        }
     315        else
     316        {
     317            if (qdir.mkdir(debugdir))
     318            {
     319                VERBOSE(VB_COMMFLAG, QString("TemplateMatcher debugLevel %1"
     320                            " created debug directory \"%2\"")
     321                        .arg(debugLevel).arg(debugdir));
     322            }
     323            else
     324            {
     325                VERBOSE(VB_COMMFLAG, QString("TemplateMatcher debugLevel %1"
     326                            " failed to create \"%2\": %3")
     327                        .arg(debugLevel).arg(debugdir).arg(strerror(errno)));
     328            }
     329        }
     330    }
     331}
     332
     333TemplateMatcher::~TemplateMatcher(void)
     334{
     335    avpicture_free(&overlay);
     336
     337    if (matches)
     338        delete []matches;
     339    if (match)
     340        delete []match;
     341    avpicture_free(&cropped);
     342}
     343
     344enum FrameAnalyzer::analyzeFrameResult
     345TemplateMatcher::nuppelVideoPlayerInited(NuppelVideoPlayer *_nvp)
     346{
     347    /*
     348     * TUNABLE:
     349     *
     350     * Percent of template's edges that must be covered by candidate frame's
     351     * test area to be considered a match.
     352     *
     353     * Higher values have tighter matching requirements, but can cause
     354     * legitimate template-matching frames to be passed over (for example, if
     355     * the template is fading into or out of existence in a sequence of
     356     * frames).
     357     *
     358     * Lower values relax matching requirements, but can yield false
     359     * identification of template-matching frames when the scene just happens
     360     * to have lots of edges in the same region of the frame.
     361     */
     362    const float     MINMATCHPCT = 0.559670;
     363
     364    const int       width = _nvp->GetVideoWidth();
     365    const int       height = _nvp->GetVideoHeight();
     366
     367    int             tmpledges;
     368
     369    nvp = _nvp;
     370    nframes = nvp->GetTotalFrameCount();
     371    fps = nvp->GetFrameRate();
     372
     373    if (!(tmpl = templateFinder->getTemplate(&tmplrow, &tmplcol,
     374                    &tmplwidth, &tmplheight)))
     375            return ANALYZE_FATAL;
     376
     377    tmpledges = pgm_set(tmpl, tmplheight);
     378    mintmpledges = (int)roundf(tmpledges * MINMATCHPCT);
     379
     380    VERBOSE(VB_COMMFLAG, QString("TemplateMatcher::nuppelVideoPlayerInited "
     381                "%1x%2@(%3,%4), %5 edge pixels (want %6)")
     382            .arg(tmplwidth).arg(tmplheight).arg(tmplcol).arg(tmplrow)
     383            .arg(tmpledges).arg(mintmpledges));
     384
     385    if (debugLevel >= 2)
     386    {
     387        if (avpicture_alloc(&overlay, PIX_FMT_GRAY8, width, height))
     388        {
     389            VERBOSE(VB_COMMFLAG, QString(
     390                        "TemplateMatcher::nuppelVideoPlayerInited "
     391                    "avpicture_alloc overlay (%1x%2) failed")
     392                    .arg(width).arg(height));
     393            return ANALYZE_FATAL;
     394        }
     395    }
     396
     397    if (avpicture_alloc(&cropped, PIX_FMT_GRAY8, tmplwidth, tmplheight))
     398    {
     399        VERBOSE(VB_COMMFLAG, QString("TemplateMatcher::nuppelVideoPlayerInited "
     400                "avpicture_alloc cropped (%1x%2) failed").
     401                arg(tmplwidth).arg(tmplheight));
     402        goto free_overlay;
     403    }
     404
     405    if (pgmConverter->nuppelVideoPlayerInited(nvp))
     406        goto free_cropped;
     407
     408    matches = new unsigned short[nframes];
     409    memset(matches, 0, nframes * sizeof(*matches));
     410
     411    match = new unsigned char[nframes];
     412
     413    if (debugLevel >= 1)
     414    {
     415        if (readMatches(debugdata, matches))
     416        {
     417            VERBOSE(VB_COMMFLAG, QString(
     418                        "TemplateMatcher::nuppelVideoPlayerInited read %1")
     419                    .arg(debugdata));
     420            matches_done = true;
     421        }
     422    }
     423
     424    if (matches_done)
     425        return ANALYZE_FINISHED;
     426
     427    return ANALYZE_OK;
     428
     429free_cropped:
     430    avpicture_free(&cropped);
     431free_overlay:
     432    avpicture_free(&overlay);
     433    return ANALYZE_FATAL;
     434}
     435
     436enum FrameAnalyzer::analyzeFrameResult
     437TemplateMatcher::analyzeFrame(const VideoFrame *frame, long long frameno,
     438        long long *pNextFrame)
     439{
     440    /*
     441     * TUNABLE:
     442     *
     443     * The matching area should be a lot smaller than the area used by
     444     * TemplateFinder, so use a smaller percentile than the TemplateFinder
     445     * (intensity requirements to be considered an "edge" are lower, because
     446     * there should be less pollution from non-template edges).
     447     *
     448     * Higher values mean fewer edge pixels in the candidate template area;
     449     * occluded or faint templates might be missed.
     450     *
     451     * Lower values mean more edge pixels in the candidate template area;
     452     * non-template edges can be picked up and cause false identification of
     453     * matches.
     454     */
     455    const int           FRAMESGMPCTILE = 70;    /* sync with finishedDebug */
     456
     457    /*
     458     * TUNABLE:
     459     *
     460     * The per-pixel search radius for matching the template. Meant to deal
     461     * with template edges that might minimally slide around (such as for
     462     * animated lighting effects).
     463     *
     464     * Higher values will pick up more pixels as matching the template
     465     * (possibly false matches).
     466     *
     467     * Lower values will require more exact template matches, possibly missing
     468     * true matches.
     469     *
     470     * The TemplateMatcher accumulates all its state in the "matches" array to
     471     * be processed later by TemplateMatcher::finished.
     472     */
     473    const int           JITTER_RADIUS = 0;
     474
     475    const AVPicture     *pgm;
     476    const AVPicture     *edges;
     477    int                 pgmwidth, pgmheight;
     478
     479    *pNextFrame = NEXTFRAME;
     480
     481    if (frameno >= 1 && frameno * 10 / nframes != (frameno - 1) * 10 / nframes)
     482    {
     483        /* Log something every 10%. */
     484        VERBOSE(VB_COMMFLAG, QString(
     485                    "TemplateMatcher::analyzeFrame %1 of %2 (%3%)")
     486                .arg(frameno).arg(nframes).arg((frameno + 1) * 100 / nframes));
     487    }
     488
     489    if (!(pgm = pgmConverter->getImage(frame, frameno, &pgmwidth, &pgmheight)))
     490        goto error;
     491
     492    if (pgm_crop(&cropped, pgm, pgmheight, tmplrow, tmplcol,
     493                tmplwidth, tmplheight))
     494        goto error;
     495
     496    if (!(edges = edgeDetector->detectEdges(&cropped, tmplheight,
     497                    FRAMESGMPCTILE)))
     498        goto error;
     499
     500    if (pgm_match(tmpl, edges, tmplheight, JITTER_RADIUS, &matches[frameno]))
     501        goto error;
     502
     503    return ANALYZE_OK;
     504
     505error:
     506    VERBOSE(VB_COMMFLAG, QString(
     507                "TemplateMatcher::analyzeFrame error at frame %1 of %2")
     508            .arg(frameno).arg(nframes));
     509    return ANALYZE_ERROR;
     510}
     511
     512int
     513TemplateMatcher::finished(void)
     514{
     515    /*
     516     * TUNABLE:
     517     *
     518     * Eliminate false negatives and false positives by eliminating segments
     519     * shorter than these periods.
     520     *
     521     * Higher values could eliminate real breaks entirely.
     522     * Lower values can yield more false "short" breaks.
     523     */
     524    const int       MINBREAKLEN = (int)roundf(15 * fps);  /* seconds */
     525    const int       MINSEGLEN = (int)roundf(15 * fps);    /* seconds */
     526
     527    int             ii, seg1len;
     528    long long       seg1b, seg1e;
     529
     530    if (!matches_done)
     531    {
     532        if (writeMatches(debugdata, matches, nframes))
     533        {
     534            VERBOSE(VB_COMMFLAG, QString("TemplateMatcher::finished wrote %1")
     535                    .arg(debugdata));
     536            matches_done = true;
     537        }
     538    }
     539
     540    for (ii = 0; ii < nframes; ii++)
     541        match[ii] = matches[ii] >= mintmpledges ? 1 : 0;
     542
     543    if (debugLevel >= 1)
     544    {
     545        if (finishedDebug(pgmConverter, edgeDetector, nvp,
     546                    nframes, match, matches, &overlay, &cropped,
     547                    tmplrow, tmplcol, tmplwidth, tmplheight,
     548                    debugLevel, debugdir))
     549            goto error;
     550    }
     551
     552    /*
     553     * Cut out false positives (breaks shorter than some threshold amount of
     554     * time).
     555     */
     556    seg1b = 0;
     557    while (seg1b < nframes)
     558    {
     559        /* Find break. */
     560        if (match[seg1b])
     561            seg1b = matchspn(nframes, match, seg1b, match[seg1b]);
     562
     563        seg1e = matchspn(nframes, match, seg1b, match[seg1b]);
     564        seg1len = seg1e - seg1b;
     565
     566        /* Eliminate break. */
     567        if (seg1len < MINBREAKLEN)
     568            memset(&match[seg1b], 1, seg1len);
     569
     570        seg1b = seg1e;
     571    }
     572
     573    /*
     574     * Cut out false negatives (show segments shorter than some threshold
     575     * amount of time).
     576     */
     577    seg1b = 0;
     578    while (seg1b < nframes)
     579    {
     580        /* Find segment. */
     581        if (!match[seg1b])
     582            seg1b = matchspn(nframes, match, seg1b, match[seg1b]);
     583
     584        seg1e = matchspn(nframes, match, seg1b, match[seg1b]);
     585        seg1len = seg1e - seg1b;
     586
     587        /* Eliminate segment. */
     588        if (seg1len < MINSEGLEN)
     589            memset(&match[seg1b], 0, seg1len);
     590
     591        seg1b = seg1e;
     592    }
     593    return 0;
     594
     595error:
     596    return -1;
     597}
     598
     599/* vim: set expandtab tabstop=4 shiftwidth=4: */
  • programs/mythcommflag/PGMConverter.h

     
     1/*
     2 * PGMConverter
     3 *
     4 * Object to convert a NuppelVideoPlayer frame into a greyscale image.
     5 */
     6
     7#ifndef __PGMCONVERTER_H__
     8#define __PGMCONVERTER_H__
     9
     10typedef struct VideoFrame_ VideoFrame;
     11typedef struct AVPicture AVPicture;
     12class NuppelVideoPlayer;
     13
     14class PGMConverter
     15{
     16public:
     17    /* Ctor/dtor. */
     18    PGMConverter(void);
     19    ~PGMConverter(void);
     20
     21    int nuppelVideoPlayerInited(const NuppelVideoPlayer *nvp);
     22    const AVPicture *getImage(const VideoFrame *frame, long long frameno,
     23            int *pwidth, int *pheight);
     24
     25private:
     26    long long       frameno;            /* frame number */
     27    int             width, height;      /* frame dimensions */
     28    AVPicture       pgm;                /* grayscale frame */
     29};
     30
     31#endif  /* !__PGMCONVERTER_H__ */
     32
     33/* vim: set expandtab tabstop=4 shiftwidth=4: */
  • programs/mythcommflag/CommDetectorFactory.h

     
    11#ifndef _COMMDETECTOR_FACTORY_H_
    22#define _COMMDETECTOR_FACTORY_H_
    33
     4#include "CommDetector.h"
     5
    46class CommDetectorBase;
    57class NuppelVideoPlayer;
    68class RemoteEncoder;
     
    1214        CommDetectorFactory() {}
    1315        ~CommDetectorFactory() {}
    1416
    15         CommDetectorBase* makeCommDetector(int commDetectMethod, bool showProgress,
     17        CommDetectorBase* makeCommDetector(enum SkipTypes commDetectMethod,
     18                                       bool showProgress,
    1619                                       bool fullSpeed, NuppelVideoPlayer* nvp,
     20                                       int chanid,
    1721                                       const QDateTime& startedAt,
    1822                                       const QDateTime& stopsAt,
    1923                                       const QDateTime& recordingStartedAt,
  • programs/mythcommflag/pgm.cpp

     
     1#include "avcodec.h"
     2#include "frame.h"
     3#include "mythcontext.h"
     4#include "pgm.h"
     5
     6/*
     7 * N.B.: this is really C code, but VERBOSE, #define'd in mythcontext.h, is in
     8 * a C++ header file, so this has to be compiled with a C++ compiler, which
     9 * means this has to be a C++ source file.
     10 */
     11
     12static enum PixelFormat
     13pixelTypeOfVideoFrameType(VideoFrameType codec)
     14{
     15    /* XXX: how to map VideoFrameType values to PixelFormat values??? */
     16    switch (codec) {
     17    case FMT_YV12:      return PIX_FMT_YUV420P;
     18    default:            break;
     19    }
     20    return PIX_FMT_NONE;
     21}
     22
     23int
     24pgm_fill(AVPicture *dst, const VideoFrame *frame)
     25{
     26    enum PixelFormat        srcfmt;
     27    AVPicture               src;
     28
     29    if ((srcfmt = pixelTypeOfVideoFrameType(frame->codec)) == PIX_FMT_NONE)
     30    {
     31        VERBOSE(VB_COMMFLAG, QString("pgm_fill unknown codec: %1")
     32                .arg(frame->codec));
     33        return -1;
     34    }
     35
     36    if (avpicture_fill(&src, frame->buf, srcfmt, frame->width,
     37                frame->height) < 0)
     38    {
     39        VERBOSE(VB_COMMFLAG, "pgm_fill avpicture_fill failed");
     40        return -1;
     41    }
     42
     43    if (img_convert(dst, PIX_FMT_GRAY8, &src, srcfmt, frame->width,
     44                frame->height))
     45    {
     46        VERBOSE(VB_COMMFLAG, "pgm_fill img_convert failed");
     47        return -1;
     48    }
     49
     50    return 0;
     51}
     52
     53int
     54pgm_read(unsigned char *buf, int width, int height, const char *filename)
     55{
     56    FILE        *fp;
     57    int         nn, fwidth, fheight, maxgray, rr;
     58
     59    if (!(fp = fopen(filename, "r")))
     60    {
     61        VERBOSE(VB_COMMFLAG, QString("pgm_read fopen %1 failed: %2")
     62                .arg(filename).arg(strerror(errno)));
     63        return -1;
     64    }
     65
     66    if ((nn = fscanf(fp, "P5\n%d %d\n%d\n", &fwidth, &fheight, &maxgray)) != 3)
     67    {
     68        VERBOSE(VB_COMMFLAG, QString("pgm_read fscanf %1 failed: %2")
     69                .arg(filename).arg(strerror(errno)));
     70        goto error;
     71    }
     72
     73    if (fwidth != width || fheight != height || maxgray != UCHAR_MAX)
     74    {
     75        VERBOSE(VB_COMMFLAG, QString("pgm_read header (%1x%2,%3) != (%4x%5,%6)")
     76                .arg(fwidth).arg(fheight).arg(maxgray)
     77                .arg(width).arg(height).arg(UCHAR_MAX));
     78        goto error;
     79    }
     80
     81    for (rr = 0; rr < height; rr++)
     82    {
     83        if (fread(buf + rr * width, 1, width, fp) != (size_t)width)
     84        {
     85            VERBOSE(VB_COMMFLAG, QString("pgm_read fread %1 failed: %2")
     86                    .arg(filename).arg(strerror(errno)));
     87            goto error;
     88        }
     89    }
     90
     91    (void)fclose(fp);
     92    return 0;
     93
     94error:
     95    (void)fclose(fp);
     96    return -1;
     97}
     98
     99int
     100pgm_write(const unsigned char *buf, int width, int height, const char *filename)
     101{
     102    /* Copied from libavcodec/apiexample.c */
     103    FILE        *fp;
     104    int         rr;
     105
     106    if (!(fp = fopen(filename, "w")))
     107    {
     108        VERBOSE(VB_COMMFLAG, QString("pgm_write fopen %1 failed: %2")
     109                .arg(filename).arg(strerror(errno)));
     110        return -1;
     111    }
     112
     113    (void)fprintf(fp, "P5\n%d %d\n%d\n", width, height, UCHAR_MAX);
     114    for (rr = 0; rr < height; rr++)
     115    {
     116        if (fwrite(buf + rr * width, 1, width, fp) != (size_t)width)
     117        {
     118            VERBOSE(VB_COMMFLAG, QString("pgm_write fwrite %1 failed: %2")
     119                    .arg(filename).arg(strerror(errno)));
     120            goto error;
     121        }
     122    }
     123
     124    (void)fclose(fp);
     125    return 0;
     126
     127error:
     128    (void)fclose(fp);
     129    return -1;
     130}
     131
     132static int
     133pgm_expand(AVPicture *dst, const AVPicture *src, int srcheight,
     134        int extratop, int extraright, int extrabottom, int extraleft)
     135{
     136    /* Pad all edges with the edge color. */
     137    const int           srcwidth = src->linesize[0];
     138    const int           newwidth = srcwidth + extraleft + extraright;
     139    const int           newheight = srcheight + extratop + extrabottom;
     140    const unsigned char *srcdata;
     141    int                 rr;
     142
     143    /* Copy the image. */
     144    for (rr = 0; rr < srcheight; rr++)
     145        memcpy(dst->data[0] + (rr + extratop) * newwidth + extraleft,
     146                src->data[0] + rr * srcwidth,
     147                srcwidth);
     148
     149    /* Pad the top. */
     150    srcdata = src->data[0];
     151    for (rr = 0; rr < extratop; rr++)
     152        memcpy(dst->data[0] + rr * newwidth + extraleft, srcdata, srcwidth);
     153
     154    /* Pad the bottom. */
     155    srcdata = src->data[0] + (srcheight - 1) * srcwidth;
     156    for (rr = extratop + srcheight; rr < newheight; rr++)
     157        memcpy(dst->data[0] + rr * newwidth + extraleft, srcdata, srcwidth);
     158
     159    /* Pad the left. */
     160    for (rr = 0; rr < newheight; rr++)
     161        memset(dst->data[0] + rr * newwidth,
     162                dst->data[0][rr * newwidth + extraleft],
     163                extraleft);
     164
     165    /* Pad the right. */
     166    for (rr = 0; rr < newheight; rr++)
     167        memset(dst->data[0] + rr * newwidth + extraleft + srcwidth,
     168                dst->data[0][rr * newwidth + extraleft + srcwidth - 1],
     169                extraright);
     170
     171    return 0;
     172}
     173
     174static int
     175pgm_expand_uniform(AVPicture *dst, const AVPicture *src, int srcheight,
     176        int extramargin)
     177{
     178    return pgm_expand(dst, src, srcheight,
     179            extramargin, extramargin, extramargin, extramargin);
     180}
     181
     182int
     183pgm_crop(AVPicture *dst, const AVPicture *src, int srcheight,
     184        int srcrow, int srccol, int cropwidth, int cropheight)
     185{
     186    const int   srcwidth = src->linesize[0];
     187    int         rr;
     188
     189    if (dst->linesize[0] != cropwidth)
     190    {
     191        VERBOSE(VB_COMMFLAG, QString("pgm_crop want width %1, have %2")
     192                .arg(cropwidth).arg(dst->linesize[0]));
     193        return -1;
     194    }
     195
     196    for (rr = 0; rr < cropheight; rr++)
     197        memcpy(dst->data[0] + rr * cropwidth,
     198                src->data[0] + (srcrow + rr) * srcwidth + srccol,
     199                cropwidth);
     200
     201    (void)srcheight;    /* gcc */
     202    return 0;
     203}
     204
     205int
     206pgm_overlay(AVPicture *dst, const AVPicture *s1, int s1height,
     207        int s1row, int s1col, const AVPicture *s2, int s2height)
     208{
     209    const int   dstwidth = dst->linesize[0];
     210    const int   s1width = s1->linesize[0];
     211    const int   s2width = s2->linesize[0];
     212    int         rr;
     213
     214    if (dstwidth != s1width)
     215    {
     216        VERBOSE(VB_COMMFLAG, QString("pgm_overlay want width %1, have %2")
     217                .arg(s1width).arg(dst->linesize[0]));
     218        return -1;
     219    }
     220
     221    img_copy(dst, s1, PIX_FMT_GRAY8, s1width, s1height);
     222
     223    /* Overwrite overlay area of "dst" with "s2". */
     224    for (rr = 0; rr < s2height; rr++)
     225        memcpy(dst->data[0] + (s1row + rr) * s1width + s1col,
     226                s2->data[0] + rr * s2width,
     227                s2width);
     228
     229    return 0;
     230}
     231
     232int
     233pgm_convolve_radial(AVPicture *dst, AVPicture *s1, AVPicture *s2,
     234        const AVPicture *src, int srcheight,
     235        const double *mask, int mask_radius)
     236{
     237    /*
     238     * Pad and convolve an image.
     239     *
     240     * "s1" and "s2" are caller-pre-allocated "scratch space" (avoid repeated
     241     * per-frame allocation/deallocation).
     242     *
     243     * Remove noise from image; smooth by convolving with a Gaussian mask. See
     244     * http://www.cogs.susx.ac.uk/users/davidy/teachvision/vision0.html
     245     *
     246     * Optimization for radially-symmetric masks: implement a single
     247     * two-dimensional convolution with two commutative single-dimensional
     248     * convolutions.
     249     */
     250    const int       srcwidth = src->linesize[0];
     251    const int       newwidth = srcwidth + 2 * mask_radius;
     252    const int       newheight = srcheight + 2 * mask_radius;
     253    int             ii, rr, cc;
     254    double          sum;
     255
     256    /* Get a padded copy of the src image for use by the convolutions. */
     257    if (pgm_expand_uniform(s1, src, srcheight, mask_radius))
     258        return -1;
     259
     260    /* copy s1 to s2 and dst */
     261    img_copy(s2, s1, PIX_FMT_GRAY8, newwidth, newheight);
     262    img_copy(dst, s1, PIX_FMT_GRAY8, newwidth, newheight);
     263
     264    /* "s1" convolve with column vector => "s2" */
     265    for (rr = mask_radius; rr < mask_radius + srcheight; rr++)
     266    {
     267        for (cc = mask_radius; cc < mask_radius + srcwidth; cc++)
     268        {
     269            sum = 0;
     270            for (ii = -mask_radius; ii <= mask_radius; ii++)
     271            {
     272                sum += mask[ii + mask_radius] *
     273                    s1->data[0][(rr + ii) * newwidth + cc];
     274            }
     275            s2->data[0][rr * newwidth + cc] = (unsigned char)(sum + 0.5);
     276        }
     277    }
     278
     279    /* "s2" convolve with row vector => "dst" */
     280    for (rr = mask_radius; rr < mask_radius + srcheight; rr++)
     281    {
     282        for (cc = mask_radius; cc < mask_radius + srcwidth; cc++)
     283        {
     284            sum = 0;
     285            for (ii = -mask_radius; ii <= mask_radius; ii++)
     286            {
     287                sum += mask[ii + mask_radius] *
     288                    s2->data[0][rr * newwidth + cc + ii];
     289            }
     290            dst->data[0][rr * newwidth + cc] = (unsigned char)(sum + 0.5);
     291        }
     292    }
     293
     294    return 0;
     295}
     296
     297/* vim: set expandtab tabstop=4 shiftwidth=4: */
  • programs/mythcommflag/main.cpp

     
    4444CommDetectorBase* commDetector = NULL;
    4545RemoteEncoder* recorder = NULL;
    4646ProgramInfo* program_info = NULL;
    47 int commDetectMethod = -1;
     47enum SkipTypes commDetectMethod = COMM_DETECT_UNINIT;
    4848int recorderNum = -1;
    4949bool dontSubmitCommbreakListToDB =  false;
    5050QString outputfilename;
     
    372372}
    373373
    374374int DoFlagCommercials(bool showPercentage, bool fullSpeed, bool inJobQueue,
    375                       NuppelVideoPlayer* nvp, int commDetectMethod)
     375                      NuppelVideoPlayer* nvp, enum SkipTypes commDetectMethod)
    376376{
    377377    CommDetectorFactory factory;
    378378    commDetector = factory.makeCommDetector(commDetectMethod, showPercentage,
    379379                                            fullSpeed, nvp,
     380                                            program_info->chanid.toInt(),
    380381                                            program_info->startts,
    381382                                            program_info->endts,
    382383                                            program_info->recstartts,
     
    451452int FlagCommercials(QString chanid, QString starttime)
    452453{
    453454    int breaksFound = 0;
    454     if (commDetectMethod==-1)
    455         commDetectMethod = gContext->GetNumSetting("CommercialSkipMethod",
    456                                                   COMM_DETECT_ALL);
     455    if (commDetectMethod == COMM_DETECT_UNINIT)
     456        commDetectMethod = (enum SkipTypes)gContext->GetNumSetting(
     457                                    "CommercialSkipMethod", COMM_DETECT_ALL);
    457458    QMap<long long, int> blanks;
    458459    recorder = NULL;
    459460    program_info = ProgramInfo::GetProgramFromRecorded(chanid, starttime);
     
    684685        {
    685686            QString method = (a.argv()[++argpos]);
    686687            bool ok;
    687             commDetectMethod = method.toInt(&ok);
     688            commDetectMethod = (enum SkipTypes)method.toInt(&ok);
    688689            if (!ok)
    689                 commDetectMethod = -1;
     690                commDetectMethod = COMM_DETECT_UNINIT;
    690691        }
    691692        else if (!strcmp(a.argv()[argpos], "--gencutlist"))
    692693            copyToCutlist = true;
  • programs/mythcommflag/TemplateMatcher.h

     
     1/*
     2 * TemplateMatcher
     3 *
     4 * Decide whether or not an image matches a given template.
     5 *
     6 * This TemplateMatcher is tuned to yield very few false positives, at the cost
     7 * of slightly more false negatives.
     8 *
     9 * After all images are analyzed, attempt to remove the mis-categorized images
     10 * based on inter-image information (e.g., minimum sequence lengths of template
     11 * matching or non-matching).
     12 *
     13 * This TemplateMatcher only expects to successfully match templates in a fixed
     14 * location. It would be trivial to add code to search the entire frame for a
     15 * template known to change locations, but at a very large cost of time.
     16 */
     17
     18#ifndef __TEMPLATEMATCHER_H__
     19#define __TEMPLATEMATCHER_H__
     20
     21#include "FrameAnalyzer.h"
     22
     23typedef struct AVPicture AVPicture;
     24class PGMConverter;
     25class EdgeDetector;
     26class TemplateFinder;
     27
     28class TemplateMatcher : public FrameAnalyzer
     29{
     30public:
     31    /* Ctor/dtor. */
     32    TemplateMatcher(PGMConverter *pgmc, EdgeDetector *ed, TemplateFinder *tf,
     33            QString debugdir);
     34    ~TemplateMatcher(void);
     35
     36    /* FrameAnalyzer interface. */
     37    enum analyzeFrameResult nuppelVideoPlayerInited(NuppelVideoPlayer *nvp);
     38    enum analyzeFrameResult analyzeFrame(const VideoFrame *frame,
     39            long long frameno, long long *pNextFrame);
     40    int finished(void);
     41    bool isContent(long long frameno) const { return match[frameno] > 0; }
     42
     43private:
     44    PGMConverter            *pgmConverter;
     45    EdgeDetector            *edgeDetector;
     46    TemplateFinder          *templateFinder;
     47    const struct AVPicture  *tmpl;                  /* template image */
     48    int                     tmplrow, tmplcol;       /* template location */
     49    int                     tmplwidth, tmplheight;  /* template dimensions */
     50    int                     mintmpledges;           /* # edge pixels in tmpl */
     51
     52    unsigned short          *matches;               /* matching pixels */
     53    unsigned char           *match;                 /* boolean result: 1/0 */
     54
     55    long long               nframes;                /* total frame count */
     56    float                   fps;
     57
     58    AVPicture               cropped;                /* cropped frame */
     59
     60    /* Debugging */
     61    int                     debugLevel;
     62    QString                 debugdir;
     63    QString                 debugdata;
     64    AVPicture               overlay;                /* frame + logo overlay */
     65    NuppelVideoPlayer       *nvp;
     66    bool                    matches_done;
     67};
     68
     69#endif  /* !__TEMPLATEMATCHER_H__ */
     70
     71/* vim: set expandtab tabstop=4 shiftwidth=4: */
     72
  • programs/mythcommflag/ClassicCommDetector.cpp

     
    3232    COMM_FORMAT_MAX
    3333};
    3434
    35 ClassicCommDetector::ClassicCommDetector(int commDetectMethod_in,
     35ClassicCommDetector::ClassicCommDetector(enum SkipTypes commDetectMethod_in,
    3636                                         bool showProgress_in,
    3737                                         bool fullSpeed_in,
    3838                                         NuppelVideoPlayer* nvp_in,
     
    563563            case COMM_DETECT_ALL:         BuildAllMethodsCommList();
    564564                                          marks = commBreakMap;
    565565                                          break;
     566            default: VERBOSE(VB_COMMFLAG,
     567                             QString("Unexpected commDetectMethod: %1")
     568                             .arg(commDetectMethod));
     569                     break;
    566570    }
    567571
    568572    VERBOSE(VB_COMMFLAG, "Final Commercial Break Map" );
  • programs/mythcommflag/CannyEdgeDetector.cpp

     
     1#include <math.h>
     2
     3#include "NuppelVideoPlayer.h"
     4#include "avcodec.h"        // AVPicture
     5#include "frame.h"          // VideoFrame
     6
     7#include "pgm.h"
     8#include "CannyEdgeDetector.h"
     9
     10using namespace edgeDetector;
     11
     12CannyEdgeDetector::CannyEdgeDetector(void)
     13    : sgm(NULL)
     14    , sgmsorted(NULL)
     15    , ewidth(-1)
     16    , eheight(-1)
     17{
     18    /*
     19     * In general, the Gaussian mask is truncated at a point where values cease
     20     * to make any meaningful contribution. The sigma=>truncation computation
     21     * is best done by table lookup (which I don't have here). For sigma=0.5,
     22     * the magic truncation value is 4.
     23     */
     24    const int       TRUNCATION = 4;
     25    const double    sigma = 0.5;
     26    const double    TWO_SIGMA2 = 2 * sigma * sigma;
     27
     28    double          val, sum;
     29    int             mask_width, rr, ii;
     30
     31    /* The SGM computations require that mask_radius >= 2. */
     32    mask_radius = max(2, (int)roundf(TRUNCATION * sigma));
     33    mask_width = 2 * mask_radius + 1;
     34
     35    /* Compute Gaussian mask. */
     36    mask = new double[mask_width];
     37    val = exp(0);
     38    mask[mask_radius] = val;
     39    sum = val;
     40    for (rr = 1; rr <= mask_radius; rr++)
     41    {
     42        val = exp(-(rr * rr) / TWO_SIGMA2); // Gaussian weight(rr,sigma)
     43        mask[mask_radius + rr] = val;
     44        mask[mask_radius - rr] = val;
     45        sum += 2 * val;
     46    }
     47    for (ii = 0; ii < mask_width; ii++)
     48        mask[ii] /= sum;    /* normalize to [0,1] */
     49
     50    memset(&s1, 0, sizeof(s1));
     51    memset(&s2, 0, sizeof(s2));
     52    memset(&convolved, 0, sizeof(convolved));
     53    memset(&edges, 0, sizeof(edges));
     54}
     55
     56CannyEdgeDetector::~CannyEdgeDetector(void)
     57{
     58    avpicture_free(&edges);
     59    avpicture_free(&convolved);
     60    avpicture_free(&s2);
     61    avpicture_free(&s1);
     62    if (sgmsorted)
     63        delete []sgmsorted;
     64    if (sgm)
     65        delete []sgm;
     66    if (mask)
     67        delete []mask;
     68}
     69
     70int
     71CannyEdgeDetector::resetBuffers(int newwidth, int newheight)
     72{
     73    if (ewidth == newwidth && eheight == newheight)
     74        return 0;
     75
     76    if (sgm) {
     77        /*
     78         * Sentinel value to determine whether or not stuff has already been
     79         * allocated.
     80         */
     81        avpicture_free(&s1);
     82        avpicture_free(&s2);
     83        avpicture_free(&convolved);
     84        avpicture_free(&edges);
     85        delete []sgm;
     86        delete []sgmsorted;
     87        sgm = NULL;
     88    }
     89
     90    const int   padded_width = newwidth + 2 * mask_radius;
     91    const int   padded_height = newheight + 2 * mask_radius;
     92
     93    if (avpicture_alloc(&s1, PIX_FMT_GRAY8, padded_width, padded_height))
     94    {
     95        VERBOSE(VB_COMMFLAG, "CannyEdgeDetector::resetBuffers "
     96                "avpicture_alloc s1 failed");
     97        return -1;
     98    }
     99
     100    if (avpicture_alloc(&s2, PIX_FMT_GRAY8, padded_width, padded_height))
     101    {
     102        VERBOSE(VB_COMMFLAG, "CannyEdgeDetector::resetBuffers "
     103                "avpicture_alloc s2 failed");
     104        goto free_s1;
     105    }
     106
     107    if (avpicture_alloc(&convolved, PIX_FMT_GRAY8, padded_width, padded_height))
     108    {
     109        VERBOSE(VB_COMMFLAG, "CannyEdgeDetector::resetBuffers "
     110                "avpicture_alloc convolved failed");
     111        goto free_s2;
     112    }
     113
     114    if (avpicture_alloc(&edges, PIX_FMT_GRAY8, newwidth, newheight))
     115    {
     116        VERBOSE(VB_COMMFLAG, "CannyEdgeDetector::resetBuffers "
     117                "avpicture_alloc edges failed");
     118        goto free_convolved;
     119    }
     120
     121    sgm = new unsigned int[padded_width * padded_height];
     122    sgmsorted = new unsigned int[newwidth * newheight];
     123
     124    ewidth = newwidth;
     125    eheight = newheight;
     126
     127    return 0;
     128
     129free_convolved:
     130    avpicture_free(&convolved);
     131free_s2:
     132    avpicture_free(&s2);
     133free_s1:
     134    avpicture_free(&s1);
     135    return -1;
     136}
     137
     138const AVPicture *
     139CannyEdgeDetector::detectEdges(const AVPicture *pgm, int pgmheight,
     140        int percentile)
     141{
     142    /*
     143     * Canny edge detection
     144     *
     145     * See
     146     * http://www.cs.cornell.edu/courses/CS664/2003fa/handouts/664-l6-edges-03.pdf
     147     */
     148
     149    const int   pgmwidth = pgm->linesize[0];
     150    const int   padded_height = pgmheight + 2 * mask_radius;
     151
     152    if (resetBuffers(pgmwidth, pgmheight))
     153        return NULL;
     154
     155    if (pgm_convolve_radial(&convolved, &s1, &s2, pgm, pgmheight,
     156                mask, mask_radius))
     157        return NULL;
     158
     159    if (edge_mark_uniform(&edges, pgmheight, mask_radius,
     160                sgm_init(sgm, &convolved, padded_height), sgmsorted,
     161                percentile))
     162        return NULL;
     163
     164    return &edges;
     165}
     166
     167/* vim: set expandtab tabstop=4 shiftwidth=4: */
  • programs/mythcommflag/pgm.h

     
     1/*
     2 * pgm.h
     3 *
     4 * Routines for querying and manipulating PGM (greyscale) images.
     5 */
     6
     7#ifndef __PGM_H__
     8#define __PGM_H__
     9
     10struct VideoFrame_;
     11struct AVPicture;
     12
     13int pgm_fill(struct AVPicture *dst, const struct VideoFrame_ *frame);
     14int pgm_read(unsigned char *buf, int width, int height, const char *filename);
     15int pgm_write(const unsigned char *buf, int width, int height,
     16        const char *filename);
     17int pgm_crop(struct AVPicture *dst, const struct AVPicture *src, int srcheight,
     18        int srcrow, int srccol, int cropwidth, int cropheight);
     19int pgm_overlay(struct AVPicture *dst,
     20        const struct AVPicture *s1, int s1height, int s1row, int s1col,
     21        const struct AVPicture *s2, int s2height);
     22int pgm_convolve_radial(struct AVPicture *dst, struct AVPicture *s1,
     23        struct AVPicture *s2, const struct AVPicture *src, int srcheight,
     24        const double *mask, int mask_radius);
     25
     26#endif  /* !__PGM_H__ */
     27
     28/* vim: set expandtab tabstop=4 shiftwidth=4: */
  • programs/mythcommflag/CommDetector.h

     
    33
    44// This is used as a bitmask.
    55enum SkipTypes {
    6     COMM_DETECT_OFF         = 0x0000,
    7     COMM_DETECT_BLANKS      = 0x0001,
    8     COMM_DETECT_SCENE       = 0x0002,
    9     COMM_DETECT_BLANK_SCENE = 0x0003,
    10     COMM_DETECT_LOGO        = 0x0004,
    11     COMM_DETECT_ALL         = 0x00FF
     6    COMM_DETECT_UNINIT      = -1,
     7    COMM_DETECT_OFF         = 0x00000000,
     8    COMM_DETECT_BLANKS      = 0x00000001,
     9    COMM_DETECT_SCENE       = 0x00000002,
     10    COMM_DETECT_BLANK_SCENE = 0x00000003,
     11    COMM_DETECT_LOGO        = 0x00000004,
     12    COMM_DETECT_ALL         = 0x000000FF,
     13    COMM_DETECT_2           = 0x00000100,
     14    COMM_DETECT_2_LOGO      = 0x00000101,
     15    COMM_DETECT_2_ALL       = 0x000001FF,
    1216};
    1317
    1418#endif
  • programs/mythcommflag/ClassicCommDetector.h

     
    1010class ClassicCommDetector : public CommDetectorBase
    1111{
    1212    public:
    13         ClassicCommDetector(int commDetectMethod, bool showProgress,
     13        ClassicCommDetector(enum SkipTypes commDetectMethod, bool showProgress,
    1414                            bool fullSpeed, NuppelVideoPlayer* nvp,
    1515                            const QDateTime& startedAt_in,
    1616                            const QDateTime& stopsAt_in,
     
    9292        void DetectEdges(VideoFrame *frame, EdgeMaskEntry *edges, int edgeDiff);
    9393        void GetLogoCommBreakMap(QMap<long long, int> &map);
    9494
    95         int commDetectMethod;
     95        enum SkipTypes commDetectMethod;
    9696        bool showProgress;
    9797        bool fullSpeed;
    9898        NuppelVideoPlayer *nvp;
  • programs/mythcommflag/CannyEdgeDetector.h

     
     1/*
     2 * CannyEdgeDetector
     3 *
     4 * Implement the Canny edge detection algorithm.
     5 */
     6
     7#ifndef __CANNYEDGEDETECTOR_H__
     8#define __CANNYEDGEDETECTOR_H__
     9
     10#include "EdgeDetector.h"
     11
     12typedef struct VideoFrame_ VideoFrame;
     13typedef struct AVPicture AVPicture;
     14class NuppelVideoPlayer;
     15
     16class CannyEdgeDetector : public EdgeDetector
     17{
     18public:
     19    CannyEdgeDetector(void);
     20    ~CannyEdgeDetector(void);
     21    int nuppelVideoPlayerInited(const NuppelVideoPlayer *nvp,
     22            int width, int height);
     23    const AVPicture *detectEdges(const AVPicture *pgm, int pgmheight,
     24            int percentile);
     25
     26private:
     27    int resetBuffers(int pgmwidth, int pgmheight);
     28
     29    double          *mask;                  /* pre-computed Gaussian mask */
     30    int             mask_radius;            /* radius of mask */
     31
     32    unsigned int    *sgm, *sgmsorted;       /* squared-gradient magnitude */
     33    AVPicture       s1, s2, convolved;      /* smoothed grayscale frame */
     34    int             ewidth, eheight;        /* dimensions */
     35    AVPicture       edges;                  /* detected edges */
     36};
     37
     38#endif  /* !__CANNYEDGEDETECTOR_H__ */
     39
     40/* vim: set expandtab tabstop=4 shiftwidth=4: */
  • programs/mythbackend/mainserver.cpp

     
    5858/** Number of threads in process request thread pool at startup. */
    5959#define PRT_STARTUP_THREAD_COUNT 5
    6060
     61namespace {
     62
     63int deleteFile(QString filename, bool followLinks)
     64{
     65    QFile checkFile(filename);
     66    int err1, err2;
     67
     68    VERBOSE(VB_FILE, QString("About to unlink/delete file: %1").arg(filename));
     69    err1 = 0;
     70    if (followLinks)
     71    {
     72        QFileInfo finfo(filename);
     73        if (finfo.isSymLink() && (err1 = unlink(finfo.readLink().local8Bit())))
     74        {
     75            VERBOSE(VB_IMPORTANT, QString("Error deleting '%1' @ '%2', %3")
     76                    .arg(filename).arg(finfo.readLink().local8Bit())
     77                    .arg(strerror(errno)));
     78        }
     79    }
     80    if (checkFile.exists() && (err2 = unlink(filename.local8Bit())))
     81    {
     82        VERBOSE(VB_IMPORTANT, QString("Error deleting '%1', %2")
     83                .arg(filename).arg(strerror(errno)));
     84    }
     85    return err1 ? err1 : err2;
     86}
     87
     88};
     89
    6190class ProcessRequestThread : public QThread
    6291{
    6392  public:
     
    13431372    QString filename = ds->filename;
    13441373    bool followLinks = gContext->GetNumSetting("DeletesFollowLinks", 0);
    13451374
    1346     VERBOSE(VB_FILE, QString("About to unlink/delete file: %1").arg(filename));
    1347     if (followLinks)
    1348     {
    1349         QFileInfo finfo(filename);
    1350         if (finfo.isSymLink() && (err = unlink(finfo.readLink().local8Bit())))
    1351         {
    1352             VERBOSE(VB_IMPORTANT, QString("Error deleting '%1' @ '%2', %3")
    1353                     .arg(filename).arg(finfo.readLink().local8Bit())
    1354                     .arg(strerror(errno)));
    1355         }
    1356     }
    1357     if ((err = unlink(filename.local8Bit())))
    1358         VERBOSE(VB_IMPORTANT, QString("Error deleting '%1', %2")
    1359                 .arg(filename).arg(strerror(errno)));
     1375    /* Delete recording. */
     1376    err = deleteFile(filename, followLinks);
    13601377   
    13611378    sleep(2);
    13621379
     
    13791396        return;
    13801397    }
    13811398
     1399    /* Delete preview thumbnail. */
    13821400    filename = ds->filename + ".png";
    1383     if (followLinks)
    1384     {
    1385         QFileInfo finfo(filename);
    1386         if (finfo.isSymLink() && (err = unlink(finfo.readLink().local8Bit())))
    1387         {
    1388             VERBOSE(VB_IMPORTANT, QString("Error deleting '%1' @ '%2', %3")
    1389                     .arg(filename).arg(finfo.readLink().local8Bit())
    1390                     .arg(strerror(errno)));
    1391         }
    1392     }
     1401    err = deleteFile(filename, followLinks);
    13931402
    1394     checkFile.setName(filename);
    1395     if (checkFile.exists() && (err = unlink(filename.local8Bit())))
    1396         VERBOSE(VB_IMPORTANT, QString("Error deleting '%1', %2")
    1397                 .arg(filename).arg(strerror(errno)));
     1403    /* Delete commflag template. */
     1404    QFileInfo fi(ds->filename);
     1405    filename = fi.dirPath(TRUE) + "/" + fi.baseName(TRUE) + "-template.pgm";
     1406    err = deleteFile(filename, followLinks);
    13981407
    13991408    MSqlQuery query(MSqlQuery::InitCon());
    14001409    query.prepare("DELETE FROM recorded WHERE chanid = :CHANID AND "
  • programs/mythbackend/autoexpire.cpp

     
    418418        if (CheckFile(*it, record_file_prefix, fsid))
    419419        {
    420420            // Print informative message
    421             msg = QString("Expiring \"%1\" from %2, %3 MBytes")
    422                 .arg((*it)->title).arg((*it)->startts.toString())
     421            QString titlestr = (*it)->title;
     422            if (!(*it)->subtitle.isEmpty())
     423                titlestr += " \"" + (*it)->subtitle + "\"";
     424            msg = QString("Expiring %1 from %2, %3 MBytes")
     425                .arg(titlestr)
     426                .arg((*it)->startts.toString())
    423427                .arg((int)((*it)->filesize/1024/1024));
    424428
    425429            if (deleteAll)