Ticket #1877: mythtv.commflag.diff

File mythtv.commflag.diff, 117.1 KB (added by Robert Tsai <rtsai1111>, 15 years ago)
  • 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         * Intuitively, we should simply repeat until a single bounding box is
     164         * converged upon. However, this requires a more sophisticated
     165         * bounding_score function that I don't feel like figuring out.
     166         * Indefinitely repeating with the present bounding_score function will
     167         * tend to chop off too much. Instead, simply do some sanity checks on
     168         * the candidate template's size, and prune the template area and
     169         * repeat if it is too "large".
     170         */
     171
     172        if (width > maxwidth)
     173        {
     174            /* Too wide; pick the left or right side. */
     175            int             chop = width / 3;
     176            int             chopwidth = width - chop;
     177            unsigned int    left, right;
     178
     179            left = bounding_score(img, row, col, chopwidth, height);
     180            right = bounding_score(img, row, col + chop, chopwidth, height);
     181            if (left < right)
     182            {
     183                VERBOSE(VB_COMMFLAG, QString(
     184                            "bounding_box width %1 > %2, pick right (%3)")
     185                        .arg(width).arg(maxwidth).arg(width - chop));
     186                col += chop;
     187            }
     188            else
     189            {
     190                VERBOSE(VB_COMMFLAG, QString(
     191                            "bounding_box width %1 > %2, pick left (%3)")
     192                        .arg(width).arg(maxwidth).arg(width - chop));
     193            }
     194            width -= chop;
     195            continue;
     196        }
     197
     198        if (height > maxheight)
     199        {
     200            /* Too tall; pick the upper or lower part. */
     201            int             chop = height / 3;
     202            int             chopheight = height - chop;
     203            unsigned int    upper, lower;
     204
     205            upper = bounding_score(img, row, col, width, chopheight);
     206            lower = bounding_score(img, row + chop, col, width, chopheight);
     207            if (upper < lower)
     208            {
     209                VERBOSE(VB_COMMFLAG, QString(
     210                            "bounding_box height %1 > %2, pick bottom (%3)")
     211                        .arg(height).arg(maxheight).arg(height - chop));
     212                row += chop;
     213            }
     214            else
     215            {
     216                VERBOSE(VB_COMMFLAG, QString(
     217                            "bounding_box height %1 > %2, pick top (%3)")
     218                        .arg(height).arg(maxheight).arg(height - chop));
     219            }
     220            height -= chop;
     221            continue;
     222        }
     223
     224        break;
     225    }
     226
     227    *prow = row;
     228    *pcol = col;
     229    *pwidth = width;
     230    *pheight = height;
     231    return 0;
     232}
     233
     234int
     235template_alloc(const unsigned int *scores, int width, int height,
     236        int minrow, int maxrow, int mincol, int maxcol, AVPicture *tmpl,
     237        int *ptmplrow, int *ptmplcol, int *ptmplwidth, int *ptmplheight,
     238        int debugLevel, QString debugdir)
     239{
     240    /*
     241     * TUNABLE:
     242     *
     243     * Higher values select for "stronger" pixels to be in the template, but
     244     * weak pixels might be missed.
     245     *
     246     * Lower values allow more pixels to be included as part of the template,
     247     * but strong non-template pixels might be included.
     248     */
     249    static const float      MINSCOREPCTILE = 0.998;
     250
     251    const int               nn = width * height;
     252    int                     ii, first, last;
     253    unsigned int            *sortedscores, threshscore;
     254    AVPicture               thresh;
     255
     256    if (avpicture_alloc(&thresh, PIX_FMT_GRAY8, width, height))
     257    {
     258        VERBOSE(VB_COMMFLAG, QString("template_alloc "
     259                "avpicture_alloc thresh (%1x%2) failed")
     260                .arg(width).arg(height));
     261        return -1;
     262    }
     263
     264    sortedscores = new unsigned int[nn];
     265    memcpy(sortedscores, scores, nn * sizeof(*sortedscores));
     266    qsort(sortedscores, nn, sizeof(*sortedscores), sort_ascending);
     267
     268    if (sortedscores[0] == sortedscores[nn - 1])
     269    {
     270        /* All pixels in the template area look the same; no template. */
     271        VERBOSE(VB_COMMFLAG, QString("template_alloc: pixels all identical!"));
     272        goto free_thresh;
     273    }
     274
     275    /* Threshold the edge frequences. */
     276
     277    ii = (int)roundf(nn * MINSCOREPCTILE);
     278    threshscore = sortedscores[ii];
     279    for (first = ii; first > 0 && sortedscores[first] == threshscore; first--)
     280        ;
     281    if (sortedscores[first] != threshscore)
     282        first++;
     283    for (last = ii; last < nn - 1 && sortedscores[last] == threshscore; last++)
     284        ;
     285    if (sortedscores[last] != threshscore)
     286        last--;
     287
     288    VERBOSE(VB_COMMFLAG, QString("template_alloc wanted %1, got %2-%3")
     289            .arg(MINSCOREPCTILE, 0, 'f', 6)
     290            .arg((float)first / nn, 0, 'f', 6)
     291            .arg((float)last / nn, 0, 'f', 6));
     292
     293    for (ii = 0; ii < nn; ii++)
     294        thresh.data[0][ii] = scores[ii] >= threshscore ? UCHAR_MAX : 0;
     295
     296    if (debugLevel >= 1)
     297    {
     298        QString convertfmt("convert -quality 50 -resize 192x144 %1 %2");
     299        QString base, pgmfile, jpgfile;
     300        QFile tfile;
     301
     302        base = debugdir + "/tf-edgecounts";
     303        pgmfile = base + ".pgm";
     304        jpgfile = base + ".jpg";
     305        if (pgm_write(thresh.data[0], width, height, pgmfile.ascii()))
     306            goto free_thresh;
     307        if (myth_system(convertfmt.arg(pgmfile).arg(jpgfile)))
     308            goto free_thresh;
     309        tfile.setName(pgmfile);
     310        if (!tfile.remove())
     311        {
     312            VERBOSE(VB_COMMFLAG, QString(
     313                        "template_alloc error removing %1 (%2)")
     314                    .arg(tfile.name()).arg(strerror(errno)));
     315            goto free_thresh;
     316        }
     317    }
     318
     319    /* Crop to a minimal bounding box. */
     320
     321    if (bounding_box(&thresh, minrow, maxrow, mincol, maxcol,
     322                ptmplrow, ptmplcol, ptmplwidth, ptmplheight))
     323        goto free_thresh;
     324
     325    if (*ptmplwidth * *ptmplheight > USHRT_MAX)
     326    {
     327        /* Max value of data type of TemplateMatcher::edgematch */
     328        VERBOSE(VB_COMMFLAG, QString(
     329                    "template_alloc bounding_box too big (%1x%2)")
     330                .arg(*ptmplwidth).arg(*ptmplheight));
     331        goto free_thresh;
     332    }
     333
     334    if (avpicture_alloc(tmpl, PIX_FMT_GRAY8, *ptmplwidth, *ptmplheight))
     335    {
     336        VERBOSE(VB_COMMFLAG, QString("template_alloc "
     337                "avpicture_alloc tmpl (%1x%2) failed")
     338                .arg(*ptmplwidth).arg(*ptmplheight));
     339        goto free_thresh;
     340    }
     341
     342    if (pgm_crop(tmpl, &thresh, height, *ptmplrow, *ptmplcol,
     343                *ptmplwidth, *ptmplheight))
     344        goto free_thresh;
     345
     346    delete []sortedscores;
     347    avpicture_free(&thresh);
     348    return 0;
     349
     350free_thresh:
     351    delete []sortedscores;
     352    avpicture_free(&thresh);
     353    return -1;
     354}
     355
     356int
     357analyzeFrameDebug(long long frameno, const AVPicture *pgm, int pgmheight,
     358        const AVPicture *cropped, const AVPicture *edges, int cropheight,
     359        int croprow, int cropcol, QString debugdir)
     360{
     361    static const int    delta = 24;
     362    static int          lastrow, lastcol, lastwidth, lastheight;
     363    const int           pgmwidth = pgm->linesize[0];
     364    const int           cropwidth = cropped->linesize[0];
     365    int                 rowsame, colsame, widthsame, heightsame;
     366
     367    rowsame = abs(lastrow - croprow) <= delta ? 1 : 0;
     368    colsame = abs(lastcol - cropcol) <= delta ? 1 : 0;
     369    widthsame = abs(lastwidth - cropwidth) <= delta ? 1 : 0;
     370    heightsame = abs(lastheight - cropheight) <= delta ? 1 : 0;
     371    if (frameno > 0 && rowsame + colsame + widthsame + heightsame >= 3)
     372        return 0;
     373
     374    VERBOSE(VB_COMMFLAG, QString("TemplateFinder Frame %1: %2x%3@(%4,%5)")
     375            .arg(frameno, 5)
     376            .arg(cropwidth).arg(cropheight)
     377            .arg(cropcol).arg(croprow));
     378    lastrow = croprow;
     379    lastcol = cropcol;
     380    lastwidth = cropwidth;
     381    lastheight = cropheight;
     382
     383    QString convertfmt("convert -quality 50 -resize 192x144 %1 %2");
     384    QString base, pgmfile, jpgfile;
     385    QFile tfile;
     386    base.sprintf("%s/tf-%05lld", debugdir.ascii(), frameno);
     387
     388    pgmfile = base + ".pgm";
     389    jpgfile = base + ".jpg";
     390    if (pgm_write(pgm->data[0], pgmwidth, pgmheight, pgmfile.ascii()))
     391        goto error;
     392    if (myth_system(convertfmt.arg(pgmfile).arg(jpgfile)))
     393        goto error;
     394    tfile.setName(pgmfile);
     395    if (!tfile.remove())
     396    {
     397        VERBOSE(VB_COMMFLAG, QString("TemplateFinder.analyzeFrameDebug"
     398                    " error removing %1 (%2)")
     399                .arg(tfile.name()).arg(strerror(errno)));
     400        goto error;
     401    }
     402
     403    pgmfile = base + "-cropped.pgm";
     404    jpgfile = base + "-cropped.jpg";
     405    if (pgm_write(cropped->data[0], cropwidth, cropheight, pgmfile.ascii()))
     406        goto error;
     407    if (myth_system(convertfmt.arg(pgmfile).arg(jpgfile)))
     408        goto error;
     409    tfile.setName(pgmfile);
     410    if (!tfile.remove())
     411    {
     412        VERBOSE(VB_COMMFLAG, QString("TemplateFinder.analyzeFrameDebug"
     413                    " error removing %1 (%2)")
     414                .arg(tfile.name()).arg(strerror(errno)));
     415        goto error;
     416    }
     417
     418    pgmfile = base + "-edges.pgm";
     419    jpgfile = base + "-edges.jpg";
     420    if (pgm_write(edges->data[0], cropwidth, cropheight, pgmfile.ascii()))
     421        goto error;
     422    if (myth_system(convertfmt.arg(pgmfile).arg(jpgfile)))
     423        goto error;
     424    tfile.setName(pgmfile);
     425    if (!tfile.remove())
     426    {
     427        VERBOSE(VB_COMMFLAG, QString("TemplateFinder.analyzeFrameDebug"
     428                    " error removing %1 (%2)")
     429                .arg(tfile.name()).arg(strerror(errno)));
     430        goto error;
     431    }
     432
     433    return 0;
     434
     435error:
     436    return -1;
     437}
     438
     439};  /* namespace */
     440
     441TemplateFinder::TemplateFinder(PGMConverter *pgmc, BorderDetector *bd,
     442        EdgeDetector *ed, NuppelVideoPlayer *nvp, int chanid,
     443        const QDateTime &recstartts_in, QString debugdir)
     444    : FrameAnalyzer()
     445    , pgmConverter(pgmc)
     446    , borderDetector(bd)
     447    , edgeDetector(ed)
     448    , nextFrame(0)
     449    , width(-1)
     450    , height(-1)
     451    , scores(NULL)
     452    , mincontentrow(INT_MAX)
     453    , maxcontentrow(INT_MIN)
     454    , mincontentcol(INT_MAX)
     455    , maxcontentcol(INT_MIN)
     456    , tmplrow(-1)
     457    , tmplcol(-1)
     458    , tmplwidth(-1)
     459    , tmplheight(-1)
     460    , cwidth(-1)
     461    , cheight(-1)
     462    , chanid(chanid)
     463    , recstartts(recstartts_in)
     464    , debugLevel(0)
     465    , debugdir(debugdir)
     466{
     467    const unsigned int  sampleSpacing = 5;      /* Sample every <n> seconds. */
     468    const float         fps = nvp->GetFrameRate();
     469
     470    /*
     471     * TUNABLE:
     472     *
     473     * The leading amount of time (in seconds) to sample frames for building up
     474     * the possible template, and the interval between frames for analysis.
     475     *
     476     * This is a simple time vs. accuracy trade off. Higher values will gain
     477     * more (possibly unneeded) accuracy at the cost of more processing time.
     478     */
     479    sampleTime = 20 * 60;   /* Sample first <n> minutes. */
     480
     481    if ((sampleTime / sampleSpacing) > UINT_MAX)
     482    {
     483        /* Max value of "scores" data type */
     484        sampleTime = UINT_MAX / sampleSpacing;
     485    }
     486
     487    frameInterval = (int)roundf(sampleSpacing * fps);
     488    endFrame = (long long)(sampleTime * fps);
     489
     490    VERBOSE(VB_COMMFLAG,
     491            QString("TemplateFinder: sampleTime=%1s, sampleSpacing=%2s")
     492            .arg(sampleTime).arg(sampleSpacing));
     493
     494    memset(&cropped, 0, sizeof(cropped));
     495    memset(&tmpl, 0, sizeof(tmpl));
     496
     497    /*
     498     * debugLevel:
     499     *      0: no debugging
     500     *      1: extra verbosity, dump frames into debugdir
     501     */
     502    debugLevel = gContext->GetNumSetting("TemplateFinderDebugLevel", 0);
     503    if (debugLevel > 0)
     504    {
     505        QDir qdir(debugdir);
     506        if (qdir.exists())
     507        {
     508            VERBOSE(VB_COMMFLAG, QString("Using debug directory \"%1\"")
     509                    .arg(debugdir));
     510        }
     511        else
     512        {
     513            if (qdir.mkdir(debugdir))
     514            {
     515                VERBOSE(VB_COMMFLAG, QString("Created debug directory \"%1\"")
     516                        .arg(debugdir));
     517            }
     518            else
     519            {
     520                VERBOSE(VB_COMMFLAG, QString("Failed to create \"%1\": %2")
     521                        .arg(debugdir).arg(strerror(errno)));
     522            }
     523        }
     524    }
     525}
     526
     527TemplateFinder::~TemplateFinder(void)
     528{
     529    if (scores)
     530        delete []scores;
     531    avpicture_free(&tmpl);
     532    avpicture_free(&cropped);
     533}
     534
     535int
     536TemplateFinder::extraBuffer(int preroll) const
     537{
     538    return max(0, preroll) + sampleTime;
     539}
     540
     541int
     542TemplateFinder::nuppelVideoPlayerInited(NuppelVideoPlayer *nvp)
     543{
     544    /*
     545     * Only detect edges in portions of the frame where we expect to find
     546     * a template. This serves two purposes:
     547     *
     548     *  - Speed: reduce search space.
     549     *  - Correctness (insofar as the assumption of template location is
     550     *    correct): don't "pollute" the set of candidate template edges with
     551     *    the "content" edges in the non-template portions of the frame.
     552     */
     553    MSqlQuery   query(MSqlQuery::InitCon());
     554    bool        tmplindb = false;
     555
     556    width = nvp->GetVideoWidth();
     557    height = nvp->GetVideoHeight();
     558
     559    query.prepare("SELECT basename"
     560            ", template"
     561            ", template_row"
     562            ", template_col"
     563            ", template_width"
     564            ", template_height"
     565            " FROM recorded"
     566            " WHERE chanid = :CHANID"
     567            "   AND starttime = :STARTTIME"
     568            ";");
     569    query.bindValue(":CHANID", chanid);
     570    query.bindValue(":STARTTIME", recstartts);
     571    query.exec();
     572    if (!query.isActive())
     573    {
     574        MythContext::DBError("Error in TemplateFinder::nuppelVideoPlayerInited",
     575                query);
     576        return -1;
     577    }
     578    else if (query.size() > 0 && query.next())
     579    {
     580        QString basename = query.value(0).toString();
     581        tmplfile = query.value(1).toString();
     582        tmplrow = query.value(2).toInt();
     583        tmplcol = query.value(3).toInt();
     584        tmplwidth = query.value(4).toInt();
     585        tmplheight = query.value(5).toInt();
     586        if (tmplfile.isEmpty())
     587        {
     588            QFileInfo fileinfo(basename);
     589            tmplfile = gContext->GetSetting("RecordFilePrefix") + "/" +
     590                fileinfo.baseName(TRUE) + "-template.pgm";
     591        }
     592        else
     593        {
     594            tmplfile = gContext->GetSetting("RecordFilePrefix") + "/" +
     595                tmplfile;
     596        }
     597        if (QFile::exists(tmplfile))
     598        {
     599            if (avpicture_alloc(&tmpl, PIX_FMT_GRAY8, tmplwidth, tmplheight))
     600            {
     601                VERBOSE(VB_COMMFLAG, QString(
     602                            "TemplateFinder::nuppelVideoPlayerInited "
     603                            "avpicture_alloc tmpl (%1x%2) failed")
     604                        .arg(tmplwidth).arg(tmplheight));
     605                return -1;
     606            }
     607
     608            if (pgm_read(tmpl.data[0], tmplwidth, tmplheight, tmplfile.ascii()))
     609            {
     610                avpicture_free(&tmpl);
     611            }
     612            else
     613            {
     614                endFrame = FINISHED;
     615                VERBOSE(VB_COMMFLAG, QString(
     616                            "TemplateFinder::nuppelVideoPlayerInited"
     617                            " %1x%2@(%3,%4) of %5x%6 (%7)")
     618                        .arg(tmplwidth).arg(tmplheight)
     619                        .arg(tmplcol).arg(tmplrow)
     620                        .arg(width).arg(height).arg(tmplfile));
     621                tmplindb = true;
     622            }
     623        }
     624    }
     625    else
     626    {
     627        VERBOSE(VB_COMMFLAG, "Error in SQL query");
     628        return -1;
     629    }
     630
     631    if (pgmConverter->nuppelVideoPlayerInited(nvp))
     632        goto free_tmpl;
     633
     634    if (!tmplindb)
     635    {
     636        VERBOSE(VB_COMMFLAG, QString("TemplateFinder::nuppelVideoPlayerInited"
     637                    " framesize %1x%2")
     638                .arg(width).arg(height));
     639        scores = new unsigned int[width * height];
     640    }
     641
     642    return 0;
     643
     644free_tmpl:
     645    avpicture_free(&tmpl);
     646    return -1;
     647}
     648
     649int
     650TemplateFinder::resetBuffers(int newwidth, int newheight)
     651{
     652    if (cwidth == newwidth && cheight == newheight)
     653        return 0;
     654
     655    avpicture_free(&cropped);
     656
     657    if (avpicture_alloc(&cropped, PIX_FMT_GRAY8, newwidth, newheight))
     658    {
     659        VERBOSE(VB_COMMFLAG, QString(
     660                    "TemplateFinder::resetBuffers "
     661                    "avpicture_alloc cropped (%1x%2) failed")
     662                .arg(newwidth).arg(newheight));
     663        return -1;
     664    }
     665
     666    cwidth = newwidth;
     667    cheight = newheight;
     668    return 0;
     669}
     670
     671enum FrameAnalyzer::analyzeFrameResult
     672TemplateFinder::analyzeFrame(const VideoFrame *frame, long long frameno,
     673        long long *pNextFrame)
     674{
     675    /*
     676     * TUNABLE:
     677     *
     678     * When looking for edges in frames, select some percentile of
     679     * squared-gradient magnitudes (intensities) as candidate edges. (This
     680     * number conventionally should not go any lower than the 95th percentile;
     681     * see edge_mark.)
     682     *
     683     * Higher values result in fewer edges; faint logos might not be picked up.
     684     * Lower values result in more edges; non-logo edges might be picked up.
     685     *
     686     * The TemplateFinder accumulates all its state in the "scores" array to
     687     * be processed later by TemplateFinder::finished.
     688     */
     689    const int           FRAMESGMPCTILE = 97;
     690
     691    const AVPicture     *pgm, *edges;
     692    int                 pgmwidth, pgmheight;
     693    int                 croprow, cropcol, cropwidth, cropheight;
     694
     695    if (frameno > endFrame)
     696        return ANALYZE_REMOVE;
     697
     698    if (frameno < nextFrame)
     699    {
     700        *pNextFrame = nextFrame;
     701        return ANALYZE_SKIP;
     702    }
     703
     704    nextFrame = frameno + frameInterval;
     705    *pNextFrame = min(endFrame, nextFrame);
     706
     707    if (frameno * 10 / endFrame != *pNextFrame * 10 / endFrame)
     708    {
     709        /* Log something every 10%. */
     710        VERBOSE(VB_COMMFLAG, QString(
     711                    "TemplateFinder::analyzeFrame %1 of %2 (%3%)")
     712                .arg(frameno).arg(endFrame).arg(*pNextFrame * 100 / endFrame));
     713    }
     714
     715    if (!(pgm = pgmConverter->getImage(frame, frameno, &pgmwidth, &pgmheight)))
     716        goto error;
     717
     718    if (borderDetector->getDimensions(pgm, pgmheight, frameno,
     719                &croprow, &cropcol, &cropwidth, &cropheight))
     720    {
     721        /* Blank frame. */
     722        return ANALYZE_OK;
     723    }
     724
     725    if (croprow < mincontentrow)
     726        mincontentrow = croprow;
     727    if (croprow + cropheight - 1 > maxcontentrow)
     728        maxcontentrow = croprow + cropheight - 1;
     729
     730    if (cropcol < mincontentcol)
     731        mincontentcol = cropcol;
     732    if (cropcol + cropwidth - 1 > maxcontentcol)
     733        maxcontentcol = cropcol + cropwidth - 1;
     734
     735    if (resetBuffers(cropwidth, cropheight))
     736        goto error;
     737
     738    if (pgm_crop(&cropped, pgm, pgmheight, croprow, cropcol,
     739                cropwidth, cropheight))
     740        goto error;
     741
     742    if (!(edges = edgeDetector->detectEdges(&cropped, cropheight,
     743                    FRAMESGMPCTILE)))
     744        goto error;
     745
     746    if (pgm_scorepixels(scores, pgmwidth, croprow, cropcol,
     747                edges, cropheight))
     748        goto error;
     749
     750    if (debugLevel >= 1)
     751    {
     752        if (analyzeFrameDebug(frameno, pgm, pgmheight, &cropped, edges,
     753                    cropheight, croprow, cropcol, debugdir))
     754            goto error;
     755    }
     756
     757    return ANALYZE_OK;
     758
     759error:
     760    VERBOSE(VB_COMMFLAG,
     761            QString("TemplateFinder::analyzeFrame error at frame %1")
     762            .arg(frameno));
     763    return ANALYZE_ERROR;
     764}
     765
     766int
     767TemplateFinder::finished(void)
     768{
     769    if (endFrame != FINISHED)
     770    {
     771        if (template_alloc(scores, width, height,
     772                    mincontentrow, maxcontentrow, mincontentcol, maxcontentcol,
     773                    &tmpl, &tmplrow, &tmplcol, &tmplwidth, &tmplheight,
     774                    debugLevel, debugdir))
     775            return -1;
     776
     777        if (pgm_write(tmpl.data[0], tmplwidth, tmplheight, tmplfile.ascii()))
     778            goto free_tmpl;
     779
     780        VERBOSE(VB_COMMFLAG, QString("Template %1x%2@(%3,%4) saved to %5")
     781                .arg(tmplwidth).arg(tmplheight).arg(tmplcol).arg(tmplrow)
     782                .arg(tmplfile));
     783
     784        MSqlQuery query(MSqlQuery::InitCon());
     785        query.prepare("UPDATE recorded"
     786                " SET template = :TEMPLATE"
     787                ", template_row = :TEMPLATE_ROW"
     788                ", template_col = :TEMPLATE_COL"
     789                ", template_width = :TEMPLATE_WIDTH"
     790                ", template_height = :TEMPLATE_HEIGHT"
     791                " WHERE chanid = :CHANID"
     792                "   AND starttime = :STARTTIME"
     793                ";");
     794        query.bindValue(":TEMPLATE", QFileInfo(tmplfile).fileName());
     795        query.bindValue(":TEMPLATE_ROW", tmplrow);
     796        query.bindValue(":TEMPLATE_COL", tmplcol);
     797        query.bindValue(":TEMPLATE_WIDTH", tmplwidth);
     798        query.bindValue(":TEMPLATE_HEIGHT", tmplheight);
     799        query.bindValue(":CHANID", chanid);
     800        query.bindValue(":STARTTIME", recstartts);
     801        query.exec();
     802        if (!query.isActive())
     803        {
     804            MythContext::DBError("Error in TemplateFinder::analyzeFrame",
     805                    query);
     806            goto free_tmpl;
     807        }
     808
     809        endFrame = FINISHED;
     810    }
     811
     812    return 0;
     813
     814free_tmpl:
     815    avpicture_free(&tmpl);
     816    return -1;
     817}
     818
     819const struct AVPicture *
     820TemplateFinder::getTemplate(int *prow, int *pcol, int *pwidth, int *pheight)
     821    const
     822{
     823    *prow = tmplrow;
     824    *pcol = tmplcol;
     825    *pwidth = tmplwidth;
     826    *pheight = tmplheight;
     827    return &tmpl;
     828}
     829
     830/* 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/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/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    virtual int nuppelVideoPlayerInited(NuppelVideoPlayer *nvp) {
     27        (void)nvp;
     28        return 0;
     29    };
     30
     31    /* Analyze a frame. */
     32    enum analyzeFrameResult {
     33        ANALYZE_FATAL = -4,     /* Don't use this analyzer anymore. */
     34        ANALYZE_REMOVE = -3,    /* Don't use this analyzer anymore. */
     35        ANALYZE_ERROR = -2,     /* Recoverable error */
     36        ANALYZE_SKIP = -1,      /* Skipped analysis of frame for some reason */
     37        ANALYZE_OK = 0,         /* Analysis OK */
     38    };
     39
     40    /*
     41     * Populate *pNextFrame with the next frame number to analyze, or -1 for
     42     * the next frame number.
     43     */
     44    static const long long NEXTFRAME = -1;
     45    static const long long ANYFRAME = LONG_LONG_MAX;
     46    virtual enum analyzeFrameResult analyzeFrame(const VideoFrame *frame,
     47            long long frameno, long long *pNextFrame) = 0;
     48
     49    virtual int finished(void) { return 0; }
     50
     51    virtual bool isContent(long long frameno) const {
     52        (void)frameno;
     53        return false;
     54    }
     55};
     56
     57#endif  /* !__FRAMEANALYZER_H__ */
     58
     59/* vim: set expandtab tabstop=4 shiftwidth=4: */
  • 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 = 16;
     42
     43    const int                   pgmwidth = pgm->linesize[0];
     44
     45    /*
     46     * TUNABLE:
     47     *
     48     * Margins to avoid noise at the extreme edges of the signal (VBI?).
     49     */
     50    static const int            VERTMARGIN = 8;
     51    static const int            HORIZMARGIN = 8;
     52
     53    /*
     54     * TUNABLE:
     55     *
     56     * Slop to accommodate any jagged letterboxing/pillarboxing edges.
     57     */
     58    static const int            VERTSLOP = 8;
     59    static const int            HORIZSLOP = 8;
     60
     61    unsigned char               minval, maxval, val;
     62    int                         rr, cc, minrow, maxrow, mincol, maxcol;
     63    int                         newrow, newcol, newwidth, newheight;
     64    bool                        top, bottom, left, right;
     65
     66    if (_frameno != UNCACHED && _frameno == frameno)
     67        goto done;
     68
     69    top = false;
     70    bottom = false;
     71    left = false;
     72    right = false;
     73
     74    minrow = VERTMARGIN;
     75    maxrow = pgmheight - VERTMARGIN - 1;
     76
     77    mincol = HORIZMARGIN;
     78    maxcol = pgmwidth - HORIZMARGIN - 1;
     79
     80    for (;;)
     81    {
     82        /* Find left edge. */
     83        minval = UCHAR_MAX;
     84        maxval = 0;
     85        for (cc = mincol; cc <= maxcol; cc++)
     86        {
     87            for (rr = minrow; rr <= maxrow; rr++)
     88            {
     89                val = pgm->data[0][rr * pgmwidth + cc];
     90                if (max(maxval, val) - min(minval, val) > MAXRANGE)
     91                {
     92                    if (cc != mincol)
     93                        left = true;
     94                    goto found_left;
     95                }
     96                if (val < minval)
     97                    minval = val;
     98                if (val > maxval)
     99                    maxval = val;
     100            }
     101        }
     102found_left:
     103        newcol = cc + HORIZSLOP;
     104        if (newcol > maxcol)
     105            goto blank_frame;
     106
     107        /* Find right edge (keep same minval/maxval as left edge). */
     108        for (cc = maxcol; cc > newcol; cc--)
     109        {
     110            for (rr = minrow; rr <= maxrow; rr++)
     111            {
     112                val = pgm->data[0][rr * pgmwidth + cc];
     113                if (max(maxval, val) - min(minval, val) > MAXRANGE)
     114                {
     115                    if (cc != maxcol)
     116                        right = true;
     117                    goto found_right;
     118                }
     119                if (val < minval)
     120                    minval = val;
     121                if (val > maxval)
     122                    maxval = val;
     123            }
     124        }
     125found_right:
     126        newwidth = cc - HORIZSLOP - newcol + 1;
     127
     128        if (newwidth <= 0)
     129            goto blank_frame;
     130
     131        if (top || bottom)
     132            break;  /* No need to repeat letterboxing check. */
     133
     134        /* Find top edge. */
     135        minval = UCHAR_MAX;
     136        maxval = 0;
     137        for (rr = minrow; rr <= maxrow; rr++)
     138        {
     139            for (cc = newcol; cc < newcol + newwidth; cc++)
     140            {
     141                val = pgm->data[0][rr * pgmwidth + cc];
     142                if (max(maxval, val) - min(minval, val) > MAXRANGE)
     143                {
     144                    if (rr != minrow)
     145                        top = true;
     146                    goto found_top;
     147                }
     148                if (val < minval)
     149                    minval = val;
     150                if (val > maxval)
     151                    maxval = val;
     152            }
     153        }
     154found_top:
     155        newrow = rr + VERTSLOP;
     156
     157        /* Find bottom edge (keep same minval/maxval as top edge). */
     158        for (rr = maxrow; rr > newrow; rr--)
     159        {
     160            for (cc = newcol; cc < newcol + newwidth; cc++)
     161            {
     162                val = pgm->data[0][rr * pgmwidth + cc];
     163                if (max(maxval, val) - min(minval, val) > MAXRANGE)
     164                {
     165                    if (rr != maxrow)
     166                        bottom = true;
     167                    goto found_bottom;
     168                }
     169                if (val < minval)
     170                    minval = val;
     171                if (val > maxval)
     172                    maxval = val;
     173            }
     174        }
     175found_bottom:
     176        newheight = rr - VERTSLOP - newrow + 1;
     177
     178        if (newheight <= 0)
     179            goto blank_frame;
     180
     181        if (left || right)
     182            break;  /* No need to repeat pillarboxing check. */
     183
     184        if (top || bottom)
     185        {
     186            /*
     187             * Letterboxing was found; repeat loop to look for embedded
     188             * pillarboxing.
     189             */
     190            minrow = newrow;
     191            maxrow = newrow + newheight - 1;
     192            continue;
     193        }
     194
     195        /* No pillarboxing or letterboxing. */
     196        break;
     197    }
     198
     199    if (debugLevel >= 1)
     200    {
     201        if (row != newrow || col != newcol ||
     202            width != newwidth || height != newheight)
     203        {
     204            VERBOSE(VB_COMMFLAG, QString("Frame %1: %2x%3@(%4,%5)")
     205                    .arg(_frameno, 5)
     206                    .arg(newwidth).arg(newheight)
     207                    .arg(newcol).arg(newrow));
     208        }
     209    }
     210
     211    frameno = _frameno;
     212    row = newrow;
     213    col = newcol;
     214    width = newwidth;
     215    height = newheight;
     216
     217done:
     218    *prow = row;
     219    *pcol = col;
     220    *pwidth = width;
     221    *pheight = height;
     222    return 0;
     223
     224blank_frame:
     225    if (debugLevel >= 1)
     226    {
     227        VERBOSE(VB_COMMFLAG, QString("Frame %1: %2x%3@(%4,%5): blank")
     228                .arg(_frameno, 5)
     229                .arg(maxcol - mincol + 1).arg(maxrow - minrow + 1)
     230                .arg(mincol).arg(minrow));
     231    }
     232    return -1;  /* Blank frame. */
     233}
     234
     235/* 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        NuppelVideoPlayer *nvp)
     60{
     61    FrameAnalyzer   *fa;
     62
     63    for (QPtrListIterator<FrameAnalyzer> iifa(*pass);
     64            (fa = iifa.current()); ++iifa)
     65    {
     66        int     error;
     67
     68        if ((error = fa->nuppelVideoPlayerInited(nvp)))
     69            return error;
     70    }
     71    return 0;
     72}
     73
     74int processFrame(QPtrList<FrameAnalyzer> *frameAnalyzers,
     75        const VideoFrame *frame, long long frameno, long long *pNextFrame)
     76{
     77    FrameAnalyzer                       *fa;
     78    FrameAnalyzer::analyzeFrameResult   ares;
     79    long long                           nextFrame, minNextFrame;
     80
     81    minNextFrame = FrameAnalyzer::ANYFRAME;
     82    for (QPtrListIterator<FrameAnalyzer> iifa(*frameAnalyzers);
     83            (fa = iifa.current()); ++iifa)
     84    {
     85        switch (ares = fa->analyzeFrame(frame, frameno, &nextFrame)) {
     86        default:
     87            minNextFrame = min(minNextFrame, nextFrame);
     88            break;
     89        case FrameAnalyzer::ANALYZE_REMOVE:
     90            (void)fa->finished();
     91            /* fall through */
     92        case FrameAnalyzer::ANALYZE_FATAL:
     93            frameAnalyzers->remove(fa);
     94            break;
     95        }
     96    }
     97
     98    if (minNextFrame == FrameAnalyzer::ANYFRAME)
     99        minNextFrame = FrameAnalyzer::NEXTFRAME;
     100    *pNextFrame = minNextFrame;
     101
     102    if (frameAnalyzers->isEmpty())
     103        return -1;
     104
     105    return 0;
     106}
     107
     108int passFinished(QPtrList<FrameAnalyzer> *frameAnalyzers)
     109{
     110    FrameAnalyzer       *fa;
     111
     112    for (QPtrListIterator<FrameAnalyzer> iifa(*frameAnalyzers);
     113            (fa = iifa.current()); ++iifa)
     114    {
     115        if (fa->finished())
     116            return -1;
     117    }
     118    return 0;
     119}
     120
     121bool isContent(QPtrList<FrameAnalyzer> *frameAnalyzers, long long frameno)
     122{
     123    /* Compute some function of all analyses on a given frame. */
     124    unsigned int    score;
     125    FrameAnalyzer   *fa;
     126
     127    score = 0;
     128    for (QPtrListIterator<FrameAnalyzer> iifa(*frameAnalyzers);
     129            (fa = iifa.current()); ++iifa)
     130    {
     131        if (fa->isContent(frameno))
     132            score++;
     133    }
     134    return score > 0;
     135}
     136
     137QString frameToTimestamp(long long frameno, float fps)
     138{
     139    int ms, ss, mm, hh;
     140
     141    ms = (int)roundf(frameno / fps * 1000);
     142
     143    ss = ms / 1000;
     144    ms %= 1000;
     145
     146    mm = ss / 60;
     147    ss %= 60;
     148
     149    hh = mm / 60;
     150    mm %= 60;
     151
     152    QString ts;
     153    return ts.sprintf("%d:%02d:%02d", hh, mm, ss);
     154}
     155
     156};  // namespace
     157
     158QString debugDirectory(int chanid, const QDateTime& recstartts)
     159{
     160    /*
     161     * Should deleting a recording also delete its associated debug directory?
     162     */
     163    MSqlQuery query(MSqlQuery::InitCon());
     164    query.prepare("SELECT basename"
     165            " FROM recorded"
     166            " WHERE chanid = :CHANID"
     167            "   AND starttime = :STARTTIME"
     168            ";");
     169    query.bindValue(":CHANID", chanid);
     170    query.bindValue(":STARTTIME", recstartts);
     171    query.exec();
     172    if (query.size() <= 0 || !query.next())
     173    {
     174        MythContext::DBError("Error in CommDetector2::CommDetector2",
     175                query);
     176        return "";
     177    }
     178
     179    QString recordfileprefix = gContext->GetSetting("RecordFilePrefix");
     180    QString basename = query.value(0).toString();
     181    QFileInfo baseinfo(recordfileprefix + "/" + basename);
     182    QString debugdir = recordfileprefix + "/" + baseinfo.baseName(TRUE) +
     183        "-debug";
     184
     185    return debugdir;
     186}
     187
     188CommDetector2::CommDetector2(enum SkipTypes commDetectMethod_in,
     189        bool showProgress_in, bool fullSpeed_in, NuppelVideoPlayer* nvp_in,
     190        int chanid, const QDateTime& startts_in, const QDateTime& endts_in,
     191        const QDateTime& recstartts_in, const QDateTime& recendts_in)
     192    : commDetectMethod((enum SkipTypes)(commDetectMethod_in & ~COMM_DETECT_2))
     193    , showProgress(showProgress_in)
     194    , fullSpeed(fullSpeed_in)
     195    , nvp(nvp_in)
     196    , startts(startts_in)
     197    , endts(endts_in)
     198    , recstartts(recstartts_in)
     199    , recendts(recendts_in)
     200    , isRecording(QDateTime::currentDateTime() < recendts)
     201    , debugdir("")
     202{
     203    QPtrList<FrameAnalyzer> *pass0, *pass1;
     204
     205    debugdir = debugDirectory(chanid, recstartts);
     206
     207    pass0 = NULL;
     208    pass1 = NULL;
     209
     210    /*
     211     * Logo Detection requires two passes. The first pass looks for static
     212     * areas of the screen to look for logos and generate a template. The
     213     * second pass matches the template against the frame and computes the
     214     * closeness of the match.
     215     */
     216    if ((commDetectMethod & COMM_DETECT_2_LOGO))
     217    {
     218        PGMConverter            *pgmConverter = NULL;
     219        BorderDetector          *borderDetector = NULL;
     220        CannyEdgeDetector       *cannyEdgeDetector = NULL;
     221        TemplateFinder          *logoFinder = NULL;
     222        TemplateMatcher         *logoMatcher = NULL;
     223
     224        if (!pass0)
     225            pass0 = new QPtrList<FrameAnalyzer>;
     226        if (!pass1)
     227            pass1 = new QPtrList<FrameAnalyzer>;
     228
     229        if (!pgmConverter)
     230            pgmConverter = new PGMConverter();
     231
     232        if (!borderDetector)
     233            borderDetector = new BorderDetector();
     234
     235        if (!cannyEdgeDetector)
     236            cannyEdgeDetector = new CannyEdgeDetector();
     237
     238        if (!logoFinder)
     239        {
     240            logoFinder = new TemplateFinder(pgmConverter, borderDetector,
     241                    cannyEdgeDetector, nvp, chanid, recstartts, debugdir);
     242            pass0->append(logoFinder);
     243        }
     244
     245        if (!logoMatcher)
     246        {
     247            logoMatcher = new TemplateMatcher(pgmConverter, cannyEdgeDetector,
     248                    logoFinder, debugdir);
     249            pass1->append(logoMatcher);
     250        }
     251    }
     252
     253    /* Aggregate them all together. */
     254    if (pass0)
     255        frameAnalyzers.append(pass0);
     256    if (pass1)
     257        frameAnalyzers.append(pass1);
     258}
     259
     260int CommDetector2::buildBuffer(int minbuffer)
     261{
     262    bool                    wereRecording = isRecording;
     263    int                     buffer = minbuffer;
     264    int                     maxExtra = 0;
     265    int                     preroll = recstartts.secsTo(startts);
     266    QPtrList<FrameAnalyzer> *pass;
     267
     268    for (QPtrListIterator<QPtrList<FrameAnalyzer> > iipass(frameAnalyzers);
     269            (pass = iipass.current()); ++iipass)
     270    {
     271        FrameAnalyzer   *fa;
     272
     273        if (pass->isEmpty())
     274            continue;
     275
     276        for (QPtrListIterator<FrameAnalyzer> iifa(*pass);
     277                (fa = iifa.current()); ++iifa)
     278        {
     279            maxExtra = max(maxExtra, fa->extraBuffer(preroll));
     280        }
     281
     282        buffer += maxExtra;
     283    }
     284
     285    emit statusUpdate("Building Detection Buffer");
     286
     287    int reclen;
     288    while (isRecording && (reclen = recstartts.secsTo(
     289        QDateTime::currentDateTime())) < buffer)
     290    {
     291        emit breathe();
     292        if (m_bStop)
     293            return -1;
     294        sleep(2);
     295    }
     296
     297    // Don't bother flagging short ~realtime recordings
     298    if (wereRecording && !isRecording && reclen < buffer)
     299        return -1;
     300
     301    return 0;
     302}
     303
     304void CommDetector2::reportState(int elapsed_sec, long long frameno,
     305        long long nframes, unsigned int passno, unsigned int npasses)
     306{
     307    float fps = elapsed_sec ? (float)frameno / elapsed_sec : 0;
     308
     309    /* Assume that 0-th pass is negligible in terms of computational cost. */
     310    int percentage = passno == 0 ? 0 :
     311        (passno - 1) * 100 / (npasses - 1) +
     312        min((long long)100, (frameno * 100 / nframes) / (npasses - 1));
     313
     314    if (showProgress)
     315    {
     316        if (nframes)
     317        {
     318            cerr << "\b\b\b\b\b\b\b\b\b\b\b"
     319                 << QString::number(percentage).rightJustify(3, ' ')
     320                 << "%/"
     321                 << QString::number((int)fps).rightJustify(3, ' ')
     322                 << "fps";
     323        }
     324        else
     325        {
     326            cerr << "\b\b\b\b\b\b\b\b\b\b\b\b\b"
     327                 << QString::number(frameno).rightJustify(6, ' ')
     328                 << "/"
     329                 << QString::number((int)fps).rightJustify(3, ' ')
     330                 << "fps";
     331        }
     332        cerr.flush();
     333    }
     334
     335    if (nframes)
     336    {
     337        emit statusUpdate(QObject::tr("%1% Completed @ %2 fps.")
     338                .arg(percentage).arg(fps));
     339    }
     340    else
     341    {
     342        emit statusUpdate(QObject::tr("%1 Frames Completed @ %2 fps.")
     343                .arg(frameno).arg(fps));
     344    }
     345}
     346
     347bool CommDetector2::go(void)
     348{
     349    int minlag = 7; // seconds
     350
     351    nvp->SetNullVideo();
     352
     353    if (buildBuffer(minlag) < 0)
     354        return false;
     355
     356    if (nvp->OpenFile() < 0)
     357        return false;
     358
     359    if (!nvp->InitVideo())
     360    {
     361        VERBOSE(VB_IMPORTANT,
     362                "NVP: Unable to initialize video for FlagCommercials.");
     363        return false;
     364    }
     365
     366    nvp->SetCaptionsEnabled(false);
     367
     368    QTime flagTime;
     369    flagTime.start();
     370   
     371    long long myTotalFrames = recendts < QDateTime::currentDateTime() ?
     372        nvp->GetTotalFrameCount() :
     373        (long long)(nvp->GetFrameRate() * recstartts.secsTo(recendts));
     374
     375    if (showProgress)
     376    {
     377        if (myTotalFrames)
     378            cerr << "  0%/      ";
     379        else
     380            cerr << "     0/      ";
     381        cerr.flush();
     382    }
     383
     384    emit breathe();
     385
     386    unsigned int passno = 0;
     387    unsigned int npasses = frameAnalyzers.count();
     388    long long nframes = nvp->GetTotalFrameCount();
     389    QPtrList<FrameAnalyzer> *pass;
     390    for (QPtrListIterator<QPtrList<FrameAnalyzer> > iipass(frameAnalyzers);
     391            (pass = iipass.current()); ++iipass, passno++)
     392    {
     393        VERBOSE(VB_COMMFLAG, QString(
     394                    "CommDetector2::go pass %1 of %2 (%3 frames)")
     395                .arg(passno + 1).arg(npasses)
     396                .arg(nvp->GetTotalFrameCount()));
     397
     398        if (nuppelVideoPlayerInited(pass, nvp))
     399            return false;
     400
     401        /*
     402         * Each pass starts at beginning. VideoFrame.frameNumber appears to be
     403         * a cumulative counter of total frames "played" rather than an actual
     404         * index into the file, so maintain the frame index in
     405         * currentFrameNumber.
     406         */
     407        nvp->DiscardVideoFrame(nvp->GetRawVideoFrame(0));
     408        long long nextFrame = -1;
     409        long long currentFrameNumber = 0;
     410
     411        while (currentFrameNumber < nframes && !nvp->GetEof())
     412        {
     413            struct timeval startTime;
     414
     415            if (isRecording)
     416                (void)gettimeofday(&startTime, NULL);
     417
     418            VideoFrame *currentFrame = nvp->GetRawVideoFrame(nextFrame);
     419
     420            if (stopForBreath(isRecording, currentFrameNumber))
     421            {
     422                emit breathe();
     423                if (m_bStop)
     424                {
     425                    nvp->DiscardVideoFrame(currentFrame);
     426                    return false;
     427                }
     428            }
     429
     430            while (m_bPaused)
     431            {
     432                emit breathe();
     433                sleep(1);
     434            }
     435
     436            // sleep a little so we don't use all cpu even if we're niced
     437            if (!fullSpeed && !isRecording)
     438                usleep(10000);  // 10ms
     439
     440            if (needToReportState(showProgress, isRecording,
     441                        currentFrameNumber))
     442            {
     443                reportState(flagTime.elapsed() / 1000, currentFrameNumber,
     444                        myTotalFrames, passno, npasses);
     445            }
     446
     447            if (processFrame(pass, currentFrame, currentFrameNumber,
     448                        &nextFrame))
     449            {
     450                nvp->DiscardVideoFrame(currentFrame);
     451                break;
     452            }
     453
     454            if (isRecording)
     455            {
     456                waitForBuffer(&startTime, minlag,
     457                        recstartts.secsTo(QDateTime::currentDateTime()) -
     458                        flagTime.elapsed() / 1000, nvp->GetFrameRate(),
     459                        fullSpeed);
     460            }
     461
     462            nvp->DiscardVideoFrame(currentFrame);
     463
     464            if (nextFrame == FrameAnalyzer::NEXTFRAME)
     465                currentFrameNumber++;
     466            else
     467                currentFrameNumber = nextFrame;
     468        }
     469
     470        if (passFinished(pass))
     471            return false;
     472    }
     473
     474    if (showProgress)
     475    {
     476        if (myTotalFrames)
     477            cerr << "\b\b\b\b\b\b      \b\b\b\b\b\b";
     478        else
     479            cerr << "\b\b\b\b\b\b\b\b\b\b\b\b\b             "
     480                    "\b\b\b\b\b\b\b\b\b\b\b\b\b";
     481        cerr.flush();
     482    }
     483
     484    return true;
     485}
     486
     487void CommDetector2::getCommercialBreakList(QMap<long long, int> &marks)
     488{
     489    /*
     490     * As a matter of record, the TemplateMatcher is tuned to yield false
     491     * positives (non-commercials marked as commercials); false negatives
     492     * (commercials marked as non-commercials) are expected to be rare.
     493     */
     494    const long long                 nframes = nvp->GetTotalFrameCount();
     495    const float                     fps = nvp->GetFrameRate();
     496    unsigned int                    passno;
     497    bool                            prevContent, thisContent;
     498    QPtrList<FrameAnalyzer>         *pass, *pass1;
     499    QMap<long long, int>::Iterator  iimark;
     500
     501    marks.clear();
     502
     503    /* XXX: Pass #1 (0-based) contains the image analyses. */
     504
     505    passno = 0;
     506    pass1 = NULL;
     507    for (QPtrListIterator<QPtrList<FrameAnalyzer> > iipass(frameAnalyzers);
     508            (pass = iipass.current()); ++iipass, passno++)
     509    {
     510        if (passno == 1)
     511        {
     512            pass1 = pass;
     513            break;
     514        }
     515    }
     516
     517    if (!pass1)
     518        return;
     519
     520    /* Create break list. */
     521
     522    if (!(prevContent = isContent(pass1, 0)))
     523        marks[0] = MARK_COMM_START;
     524    for (long long frameno = 1; frameno < nframes; frameno++)
     525    {
     526        if ((thisContent = isContent(pass1, frameno)) == prevContent)
     527            continue;
     528
     529        if (!thisContent)
     530        {
     531            marks[frameno] = MARK_COMM_START;
     532        }
     533        else
     534        {
     535            if ((iimark = marks.find(frameno - 1)) == marks.end())
     536                marks[frameno - 1] = MARK_COMM_END;
     537            else
     538                marks.remove(iimark);
     539        }
     540        prevContent = thisContent;
     541    }
     542    if (!prevContent)
     543        marks[nframes] = MARK_COMM_END;
     544
     545    /* Report results. */
     546
     547    for (iimark = marks.begin(); iimark != marks.end(); ++iimark)
     548    {
     549        long long   markstart, markend;
     550
     551        markstart = iimark.key();   /* MARK_COMM_BEGIN */
     552        ++iimark;                   /* MARK_COMM_END */
     553
     554        markend = iimark.key();
     555
     556        VERBOSE(VB_COMMFLAG, QString("Break: frame %1-%2 (%3, %4)")
     557                .arg(markstart, 6).arg(markend, 6)
     558                .arg(frameToTimestamp(markstart, fps))
     559                .arg(frameToTimestamp(markend - markstart + 1, fps)));
     560    }
     561}
     562
     563void CommDetector2::recordingFinished(long long totalFileSize)
     564{
     565    CommDetectorBase::recordingFinished(totalFileSize);
     566    isRecording = false;
     567}
     568
     569/* vim: set expandtab tabstop=4 shiftwidth=4: */
  • programs/mythcommflag/TemplateFinder.h

     
     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    int 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    static const int    FINISHED = -1;
     50
     51    int resetBuffers(int newcwidth, int newcheight);
     52
     53    PGMConverter    *pgmConverter;
     54    BorderDetector  *borderDetector;
     55    EdgeDetector    *edgeDetector;
     56
     57    unsigned int    sampleTime;         /* amount of time to analyze */
     58    unsigned int    sampleSpacing;      /* seconds between frames */
     59    int             frameInterval;      /* analyze every <Interval> frames */
     60    long long       endFrame;           /* end of logo detection */
     61    long long       nextFrame;          /* next desired frame */
     62
     63    int             width, height;      /* dimensions of frames */
     64    unsigned int    *scores;            /* pixel "edge" scores */
     65
     66    int             mincontentrow;      /* limits of content area of images */
     67    int             maxcontentrow;
     68    int             mincontentcol;
     69    int             maxcontentcol;
     70
     71    AVPicture       tmpl;               /* logo-matching template */
     72    int             tmplrow, tmplcol;
     73    int             tmplwidth, tmplheight;
     74
     75    AVPicture       cropped;            /* cropped version of frame */
     76    int             cwidth, cheight;    /* cropped height */
     77
     78    QString         tmplfile;           /* saved logo template */
     79    int             chanid;             /* recorded table lookup keys */
     80    QDateTime       recstartts;
     81
     82    /* Debugging. */
     83    int             debugLevel;
     84    QString         debugdir;
     85};
     86
     87#endif  /* !__TEMPLATEFINDER_H__ */
     88
     89/* 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
     303     *      2: dump frames into debugdir
     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("Using debug directory \"%1\"")
     312                    .arg(debugdir));
     313        }
     314        else
     315        {
     316            if (qdir.mkdir(debugdir))
     317            {
     318                VERBOSE(VB_COMMFLAG, QString("Created debug directory \"%1\"")
     319                        .arg(debugdir));
     320            }
     321            else
     322            {
     323                VERBOSE(VB_COMMFLAG, QString("Failed to create \"%1\": %2")
     324                        .arg(debugdir).arg(strerror(errno)));
     325            }
     326        }
     327    }
     328}
     329
     330TemplateMatcher::~TemplateMatcher(void)
     331{
     332    avpicture_free(&overlay);
     333
     334    if (matches)
     335        delete []matches;
     336    if (match)
     337        delete []match;
     338    avpicture_free(&cropped);
     339}
     340
     341int
     342TemplateMatcher::nuppelVideoPlayerInited(NuppelVideoPlayer *_nvp)
     343{
     344    /*
     345     * TUNABLE:
     346     *
     347     * Percent of template's edges that must be covered by candidate frame's
     348     * test area to be considered a match.
     349     *
     350     * Higher values have tighter matching requirements, but can cause
     351     * legitimate template-matching frames to be passed over (for example, if
     352     * the template is fading into or out of existence in a sequence of
     353     * frames).
     354     *
     355     * Lower values relax matching requirements, but can yield false
     356     * identification of template-matching frames when the scene just happens
     357     * to have lots of edges in the same region of the frame.
     358     */
     359    const float     MINMATCHPCT = 0.559670;
     360
     361    const int       width = _nvp->GetVideoWidth();
     362    const int       height = _nvp->GetVideoHeight();
     363
     364    int             tmpledges;
     365
     366    nvp = _nvp;
     367    nframes = nvp->GetTotalFrameCount();
     368    fps = nvp->GetFrameRate();
     369
     370    tmpl = templateFinder->getTemplate(&tmplrow, &tmplcol,
     371            &tmplwidth, &tmplheight);
     372    tmpledges = pgm_set(tmpl, tmplheight);
     373    mintmpledges = (int)roundf(tmpledges * MINMATCHPCT);
     374
     375    VERBOSE(VB_COMMFLAG, QString("TemplateMatcher::nuppelVideoPlayerInited "
     376                "%1x%2@(%3,%4), %5 edge pixels (want %6)")
     377            .arg(tmplwidth).arg(tmplheight).arg(tmplcol).arg(tmplrow)
     378            .arg(tmpledges).arg(mintmpledges));
     379
     380    if (debugLevel >= 2)
     381    {
     382        if (avpicture_alloc(&overlay, PIX_FMT_GRAY8, width, height))
     383        {
     384            VERBOSE(VB_COMMFLAG, QString(
     385                        "TemplateMatcher::nuppelVideoPlayerInited "
     386                    "avpicture_alloc overlay (%1x%2) failed")
     387                    .arg(width).arg(height));
     388            return -1;
     389        }
     390    }
     391
     392    if (avpicture_alloc(&cropped, PIX_FMT_GRAY8, tmplwidth, tmplheight))
     393    {
     394        VERBOSE(VB_COMMFLAG, QString("TemplateMatcher::nuppelVideoPlayerInited "
     395                "avpicture_alloc cropped (%1x%2) failed").
     396                arg(tmplwidth).arg(tmplheight));
     397        goto free_overlay;
     398    }
     399
     400    if (pgmConverter->nuppelVideoPlayerInited(nvp))
     401        goto free_cropped;
     402
     403    matches = new unsigned short[nframes];
     404    memset(matches, 0, nframes * sizeof(*matches));
     405
     406    match = new unsigned char[nframes];
     407
     408    if (debugLevel >= 1)
     409    {
     410        if (readMatches(debugdata, matches))
     411        {
     412            VERBOSE(VB_COMMFLAG, QString(
     413                        "TemplateMatcher::nuppelVideoPlayerInited read %1")
     414                    .arg(debugdata));
     415            matches_done = true;
     416        }
     417    }
     418
     419    return 0;
     420
     421free_cropped:
     422    avpicture_free(&cropped);
     423free_overlay:
     424    avpicture_free(&overlay);
     425    return -1;
     426}
     427
     428enum FrameAnalyzer::analyzeFrameResult
     429TemplateMatcher::analyzeFrame(const VideoFrame *frame, long long frameno,
     430        long long *pNextFrame)
     431{
     432    /*
     433     * TUNABLE:
     434     *
     435     * The matching area should be a lot smaller than the area used by
     436     * TemplateFinder, so use a smaller percentile than the TemplateFinder
     437     * (intensity requirements to be considered an "edge" are lower, because
     438     * there should be less pollution from non-template edges).
     439     *
     440     * Higher values mean fewer edge pixels in the candidate template area;
     441     * occluded or faint templates might be missed.
     442     *
     443     * Lower values mean more edge pixels in the candidate template area;
     444     * non-template edges can be picked up and cause false identification of
     445     * matches.
     446     */
     447    const int           FRAMESGMPCTILE = 70;    /* sync with finishedDebug */
     448
     449    /*
     450     * TUNABLE:
     451     *
     452     * The per-pixel search radius for matching the template. Meant to deal
     453     * with template edges that might minimally slide around (such as for
     454     * animated lighting effects).
     455     *
     456     * Higher values will pick up more pixels as matching the template
     457     * (possibly false matches).
     458     *
     459     * Lower values will require more exact template matches, possibly missing
     460     * true matches.
     461     *
     462     * The TemplateMatcher accumulates all its state in the "matches" array to
     463     * be processed later by TemplateMatcher::finished.
     464     */
     465    const int           JITTER_RADIUS = 0;
     466
     467    const AVPicture     *pgm;
     468    const AVPicture     *edges;
     469    int                 pgmwidth, pgmheight;
     470
     471    if (matches_done)
     472    {
     473        *pNextFrame = nframes;
     474        return ANALYZE_OK;
     475    }
     476
     477    *pNextFrame = NEXTFRAME;
     478
     479    if (frameno * 10 / nframes != (frameno + 1) * 10 / nframes)
     480    {
     481        /* Log something every 10%. */
     482        VERBOSE(VB_COMMFLAG, QString(
     483                    "TemplateMatcher::analyzeFrame %1 of %2 (%3%)")
     484                .arg(frameno).arg(nframes).arg((frameno + 1) * 100 / nframes));
     485    }
     486
     487    if (!(pgm = pgmConverter->getImage(frame, frameno, &pgmwidth, &pgmheight)))
     488        goto error;
     489
     490    if (pgm_crop(&cropped, pgm, pgmheight, tmplrow, tmplcol,
     491                tmplwidth, tmplheight))
     492        goto error;
     493
     494    if (!(edges = edgeDetector->detectEdges(&cropped, tmplheight,
     495                    FRAMESGMPCTILE)))
     496        goto error;
     497
     498    if (pgm_match(tmpl, edges, tmplheight, JITTER_RADIUS, &matches[frameno]))
     499        goto error;
     500
     501    return ANALYZE_OK;
     502
     503error:
     504    VERBOSE(VB_COMMFLAG, QString(
     505                "TemplateMatcher::analyzeFrame error at frame %1 of %2")
     506            .arg(frameno).arg(nframes));
     507    return ANALYZE_ERROR;
     508}
     509
     510int
     511TemplateMatcher::finished(void)
     512{
     513    /*
     514     * TUNABLE:
     515     *
     516     * Eliminate false negatives and false positives by eliminating segments
     517     * shorter than these periods.
     518     *
     519     * Higher values could eliminate real breaks entirely.
     520     * Lower values can yield more false "short" breaks.
     521     */
     522    const int       MINBREAKLEN = (int)roundf(15 * fps);  /* seconds */
     523    const int       MINSEGLEN = (int)roundf(15 * fps);    /* seconds */
     524
     525    int             ii, seg1len;
     526    long long       seg1b, seg1e;
     527
     528    if (!matches_done)
     529    {
     530        if (writeMatches(debugdata, matches, nframes))
     531        {
     532            VERBOSE(VB_COMMFLAG, QString("TemplateMatcher::finished wrote %1")
     533                    .arg(debugdata));
     534        }
     535    }
     536
     537    for (ii = 0; ii < nframes; ii++)
     538        match[ii] = matches[ii] >= mintmpledges ? 1 : 0;
     539
     540    if (debugLevel >= 1)
     541    {
     542        if (finishedDebug(pgmConverter, edgeDetector, nvp,
     543                    nframes, match, matches, &overlay, &cropped,
     544                    tmplrow, tmplcol, tmplwidth, tmplheight,
     545                    debugLevel, debugdir))
     546            goto error;
     547    }
     548
     549    /*
     550     * Cut out false positives (breaks shorter than some threshold amount of
     551     * time).
     552     */
     553    seg1b = 0;
     554    while (seg1b < nframes)
     555    {
     556        /* Find break. */
     557        if (match[seg1b])
     558            seg1b = matchspn(nframes, match, seg1b, match[seg1b]);
     559
     560        seg1e = matchspn(nframes, match, seg1b, match[seg1b]);
     561        seg1len = seg1e - seg1b;
     562
     563        /* Eliminate break. */
     564        if (seg1len < MINBREAKLEN)
     565            memset(&match[seg1b], 1, seg1len);
     566
     567        seg1b = seg1e;
     568    }
     569
     570    /*
     571     * Cut out false negatives (show segments shorter than some threshold
     572     * amount of time).
     573     */
     574    seg1b = 0;
     575    while (seg1b < nframes)
     576    {
     577        /* Find segment. */
     578        if (!match[seg1b])
     579            seg1b = matchspn(nframes, match, seg1b, match[seg1b]);
     580
     581        seg1e = matchspn(nframes, match, seg1b, match[seg1b]);
     582        seg1len = seg1e - seg1b;
     583
     584        /* Eliminate segment. */
     585        if (seg1len < MINSEGLEN)
     586            memset(&match[seg1b], 0, seg1len);
     587
     588        seg1b = seg1e;
     589    }
     590    return 0;
     591
     592error:
     593    return -1;
     594}
     595
     596/* 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    int 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/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/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/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:
     
    13371369    QString filename = ds->filename;
    13381370    bool followLinks = gContext->GetNumSetting("DeletesFollowLinks", 0);
    13391371
    1340     VERBOSE(VB_FILE, QString("About to unlink/delete file: %1").arg(filename));
    1341     if (followLinks)
    1342     {
    1343         QFileInfo finfo(filename);
    1344         if (finfo.isSymLink() && (err = unlink(finfo.readLink().local8Bit())))
    1345         {
    1346             VERBOSE(VB_IMPORTANT, QString("Error deleting '%1' @ '%2', %3")
    1347                     .arg(filename).arg(finfo.readLink().local8Bit())
    1348                     .arg(strerror(errno)));
    1349         }
    1350     }
    1351     if ((err = unlink(filename.local8Bit())))
    1352         VERBOSE(VB_IMPORTANT, QString("Error deleting '%1', %2")
    1353                 .arg(filename).arg(strerror(errno)));
     1372    /* Delete recording. */
     1373    err = deleteFile(filename, followLinks);
    13541374   
    13551375    sleep(2);
    13561376
     
    13731393        return;
    13741394    }
    13751395
     1396    /* Delete preview thumbnail. */
    13761397    filename = ds->filename + ".png";
    1377     if (followLinks)
    1378     {
    1379         QFileInfo finfo(filename);
    1380         if (finfo.isSymLink() && (err = unlink(finfo.readLink().local8Bit())))
    1381         {
    1382             VERBOSE(VB_IMPORTANT, QString("Error deleting '%1' @ '%2', %3")
    1383                     .arg(filename).arg(finfo.readLink().local8Bit())
    1384                     .arg(strerror(errno)));
    1385         }
    1386     }
     1398    err = deleteFile(filename, followLinks);
    13871399
    1388     checkFile.setName(filename);
    1389     if (checkFile.exists() && (err = unlink(filename.local8Bit())))
    1390         VERBOSE(VB_IMPORTANT, QString("Error deleting '%1', %2")
    1391                 .arg(filename).arg(strerror(errno)));
     1400    /* Delete commflag template. */
     1401    QFileInfo fi(ds->filename);
     1402    filename = fi.dirPath(TRUE) + "/" + fi.baseName(TRUE) + "-template.pgm";
     1403    err = deleteFile(filename, followLinks);
    13921404
    13931405    MSqlQuery query(MSqlQuery::InitCon());
    13941406    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            msg = QString("Expiring %1 \"%2\" from %3, %4 MBytes")
     422                .arg((*it)->title).arg((*it)->subtitle)
     423                .arg((*it)->startts.toString())
    423424                .arg((int)((*it)->filesize/1024/1024));
    424425
    425426            if (deleteAll)