diff -NpuBr -Xexcludes.txt asLoadedMPlayer/command.c MPlayerWithTranslogo/command.c --- asLoadedMPlayer/command.c 2009-07-01 14:15:09.000000000 +1000 +++ MPlayerWithTranslogo/command.c 2009-07-02 12:50:17.000000000 +1000 @@ -3195,6 +3195,12 @@ int run_command(MPContext * mpctx, mp_cm #endif + case MP_CMD_LOGO_CONTROL: + ((vf_instance_t *) sh_video->vfilter)-> + control(sh_video->vfilter, VFCTRL_LOGO_CONTROL, &cmd->args[0].v.i); + break; + + default: #ifdef CONFIG_GUI if ((use_gui) && (cmd->id > MP_CMD_GUI_EVENTS)) diff -NpuBr -Xexcludes.txt asLoadedMPlayer/etc/input.conf MPlayerWithTranslogo/etc/input.conf --- asLoadedMPlayer/etc/input.conf 2009-07-01 14:15:09.000000000 +1000 +++ MPlayerWithTranslogo/etc/input.conf 2009-07-02 11:44:39.000000000 +1000 @@ -77,6 +77,8 @@ l tv_step_channel -1 n tv_step_norm b tv_step_chanlist +L logo_control # Allows repositioning of logo removed by Translogo + ## ## GUI ## diff -NpuBr -Xexcludes.txt asLoadedMPlayer/input/input.c MPlayerWithTranslogo/input/input.c --- asLoadedMPlayer/input/input.c 2009-07-01 14:15:03.000000000 +1000 +++ MPlayerWithTranslogo/input/input.c 2009-07-02 12:37:26.000000000 +1000 @@ -166,6 +166,7 @@ static const mp_cmd_t mp_cmds[] = { { MP_CMD_VO_ROOTWIN, "vo_rootwin", 0, { {MP_CMD_ARG_INT,{-1}}, {-1,{0}} } }, { MP_CMD_VO_BORDER, "vo_border", 0, { {MP_CMD_ARG_INT,{-1}}, {-1,{0}} } }, { MP_CMD_SCREENSHOT, "screenshot", 0, { {MP_CMD_ARG_INT,{0}}, {-1,{0}} } }, + { MP_CMD_LOGO_CONTROL, "logo_control", 0, { {MP_CMD_ARG_INT,{0}}, {-1,{0}} } }, { MP_CMD_PANSCAN, "panscan",1, { {MP_CMD_ARG_FLOAT,{0}}, {MP_CMD_ARG_INT,{0}}, {-1,{0}} } }, { MP_CMD_SWITCH_VSYNC, "switch_vsync", 0, { {MP_CMD_ARG_INT,{0}}, {-1,{0}} } }, { MP_CMD_LOADFILE, "loadfile", 1, { {MP_CMD_ARG_STRING, {0}}, {MP_CMD_ARG_INT,{0}}, {-1,{0}} } }, @@ -470,6 +471,7 @@ static const mp_cmd_bind_t def_cmd_binds { { 'f', 0 }, "vo_fullscreen" }, { { 's', 0 }, "screenshot 0" }, { { 'S', 0 }, "screenshot 1" }, + { { 'L', 0 }, "logo_control" }, { { 'w', 0 }, "panscan -0.1" }, { { 'e', 0 }, "panscan +0.1" }, diff -NpuBr -Xexcludes.txt asLoadedMPlayer/input/input.h MPlayerWithTranslogo/input/input.h --- asLoadedMPlayer/input/input.h 2009-07-01 14:15:03.000000000 +1000 +++ MPlayerWithTranslogo/input/input.h 2009-07-02 12:49:52.000000000 +1000 @@ -45,6 +45,7 @@ typedef enum { MP_CMD_SUB_POS, MP_CMD_DVDNAV, MP_CMD_SCREENSHOT, + MP_CMD_LOGO_CONTROL, MP_CMD_PANSCAN, MP_CMD_MUTE, MP_CMD_LOADFILE, diff -NpuBr -Xexcludes.txt asLoadedMPlayer/libmpcodecs/vf.c MPlayerWithTranslogo/libmpcodecs/vf.c --- asLoadedMPlayer/libmpcodecs/vf.c 2009-07-01 14:15:02.000000000 +1000 +++ MPlayerWithTranslogo/libmpcodecs/vf.c 2009-07-02 12:40:05.000000000 +1000 @@ -93,6 +93,7 @@ extern const vf_info_t vf_info_divtc; extern const vf_info_t vf_info_harddup; extern const vf_info_t vf_info_softskip; extern const vf_info_t vf_info_screenshot; +extern const vf_info_t vf_info_translogo; extern const vf_info_t vf_info_ass; extern const vf_info_t vf_info_mcdeint; extern const vf_info_t vf_info_yadif; @@ -127,6 +128,7 @@ static const vf_info_t* const filter_lis &vf_info_lavc, &vf_info_lavcdeint, &vf_info_screenshot, + &vf_info_translogo, #endif #ifdef CONFIG_ZR &vf_info_zrmjpeg, diff -NpuBr -Xexcludes.txt asLoadedMPlayer/libmpcodecs/vf.h MPlayerWithTranslogo/libmpcodecs/vf.h --- asLoadedMPlayer/libmpcodecs/vf.h 2009-07-01 14:15:02.000000000 +1000 +++ MPlayerWithTranslogo/libmpcodecs/vf.h 2009-07-02 13:48:52.000000000 +1000 @@ -84,6 +84,7 @@ typedef struct vf_seteq_s #define VFCTRL_SKIP_NEXT_FRAME 12 /* For encoding - drop the next frame that passes thru */ #define VFCTRL_FLUSH_FRAMES 13 /* For encoding - flush delayed frames */ #define VFCTRL_SCREENSHOT 14 /* Make a screenshot */ +#define VFCTRL_LOGO_CONTROL 20 /* Control Translogo logo position. */ #define VFCTRL_INIT_EOSD 15 /* Select EOSD renderer */ #define VFCTRL_DRAW_EOSD 16 /* Render EOSD */ #define VFCTRL_GET_PTS 17 /* Return last pts value that reached vf_vo*/ diff -NpuBr -Xexcludes.txt asLoadedMPlayer/libmpcodecs/vf_translogo.c MPlayerWithTranslogo/libmpcodecs/vf_translogo.c --- asLoadedMPlayer/libmpcodecs/vf_translogo.c 1970-01-01 10:00:00.000000000 +1000 +++ MPlayerWithTranslogo/libmpcodecs/vf_translogo.c 2009-07-02 14:44:13.000000000 +1000 @@ -0,0 +1,3458 @@ +/* + * vf_translogo.cpp Transparent logo remover. + * Copyright (C) 2008 by Sylvia Else. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version, and subject to the additional + * restrictions given below. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * The license is subject to the additional restriction that the software + * is to be used only to create output that is for private domestic use. In + * particular it may not be used to create output that is then broadcast, even + * if the broadcaster is the copyright holder or licensee of the input to + * this software. + */ + +/* + * Under Windows there is a degree of hardware sensitivty due to the + * differing formats expected by the video card. + * + * I have tested the following hardware + * + * Harware Data format + * Via/S3G UniChrome Pro IGP YV12 + * NVIDIA GeForce FX5200 YV12 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "config.h" +#include "mp_msg.h" +#include "libvo/fastmemcpy.h" +#include "input/input.h" +#include "osdep/keycodes.h" + +#include "img_format.h" +#include "mp_image.h" +#include "mp_osd.h" +#include "vf.h" + +#include +#include +#include +#include "get_path.h" + +#define MOD_CAP "Remove transparent logo" + +#define FALSE 0 +#define TRUE 1 +typedef unsigned char BOOLEAN; + +/* + * Maximum amount by which the luminance scaling factor can differ between two different + * determinations of the logo data. + */ +#define DEFAULT_MAX_YFACTOR_VAR 10 + +/* + * Ditto for the amount that is added to the luminance. + */ +#define DEFAULT_MAX_YADD_VAR 20 + +/* + * Ditto for the amount that is added to the colour difference values. + */ +#define DEFAULT_MAX_CBCR_ADD_VAR 20 + +#define min(a, b) ((a) < (b) ? (a) : (b)) +#define max(a, b) ((a) > (b) ? (a) : (b)) +#define abs(a) ((a) < 0 ? (-(a)) : (a)) + +/* + * Default border widht. + */ +#define DEFAULT_BORDER_WIDTH 5 + +/* + * Default max border luminance variation. + */ +#define DEFAULT_MAX_BORDER_VARIATION 6 + +/* + * Default number of dark and light images to capture. + */ +#define DEFAULT_CAPTURE_COUNT 2 + +/* + * Number of segments in a half-circle, used for auto-finding logos. Must + * be a power of two and not greater than 128. + */ +#define ARC_SEGMENTS 32 + +/* + * The minimum number of frames needed to perform an autoFind. Having + * this too low just results in futile premature attempts to check for having + * determined a logo's position. + */ +#define MIN_AUTOFIND_FRAMES 2000 + +/* + * Mask for arc numbers. + */ +#define ARC_SEGMENT_MASK (ARC_SEGMENTS - 1) + +#define QUADRANT_ALL 0 +#define QUADRANT_NW 1 +#define QUADRANT_NE 2 +#define QUADRANT_SW 3 +#define QUADRANT_SE 4 + +typedef struct { + unsigned char y; + unsigned char cb; + unsigned char cr; +} YCbCr; + +typedef struct { + unsigned cropCols; // Number of columns in the cropped image. + unsigned cropRows; // Number of rows in the cropped image. + unsigned cropCol; // The column where the cropped image starts within the full image. + unsigned cropRow; // The row where the cropped image starts within the full image. + mp_image_t *fullImage; // The full sized image. +} CropInfo; + +typedef struct { + mp_image_t **captured; + long *frameNumber; + unsigned captureCount; + unsigned captureWait; +} CaptureInfo; + +typedef struct { + float *yFactor; + float *yAdd; + float *cbcrFactor; + float *cbAdd; + float *crAdd; +} Adjust; + +typedef struct _RemoveYData { + int colOffset; + int rowOffset; + unsigned yFactor; // From Adjust object, multiplied by 2^11. + int yAdd; // From Adjust object, multiplied by 2^11. + struct _RemoveYData *next; +} RemoveYData; + +typedef struct _RemoveCbCrData { + int colOffset; + int rowOffset; + unsigned cbcrFactor; // From Adjust object, multiplied by 2^11 + int cbAdd; // From Adjust object, multiplied by 2^11 + int crAdd; // From Adjust object, multiplied by 2^11 + + struct _RemoveCbCrData *next; +} RemoveCbCrData; + +typedef struct _RemoveData { + int columns; + int rows; + int column; + int row; + int maxRowOffset; + int maxColumnOffset; + char *fileName; + unsigned divergence; + RemoveYData *yData; + RemoveCbCrData *cbcrData; + struct _RemoveData *next; +} RemoveData; + +typedef struct vf_priv_s { + unsigned int fmt; // Copied from the filter I used as an example. + + /* Data relating to capturing dark and light background frames. */ + char *croppedImageName; //Config: The name of the uncropped image. + char *fullImageName; // Config: The name of the cropped image. + unsigned interCaptureCount; // Config: The number of frames to wait after a capture + // before capturing again. + unsigned borderWidth; // Config: The width of the border that must be of consistent + // luminance for an image to be captured. + unsigned darkThreshold; // Config: Luminance threshold for dark images. + unsigned lightThreshold; // Config: Luminance threshold for light images. + unsigned maxBorderVariation; // Config: The maximum amount of variation (either side of the average) + // for the border of an image to be considered constant. + unsigned maxLogoAttempts; // Config: The maximum number of attempts at finding a consistent logo. + float maxYFactorVar; // Config: The maximum variation in the calculated + // luminance adjustment factors. + unsigned maxYAddVar; // Config: The maximmum variation (as a number between 0 and 255) + // between calculated luminance adjustment additions. + unsigned maxCbCrAddVar; // Config: The maximum variation (as a number between 0 and 255) + // between calculated colour difference adjustment additions. + char* captureFilePrefix; // Config: The prefix for the capture files. + char* mode; // Config: Either "capture" or "process". + char* tlgFile; // Config: The tlg file name. + unsigned minLogoPixels; // Congif: The minimum credible number of modified pixels in a logo. + unsigned captureCount; // Config: The number of dark and light images respectively to capture. + BOOLEAN noAbort; // Config: If true, then do not abort on 'fatal' errors. + unsigned logoAttempts; // The number of attempts at finding a consistent logo. + unsigned long frame; // Frame number. + BOOLEAN dumpCaptured; // If TRUE then dump the captured images and if possible, the image + // with logo removed. + BOOLEAN dumpGradients; // Config: If TRUE then dump the gradient information that was used + // to determine the location of the logo. + BOOLEAN captureMode; // If TRUE then in capture mode. + BOOLEAN autoFindMode; // If TRUE then in auto-find mode. + BOOLEAN processMode; // If TRUE then in process mode. + int ignoreLines; // Lines to ignore at the top and bottom. + int ignoreColumns; // Columns to ignore at the top and bottom. + + unsigned char borderVal; // Config: The maximum luminance of any added borders that are not + // properly part of the picture. + + CaptureInfo darkCapture; + CaptureInfo lightCapture; + + CropInfo cropInfo; // Details of the cropped image. + + mp_image_t *scaledImage; // The scaled large image used to determine the position of the + // logo region. + int scaledLogoCol; + int scaledLogoCols; + int scaledLogoRow; + int scaledLogoRows; + int scaledForRows; + int scaledForCols; + + RemoveData *currentRd; + + int configHeight; // The height specified to the config function. + int configWidth; // The width specified to the config function/ + + int yLineOffset; // Offset of the luminance information from the + // start of the data for a line. + int cbLineOffset; // Offset to the Cb information from the start of + // the data for a line. + int crLineOffset; // Offet to the Cb information from the start of + // the data for a line. + int cbcrColShift; // Bits by which to shift left a CbCr column. + int yColShift; // Bits by which to shift left a luminance column. + int cbcrRowShift; // Bits by which to shift left a CbCr row. + int cbcrRepeatCount; // Number of consecutive lines to which to apply + // the same CbCr changes. + int yPlane; // Plane containing the y data. + int cbPlane; // Plane containing the cb data. + int crPlane; // Plane containing the cr data. + + unsigned char *aTanTab; // 512 * 512 table of arcsin values, each expressed as a number + // between 0 and ARC_SEGMENTS - 1. + int *gradientCounts[3]; // Gradient counts arrays, used in autoFind mode. + + jmp_buf *exitJmpBuf; // Longjump to here to return -1 to the ultimate caller. + int quadrant; // Quadrant in which to search for the logo. +} vf_priv_t; + +/* + * Reference to aTanTab element, where -256 <= x <= 255 and -256 <= y <= 255. The + * referenced element gives the value of arctan(y/x) + pi/2, scaled to lie between + * 0 and ARC_SEGMENTS - 1 inclusive. The result is undefined for 0/0 (but the element contains 0), + * and is zero if x is zero (which is the limit of the value of the equivalent continuous function + * for negative x and y with x arbitrarily close to zero, or positive x and y with x arbitarily + * close to zero). + */ +#define ATANTAB(vfp, x, y) ((vfp)->aTanTab[(((x) + 256) << 9) + ((y) + 256)]) + +typedef struct option { + char *name; + char *value; + struct option *next; + BOOLEAN requested; +} option_t; + +typedef struct { + int left; + int top; + int right; + int bottom; +} Rectangle; + +/** + * \brief Emit an error level message. + * + * \param The message string. + */ +static void errMessage(const char *message) { + mp_msg(MSGT_VFILTER, MSGL_FATAL, "%s\n", message); +} + +static void outOfMemory(vf_priv_t *vfp) { + mp_msg(MSGT_VFILTER, MSGL_FATAL, "Out of memory\n"); + longjmp(*vfp->exitJmpBuf, 1); +} + +static vf_priv_t *logoControlCtx; + +/** + * \brief Safe allocation of cleared memory. + * + * \param t The type of the elements in the memory. + * \param count The number of elements required. + * + * This macro allocates a specified number of elements of memory of the size of + * the specified type, and then casts it to be a pointer to that type. This + * eliminates an annoyingly common error of allocating memory of the size of the + * pointer to the type rather than that of the type itself. + * + * The memory is cleared, eliminating another common problem. + */ +#define GETMEM(vfp, t, count) ((t *) getMem(vfp, sizeof(t) * (count))) + +/** + * \brief Function used exclusively by the GETMEM macro. + */ +static void *getMem(vf_priv_t *vfp, size_t count) { + void *retVal = calloc(1, count); + if(retVal == NULL) { + outOfMemory(vfp); + } + + return retVal; +} + +static YCbCr getYCbCr(png_byte *rgb) { + YCbCr ybr; + + float y = + 0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2]; + float cb = - 0.168736 * rgb[0] - 0.331264 * rgb[1] + 0.5 * rgb[2]; + float cr = + 0.5 * rgb[0] - 0.418688 * rgb[1] - 0.081312 * rgb[2]; + + ybr.y = (unsigned char) (y / 256 * 219 + 16.5); + ybr.cb = (unsigned char) (cb / 256 * 224 + 128.5); + ybr.cr = (unsigned char) (cr / 256 * 224 + 128.5); + + return ybr; +} + +static void setRGB(png_byte *buf, YCbCr ybr) { + float y = (ybr.y - 16) * 256 / 219.0; + float cb = (ybr.cb - 128) * 256 / 224.0; + float cr = (ybr.cr - 128) * 256 / 224.0; + + int red = (int) (y + 1.402 * cr + 0.5); + int green = (int) ( y - 0.34413 * cb - 0.71413 * cr + 0.5); + int blue = (int) (y + 1.772 * cb + 0.5); + + if(red < 0) + red = 0; + if(red > 255) + red = 255; + buf[0] = (unsigned char) red; + + if(green < 0) + green = 0; + if(green > 255) + green = 255; + buf[1] = (unsigned char) green; + + if(blue < 0) + blue = 0; + if(blue > 255) + blue = 255; + buf[2] = blue; +} + +static mp_image_t *readPNGImage(vf_priv_t *vfp, const char *name) { + jmp_buf *saveJmp; + mp_image_t *image; + png_struct *pngPtr = NULL; + png_info *infoPtr = NULL; + png_info *endInfo = NULL; + png_byte **rowPointers = NULL; + size_t res; + int width; + int height; + int row; + int col; + + char header[8]; + + FILE *f = fopen(name, "rb"); + if(f == NULL) { + mp_msg(MSGT_VFILTER, MSGL_FATAL, "Translogo: Cannot open file %s: %s\n", name, strerror(errno)); + longjmp(*vfp->exitJmpBuf, 1); + } + + res = fread(header, 1, 8, f); + if(res != 8 || png_sig_cmp(header, 0, 8)) { + mp_msg(MSGT_VFILTER, MSGL_FATAL, "Translogo: File %s is not a PNG file", name); + fclose(f); + longjmp(*vfp->exitJmpBuf, 1); + } + + + pngPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if(!pngPtr) + outOfMemory(vfp); + + saveJmp = vfp->exitJmpBuf; + + /* Set the new exit point. This looks strange, buf png_jmpbuf is a macro. */ + vfp->exitJmpBuf = &png_jmpbuf(pngPtr); + if(setjmp(*vfp->exitJmpBuf)) { + fclose(f); + mp_msg(MSGT_VFILTER, MSGL_FATAL, "Translogo: Error reading file %s\n", name); + + png_destroy_read_struct(&pngPtr, &infoPtr, &endInfo); + vfp->exitJmpBuf = saveJmp; + longjmp(*vfp->exitJmpBuf, 1); + } + + infoPtr = png_create_info_struct(pngPtr); + if(!infoPtr) + outOfMemory(vfp); + + endInfo = png_create_info_struct(pngPtr); + if(!endInfo) + outOfMemory(vfp); + + png_init_io(pngPtr, f); + + png_set_sig_bytes(pngPtr, 8); + + png_read_png( + pngPtr, + infoPtr, + PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_STRIP_ALPHA | PNG_TRANSFORM_PACKING | PNG_TRANSFORM_SHIFT, + NULL + ); + + rowPointers = png_get_rows(pngPtr, infoPtr); + + width = infoPtr->width; + height = infoPtr->height; + image = alloc_mpi(infoPtr->width, infoPtr->height, IMGFMT_YV12); + if(image == NULL) + outOfMemory(vfp); + + for(row = 0; row < height; row++) { + for(col = 0; col < width; col++) { + png_byte *rgb = rowPointers[row] + col * 3; + + YCbCr ybr = getYCbCr(rgb); + image->planes[0][image->stride[0] * row + col] = ybr.y; + } + } + + for(row = 0; row < height / 2; row++) { + for(col = 0; col < width / 2; col++) { + YCbCr ybr1; + YCbCr ybr2; + YCbCr ybr3; + YCbCr ybr4; + unsigned cbIndex; + unsigned crIndex; + png_byte *rgb; + + rgb = rowPointers[row * 2] + col * 2 * 3; + ybr1 = getYCbCr(rgb); + ybr2 = getYCbCr(rgb + 3); + + rgb = rowPointers[row * 2 + 1] + col * 2 * 3; + ybr3 = getYCbCr(rgb); + ybr4 = getYCbCr(rgb + 3); + + cbIndex = row * image->stride[1] + col; + crIndex = row * image->stride[2] + col; + image->planes[1][cbIndex] = + (unsigned char) (((int) ybr1.cb + (int) ybr2.cb + (int) ybr3.cb + (int) ybr4.cb + 2) / 4); + image->planes[2][crIndex] = + (unsigned char) (((int) ybr1.cr + (int) ybr2.cr + (int) ybr3.cr + (int) ybr4.cr + 2) / 4); + } + } + + fclose(f); + png_destroy_read_struct(&pngPtr, &infoPtr, &endInfo); + vfp->exitJmpBuf = saveJmp; + + return image; +} + +static void writePNGImage(vf_priv_t *vfp, mp_image_t *image, const char *name) { + jmp_buf *saveJmp; + png_struct *pngPtr = NULL; + png_info *infoPtr = NULL; + int row; + int col; + png_byte *buf = NULL; + + FILE *f = fopen(name, "wb"); + if(f == NULL) { + mp_msg(MSGT_VFILTER, MSGL_FATAL, "Translogo: Cannot create file %s: %s\n", name, strerror(errno)); + longjmp(*vfp->exitJmpBuf, 1); + } + + pngPtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if(!pngPtr) + outOfMemory(vfp); + + saveJmp = vfp->exitJmpBuf; + + /* Set the new exit point. This looks strange, buf png_jmpbuf is a macro. */ + vfp->exitJmpBuf = &png_jmpbuf(pngPtr); + if(setjmp(*vfp->exitJmpBuf)) { + fclose(f); + if(buf) + free(buf); + mp_msg(MSGT_VFILTER, MSGL_FATAL, "Translogo: Error writing file %s\n", name); + + png_destroy_write_struct(&pngPtr, &infoPtr); + vfp->exitJmpBuf = saveJmp; + longjmp(*vfp->exitJmpBuf, 1); + } + + infoPtr = png_create_info_struct(pngPtr); + if(!infoPtr) + outOfMemory(vfp); + + png_init_io(pngPtr, f); + png_set_compression_level(pngPtr, 0); + + png_set_IHDR( + pngPtr, + infoPtr, + image->width, + image->height, + 8, + PNG_COLOR_TYPE_RGB, + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT + ); + + png_write_info(pngPtr, infoPtr); + + buf = GETMEM(vfp, png_byte, image->width * 3); + for(row = 0; row < image->height; row++) { + for(col = 0; col < image->width; col++) { + YCbCr ybr; + ybr.y = image->planes[0][image->stride[0] * row + col]; + ybr.cb = image->planes[1][image->stride[1] * (row / 2) + col / 2]; + ybr.cr = image->planes[2][image->stride[2] * (row / 2) + col / 2]; + + setRGB(buf + col * 3, ybr); + } + png_write_row(pngPtr, buf); + } + + free(buf); + + png_write_end(pngPtr, NULL); + + if(fclose(f) == EOF) { + longjmp(*vfp->exitJmpBuf, 1); + } + + vfp->exitJmpBuf = saveJmp; + + png_destroy_write_struct(&pngPtr, &infoPtr); +} + +/* + * Remove the logo from an image. + */ +static void removeLogo(vf_priv_t *vfp, mp_image_t *im, RemoveData *data) { + RemoveYData *yData; + RemoveCbCrData* cbcrData; + int previousRowOffset; + int startRow = data->row; + int startCol = data->column; + int yPlane = vfp->yPlane; + int cbPlane = vfp->cbPlane; + int crPlane = vfp->crPlane; + int yColShift = vfp->yColShift; + int cbcrColShift = vfp->cbcrColShift; + int cbLineOffset = vfp->cbLineOffset; + int crLineOffset = vfp->crLineOffset; + int cbcrRowShift = vfp->cbcrRowShift; + int cbcrRepeatCount = vfp->cbcrRepeatCount; + unsigned char *yLine = NULL; + unsigned char *cbLine = NULL; + unsigned char *crLine = NULL; + + unsigned char *ucp; + int i; + + if(im->width != data->columns || im->height != data->rows) { + mp_msg(MSGT_VFILTER, MSGL_FATAL, "Translogo: Removal data is for image size %u x %u, but the image itself is of size %u x %u\n", + data->columns, + data->rows, + im->width, + im->height + ); + longjmp(*vfp->exitJmpBuf, 1); + } + + previousRowOffset = -1; + yData = data->yData; + + while(yData) { + int y; + + if(yData->rowOffset != previousRowOffset) { + yLine = im->planes[yPlane] + im->stride[yPlane] * (yData->rowOffset + startRow); + previousRowOffset = yData->rowOffset; + } + + ucp = yLine + ((yData->colOffset + startCol) << yColShift); + y = *ucp * (1 << 11); + y -= yData->yAdd; + y *= yData->yFactor; + y = (y + (1 << 21)) / (1 << 22); + if(y < 16) + y = 16; + if(y > 235) + y = 235; + *ucp = (unsigned char) y; + yData = yData->next; + } + + previousRowOffset = -1; + + for(i = 0; i < cbcrRepeatCount; i++) { + cbcrData = data->cbcrData; + while(cbcrData) { + int cr; + int cb; + int offset; + + if(cbcrData->rowOffset != previousRowOffset) { + /* + * Determine the start of the data for the chrominance signals. + */ + cbLine = + im->planes[cbPlane] + + im->stride[cbPlane] * (((cbcrData->rowOffset + startRow / 2) << cbcrRowShift) + i) + + cbLineOffset; + crLine = + im->planes[crPlane] + + im->stride[crPlane] * (((cbcrData->rowOffset + startRow / 2) << cbcrRowShift) + i) + + crLineOffset; + previousRowOffset = cbcrData->rowOffset; + } + + offset = (cbcrData->colOffset + startCol / 2) << cbcrColShift; + + ucp = cbLine + offset; + cb = *ucp * (1 << 11); + cb -= cbcrData->cbAdd; + cb *= cbcrData->cbcrFactor; + cb = (cb + (1L << 21)) / (1 << 22); + if(cb < 16) + cb = 16; + if(cb > 240) + cb = 240; + *ucp = cb; + + ucp = crLine + offset;; + cr = *ucp * (1 << 11); + cr -= cbcrData->crAdd; + cr *= cbcrData->cbcrFactor; + cr = (cr + (1 << 21)) / (1 << 22); + if(cr < 16) + cr = 16; + if(cr > 240) + cr = 240; + *ucp = cr; + + cbcrData = cbcrData->next; + } + } +} + +/* + * Find the location of a cropped image in the image from which it was cropped. + */ +static CropInfo getCropInfo(vf_priv_t *vfp, const char *largeName, const char *smallName) { + CropInfo cropInfo; + + int lrow, lcol; + BOOLEAN found; + + mp_image_t + *largeImage, + *smallImage; + + int + lir, // Large image rows + lic, // columns + sir, // Small image rows + sic; // columns + + largeImage=readPNGImage(vfp, largeName); + + /* Get the small image. */ + smallImage=readPNGImage(vfp, smallName); + + lir = (int) largeImage->height; + lic = (int) largeImage->width; + sir = (int) smallImage->height; + sic = (int) smallImage->width; + lcol = 0; // Keep compiler happy. + found = FALSE; + for(lrow = 0; lrow < lir - sir; lrow++) { + for(lcol = 0; lcol < lic - sic; lcol++) { + int srow; + + /* + * See whether there's a match here. Note, we only compare the luminance levels, because + * the colour differences get altered if the crop is on an odd pixel boundary. + */ + for(srow = 0; srow < sir; srow++) { + if(memcmp( + largeImage->planes[0] + (lrow + srow) * largeImage->stride[0] + lcol, + smallImage->planes[0] + srow * smallImage->stride[0], + sic + )) + break; + } + + if(srow == sir) { + found = TRUE; + break; + } + } + + if(found) + break; + } + + + if(!found) { + free_mp_image(largeImage); + free_mp_image(smallImage); + mp_msg(MSGT_VFILTER, MSGL_FATAL, "Translogo: Cannot find %s image in %s image\n", smallName, largeName); + longjmp(*vfp->exitJmpBuf, 1); + } + + cropInfo.cropRow = lrow; + cropInfo.cropCol = lcol; + + cropInfo.cropRows = (int) smallImage->height; + cropInfo.cropCols = (int) smallImage->width; + + cropInfo.fullImage = largeImage; + + free_mp_image(smallImage); + + return cropInfo; +} + +/** + * Crop a mp form image. The column, row, columns and rows must all be multiples of 2. + */ +static mp_image_t *cropMpForm( + vf_priv_t *vfp, mp_image_t *im, unsigned col, unsigned row, unsigned cols, unsigned rows +) { + mp_image_t *newIm; + unsigned newRow; + + assert(col % 2 == 0 && row % 2 == 0 && cols % 2 ==0 && rows % 2 == 0); + newIm = alloc_mpi(cols, rows, IMGFMT_YV12); + if(newIm == NULL) + outOfMemory(vfp); + for(newRow = 0; newRow < rows; newRow++) { + unsigned newCol; + for(newCol = 0; newCol < cols; newCol++) { + unsigned oldIndex = (newRow + row) * im->stride[0] + col + newCol; + unsigned newIndex = newRow * newIm->stride[0] + newCol; + newIm->planes[0][newIndex] = im->planes[0][oldIndex]; + } + } + + for(newRow = 0; newRow < rows / 2; newRow++) { + unsigned newCol; + for(newCol = 0; newCol < cols / 2; newCol++) { + unsigned oldCbIndex = (newRow + row / 2) * im->stride[1] + (newCol + col / 2); + unsigned oldCrIndex = (newRow + row / 2) * im->stride[2] + (newCol + col / 2); + unsigned newCbIndex = newRow * newIm->stride[1] + newCol; + unsigned newCrIndex = newRow * newIm->stride[2] + newCol; + newIm->planes[1][newCbIndex] = im->planes[1][oldCbIndex]; + newIm->planes[2][newCrIndex] = im->planes[2][oldCrIndex]; + } + } + + return newIm; +} + +/* + * Get the luminace of the border of the logo region. Returns -1 if the + * border luminance is not consistent. The luminance is considered consistent + * provided it varies by no more than +/- 1 from the average of the top row of + * the logo region. + */ +static int getLogoBorderLuminance(vf_priv_t *vfp, mp_image_t *im) { + unsigned char *logoOrigin; + unsigned char *yp; + int aveY; + unsigned col; + unsigned row; + unsigned mode; + unsigned count; + unsigned logoCol = vfp->scaledLogoCol; + unsigned logoRow = vfp->scaledLogoRow; + unsigned logoCols = vfp->scaledLogoCols; + unsigned logoRows = vfp->scaledLogoRows; + + if(logoCols <= 2 * vfp->borderWidth || logoRows <= 2 * vfp->borderWidth) { + mp_msg(MSGT_VFILTER, MSGL_FATAL, "Translogo: No room left for the logo after the border is excluded\n"); + longjmp(*vfp->exitJmpBuf, 1); + } + + /* Get the average luminance across the top line. */ + logoOrigin = im->planes[0] + logoRow * im->stride[0] + logoCol; + yp = logoOrigin; + aveY = 0; + for(col = logoCols; col > 0; col--) { + aveY += *yp++; + } + + aveY = (aveY + logoCols / 2) / logoCols; + + for(mode = 0; mode < 4; mode++) { + switch(mode) { + case 0: + case 1: + { + unsigned row; + + yp = logoOrigin; + if(mode == 1) + yp += + (logoRows - vfp->borderWidth) * im->stride[0]; + for(row = vfp->borderWidth; row > 0; row--) { + for(col = logoCols; col > 0; col--) { + int dif = aveY - *yp++; + if(dif < -(int) vfp->maxBorderVariation || dif > (int) vfp->maxBorderVariation) + return -1; + } + yp += im->stride[0] - logoCols; + } + break; + } + case 2: + case 3: + { + unsigned row; + + yp = logoOrigin + vfp->borderWidth * im->stride[0]; + if(mode == 3) + yp += logoCols - vfp->borderWidth; + for(row = logoRows - vfp->borderWidth * 2; row > 0; row--) { + unsigned col; + for(col = vfp->borderWidth; col > 0; col--) { + int dif = aveY - *yp++; + if(dif < -(int) vfp->maxBorderVariation || dif > (int) vfp->maxBorderVariation) + return -1; + } + + yp += im->stride[0] - vfp->borderWidth; + } + break; + } + } + } + + /* Now see how many pixels differ from the background. */ + count = 0; + for(row = 0; row < logoRows; row++) { + yp = logoOrigin + (row * im->stride[0]); + for(col = 0; col < logoCols; col++) { + int dif = aveY - *yp++; + if(dif < -(int) vfp->maxBorderVariation || dif > (int) vfp->maxBorderVariation) + count++; + } + } + + /* + * If the number is fewer than are required in a logo, then treat it as not + * containing the logo. This actually means we'll reject frames where the background + * happens to be the same intensity as the added logo pixels, but there will presumably + * be another opportunity to capture the logo, and this helps reduce spurious captures + * derived from blank frames. + */ + if(count < vfp->minLogoPixels) + return -1; + + return aveY; +} + +static Adjust *createAdjust(vf_priv_t *vfp, int pixelCount) { + Adjust *adjust = GETMEM(vfp, Adjust, 1); + adjust->yFactor = GETMEM(vfp, float, pixelCount); + adjust->yAdd = GETMEM(vfp, float, pixelCount); + adjust->cbcrFactor = GETMEM(vfp, float, pixelCount / 4); + adjust->cbAdd = GETMEM(vfp, float, pixelCount / 4); + adjust->crAdd = GETMEM(vfp, float, pixelCount / 4); + return adjust; +} + +static void destroyAdjust(Adjust *adjust) { + free(adjust->yFactor); + free(adjust->yAdd); + free(adjust->cbcrFactor); + free(adjust->cbAdd); + free(adjust->crAdd); + free(adjust); +} + +static Adjust *constructAdjust(vf_priv_t *vfp, mp_image_t *darkIm, mp_image_t *lightIm) { + unsigned pixelCount; + Adjust *adjust; + float lightBackY; + float darkBackY; + float backYDiff; + int row; + int col; + float lightBackCr; + float darkBackCr; + float lightBackCb; + float darkBackCb; + + int rows = darkIm->height; + int columns = darkIm->width; + + assert(rows == lightIm->height && columns == lightIm->width); + + /* + * Assert these things about the input images. We expect them to be true because the images + * should be ones we created this way. + */ + assert(columns == lightIm->stride[0] && columns == darkIm->stride[0]); + assert(lightIm->stride[1] == lightIm->stride[2] && lightIm->stride[1] == columns / 2); + assert(darkIm->stride[1] == darkIm->stride[2] && darkIm->stride[1] == columns / 2); + + pixelCount = rows * columns; + + adjust = createAdjust(vfp, pixelCount); + + lightBackY = 0; + darkBackY = 0; + for(col = 0; col < columns; col++) { + lightBackY += lightIm->planes[0][col]; + darkBackY += darkIm->planes[0][col]; + } + + lightBackY /= columns; + darkBackY /= columns;; + backYDiff = darkBackY - lightBackY; + + for(row = 0; row < rows; row++) { + for(col = 0; col < columns; col++) { + int lIndex = row * lightIm->stride[0] + col; + int dIndex = row * darkIm->stride[0] + col; + + /* + * First determine the original scale factor by which the pixels were multiplied before + * broadcast. + */ + int darkY = darkIm->planes[0][dIndex]; + int lightY = lightIm->planes[0][lIndex]; + float yFactor = (darkY - lightY) / backYDiff; + + /* Now determine the y that was added. */ + float yAdd = (darkY - darkBackY * yFactor + lightY - lightBackY * yFactor) / 2; + + int adjustIndex = row * columns + col; + adjust->yFactor[adjustIndex] = yFactor; + adjust->yAdd[adjustIndex] = yAdd; + } + } + + lightBackCr = 0; + darkBackCr = 0; + lightBackCb = 0; + darkBackCb = 0; + + for(col = 0; col < columns / 2; col++) { + lightBackCb += lightIm->planes[1][col]; + darkBackCb += darkIm->planes[1][col]; + lightBackCr += lightIm->planes[2][col]; + darkBackCr += darkIm->planes[2][col]; + } + + darkBackCr /= columns / 2; + darkBackCb /= columns / 2; + lightBackCr /= columns / 2; + lightBackCb /= columns / 2; + + for(row = 0; row < rows / 2; row++) { + for(col = 0; col < columns / 2; col++) { + unsigned cIndex; + unsigned darkCr; + unsigned darkCb; + unsigned lightCr; + unsigned lightCb; + float cFactor; + float crAdd; + float cbAdd; + + unsigned char *db = darkIm->planes[0]; + unsigned char *lb = lightIm->planes[0]; + + int dIndex = row * 2 * darkIm->stride[0] + col * 2; + int lIndex = row * 2 * lightIm->stride[0] + col * 2; + + float darkY = + (db[dIndex] + db[dIndex + 1] + db[dIndex + darkIm->stride[0]] + db[dIndex + darkIm->stride[0] + 1]) / 4.0; + float lightY = + (lb[lIndex] + lb[lIndex + 1] + lb[lIndex + lightIm->stride[0]] + lb[lIndex + lightIm->stride[0] + 1]) / 4.0; + + cIndex = row * columns / 2 + col; + + darkCb = darkIm->planes[1][cIndex]; + darkCr = darkIm->planes[2][cIndex]; + + lightCb = lightIm->planes[1][cIndex]; + lightCr = lightIm->planes[2][cIndex]; + + cFactor = (darkY - lightY) / backYDiff; + + crAdd = (darkCr - darkBackCr * cFactor + lightCr - lightBackCr * cFactor) / 2; + cbAdd = (darkCb - darkBackCb * cFactor + lightCb - lightBackCb * cFactor) / 2; + + adjust->cbcrFactor[cIndex] = cFactor; + adjust->cbAdd[cIndex] = cbAdd; + adjust->crAdd[cIndex] = crAdd; + } + } + + return adjust; +} + +static float absFloat(float f) { + return f < 0 ? -f : f; +} + +static float maxFloat(float f1, float f2) { + return f1 > f2 ? f1 : f2; +} + +static void destroyRemoveData(RemoveData *rd) { + RemoveCbCrData *cbcrData; + RemoveYData *yData; + + yData = rd->yData; + while(yData) { + RemoveYData *r = yData; + yData = yData->next; + free(r); + } + + cbcrData = rd->cbcrData; + while(cbcrData) { + RemoveCbCrData *r = cbcrData; + cbcrData = cbcrData->next; + free(r); + } + + if(rd->fileName) + free(rd->fileName); + + free(rd); +} + +static void writeRemoveData(vf_priv_t *vfp, RemoveData *rd, const char *name) { + RemoveYData *ryd; + RemoveCbCrData *rcbcrd; + + FILE *f = fopen(name, "wb"); + if(!f) { + mp_msg(MSGT_VFILTER, MSGL_FATAL, "Translogo: Cannot create file %s: %s\n", name, strerror(errno)); + longjmp(*vfp->exitJmpBuf, 1); + } + + fprintf(f, "TRANSLOGO2,%u,%u,%u,%u,%u\n", rd->column, rd->row, rd->columns, rd->rows, rd->divergence); + ryd = rd->yData; + while(ryd) { + fprintf(f, "Y%u,%u,%u,%d\n", ryd->colOffset, ryd->rowOffset, ryd->yFactor, ryd->yAdd); + ryd = ryd->next; + } + + rcbcrd = rd->cbcrData; + while(rcbcrd) { + fprintf( + f, + "C%u,%u,%u,%d,%d\n", + rcbcrd->colOffset, + rcbcrd->rowOffset, + rcbcrd->cbcrFactor, + rcbcrd->cbAdd, + rcbcrd->crAdd + ); + rcbcrd = rcbcrd->next; + } + + fprintf(f, "X\n"); + if(fclose(f) == EOF) { + mp_msg(MSGT_VFILTER, MSGL_FATAL, "Translogo: Error writing file %s: %s\n", name, strerror(errno)); + longjmp(*vfp->exitJmpBuf, 1); + } +} + +static void parseError(vf_priv_t *vfp, const char *name, unsigned line) { + mp_msg(MSGT_VFILTER, MSGL_FATAL, "Translogo: Error parsing file %s at about line %u\n", name, line); + longjmp(*vfp->exitJmpBuf, 1); +} + +static void convertVersion1Data(RemoveData *rd) { + unsigned offset = 0; + RemoveCbCrData *rcbcrd; + RemoveYData *ryd = rd->yData; + while(ryd) { + offset += ryd->rowOffset * rd->columns + ryd->colOffset; + ryd->rowOffset = offset / rd->columns; + ryd->colOffset = offset % rd->columns; + ryd = ryd->next; + } + + offset = 0; + rcbcrd = rd->cbcrData; + while(rcbcrd) { + offset += rcbcrd->rowOffset * (rd->columns / 2) + rcbcrd->colOffset; + rcbcrd->rowOffset = offset / (rd->columns / 2); + rcbcrd->colOffset = offset % (rd->columns / 2); + rcbcrd = rcbcrd->next; + } +} + +static RemoveData *readRemoveData(vf_priv_t *vfp, const char *name) { + RemoveData *rd; + unsigned line; + RemoveYData **removeYPtr; + RemoveCbCrData **removeCPtr; + RemoveYData *ryd; + RemoveCbCrData *rcbcrd; + BOOLEAN xFound; + int res; + int count; + int versionChar; + + FILE *f = fopen(name, "rb"); + if(!f) { + mp_msg(MSGT_VFILTER, MSGL_FATAL, "Translogo: Cannot open file %s: %s\n", name, strerror(errno)); + longjmp(*vfp->exitJmpBuf, 1); + } + + rd = GETMEM(vfp, RemoveData, 1); + rd->fileName = strdup(name); + line = 1; + count = 0; + fscanf(f, "TRANSLOGO%n", &count); + if(count != 9) + parseError(vfp, name, line); + + versionChar = fgetc(f); + if(versionChar != '1' && versionChar != '2') + parseError(vfp, name, line); + + if(versionChar == '1') { + res = fscanf(f, ",%u,%u,%u", &rd->columns, &rd->rows, &rd->divergence); + if(res != 3) + parseError(vfp, name, line); + } else { + res = fscanf(f, ",%u,%u,%u,%u,%u", &rd->column, &rd->row, &rd->columns, &rd->rows, &rd->divergence); + if(res != 5) + parseError(vfp, name, line); + } + + /* Ensure that we can't get arithmetic overflow later. No TV image would be this big. */ + if(rd->columns > (2 << 15) || rd->rows > (2 << 15)) { + mp_msg(MSGT_VFILTER, MSGL_FATAL, "Translogo: Inconsistent removal data refers to excessively large image\n"); + longjmp(*vfp->exitJmpBuf, 1); + } + + if(rd->column >= rd->columns || rd->row >= rd->rows) { + mp_msg(MSGT_VFILTER, MSGL_FATAL, "Translogo: Inconsistent removal data refers to a pixel outside the image\n"); + longjmp(*vfp->exitJmpBuf, 1); + } + + removeYPtr = &rd->yData; + removeCPtr = &rd->cbcrData; + xFound = FALSE; + while(!xFound) { + char type; + + line++; + res = fscanf(f, " %c", &type); + if(res != 1) + parseError(vfp, name, line); + switch(type) { + case 'Y': + { + unsigned colOffset; + unsigned rowOffset; + unsigned factor; + int add; + RemoveYData *removeYData; + + res = fscanf(f,"%u,%u,%u,%d", &colOffset, &rowOffset, &factor, &add); + if(res != 4) + parseError(vfp, name, line); + removeYData = GETMEM(vfp, RemoveYData, 1); + removeYData->colOffset = colOffset; + removeYData->rowOffset = rowOffset; + removeYData->yFactor = factor; + removeYData->yAdd = add; + *removeYPtr = removeYData; + removeYPtr = &removeYData->next; + break; + } + case 'C': + { + unsigned colOffset; + unsigned rowOffset; + unsigned factor; + unsigned cbAdd; + unsigned crAdd; + RemoveCbCrData *removeCbCrData; + + res = fscanf(f, "%u,%u,%u,%d,%d", &colOffset, &rowOffset, &factor, &cbAdd, &crAdd); + if(res != 5) + parseError(vfp, name, line); + removeCbCrData = GETMEM(vfp, RemoveCbCrData, 1); + removeCbCrData->colOffset = colOffset; + removeCbCrData->rowOffset = rowOffset; + removeCbCrData->cbcrFactor = factor; + removeCbCrData->cbAdd = cbAdd; + removeCbCrData->crAdd = crAdd; + *removeCPtr = removeCbCrData; + removeCPtr = &removeCbCrData->next; + break; + } + case 'X': + xFound = TRUE; + break; + default: + parseError(vfp, name, line); + } + } + + fclose(f); + + if(versionChar == '1') { + convertVersion1Data(rd); + } + + /* Check that each pixel reference is for a pixel within the image. */ + ryd = rd->yData; + while(ryd) { + if(ryd->rowOffset > rd->maxRowOffset) + rd->maxRowOffset = ryd->rowOffset; + + if(ryd->colOffset > rd->maxColumnOffset) + rd->maxColumnOffset = ryd->colOffset; + + if( + ryd->rowOffset + rd->row >= rd->rows + || ryd->colOffset + rd->column >= rd->columns + ) { + mp_msg(MSGT_VFILTER, MSGL_FATAL, "Translogo: Inconsistent removal data refers to a pixel outside the image\n"); + longjmp(*vfp->exitJmpBuf, 1); + } + + ryd = ryd->next; + } + + rcbcrd = rd->cbcrData; + while(rcbcrd) { + if(rcbcrd->rowOffset * 2 > rd->maxRowOffset) + rd->maxRowOffset = rcbcrd->rowOffset * 2; + + if(rcbcrd->colOffset * 2 > rd->maxColumnOffset) + rd->maxColumnOffset = rcbcrd->colOffset * 2; + + if( + rcbcrd->rowOffset + rd->row / 2 >= rd->rows / 2 + || rcbcrd->colOffset + rd->column / 2 >= rd->columns / 2 + ) { + mp_msg(MSGT_VFILTER, MSGL_FATAL, "Translogo: Inconsistent removal data refers to a pixel outside the image\n"); + longjmp(*vfp->exitJmpBuf, 1); + } + + rcbcrd = rcbcrd->next; + } + + return rd; +} + +static mp_image_t *copyImage(vf_priv_t *vfp, mp_image_t *image) { + mp_image_t *dest = alloc_mpi(image->width, image->height, image->imgfmt); + if(dest == NULL) + outOfMemory(vfp); + + memcpy_pic(dest->planes[0], image->planes[0], image->width, image->height, dest->stride[0], image->stride[0]); + memcpy_pic(dest->planes[1], image->planes[1], image->width / 2, image->height / 2, dest->stride[1], image->stride[1]); + memcpy_pic(dest->planes[2], image->planes[2], image->width / 2, image->height / 2, dest->stride[2], image->stride[2]); + + return dest; +} + +static void dumpNoLogo(vf_priv_t *vfp, RemoveData *rd, const char *name, long frame) { + mp_image_t *im; + + im = copyImage(vfp, vfp->scaledImage); + removeLogo(vfp, im, rd); + writePNGImage(vfp, im, name); + if(frame != -1) + mp_msg(MSGT_VFILTER, MSGL_INFO, "Translogo: Created image %s at frame %ld\n", name, frame); + else + mp_msg(MSGT_VFILTER, MSGL_INFO, "Translogo: Created image %s \n", name); + free_mp_image(im); +} + +/* + * Get the data for logo removal, if the logo can be consistently determined. + * + * The wholeColumns and wholeRowes parameters represent the width of the whole Transcode form image + * that would be modified. + */ +static RemoveData *getLogoRemoveData(vf_priv_t *vfp, unsigned wholeColumns, unsigned wholeRows) { + float worstYFactorVar; + float worstYAddVar; + float worstCbAddVar; + float worstCrAddVar; + unsigned first; + unsigned light; + RemoveData *removeData; + unsigned i; + + unsigned adjustCount = vfp->captureCount * vfp->captureCount; + Adjust **adjusts = GETMEM(vfp, Adjust *, adjustCount); + unsigned pixelCount = vfp->darkCapture.captured[0]->width * vfp->darkCapture.captured[0]->height; + + for(light = 0; light < vfp->captureCount; light++) { + unsigned dark; + for(dark = 0; dark < vfp->captureCount; dark++) { + adjusts[light * vfp->captureCount + dark] = + constructAdjust(vfp, vfp->darkCapture.captured[dark], vfp->lightCapture.captured[light]); + } + } + + worstYFactorVar = 0; + worstYAddVar = 0; + worstCbAddVar = 0; + worstCrAddVar = 0; + + for(first = 0; first < adjustCount; first++) { + unsigned second; + for(second = 0; second < adjustCount; second++) { + unsigned index; + + if(first == second) + continue; // No point in comparing an adjustment with itself. + + for(index = 0; index < pixelCount; index++) { + float yFactorVar; + float yAddVar; + + /* Check consistence of luminance factor, and addition. */ + yFactorVar = absFloat(adjusts[first]->yFactor[index] - adjusts[second]->yFactor[index]); + if(yFactorVar > worstYFactorVar) + worstYFactorVar = yFactorVar; + yAddVar = absFloat(adjusts[first]->yAdd[index] - adjusts[second]->yAdd[index]); + if(yAddVar > worstYAddVar) + worstYAddVar = yAddVar; + } + + for(index = 0; index < pixelCount / 4; index++) { + float cbAddVar; + float crAddVar; + + /* + * Ditto for the colour difference additions. We don't check the factors for the colour differences because + * they're merely averages of luminance values whose consistency is checked above. + */ + cbAddVar = absFloat(adjusts[first]->cbAdd[index / 4] - adjusts[second]->cbAdd[index / 4]); + if(cbAddVar > worstCbAddVar) + worstCbAddVar = cbAddVar; + + crAddVar = absFloat(adjusts[first]->crAdd[index / 4] - adjusts[second]->crAdd[index / 4]); + if(crAddVar > worstCrAddVar) + worstCrAddVar = crAddVar; + } + } + } + + removeData = (RemoveData *) NULL; + // mp_msg(MSGT_VFILTER, MSGL_INFO, "worstYFactorVar = %f against %f\n", worstYFactorVar, vfp->maxYFactorVar); + // mp_msg(MSGT_VFILTER, MSGL_INFO, "worstYAddVar = %f against %u\n", worstYAddVar, vfp->maxYAddVar); + // mp_msg(MSGT_VFILTER, MSGL_INFO, "worstCbAddVar = %f against %u\n", worstCbAddVar, vfp->maxCbCrAddVar); + // mp_msg(MSGT_VFILTER, MSGL_INFO, "worstCrAddVar = %f against %u\n", worstCrAddVar, vfp->maxCbCrAddVar); + + if( + worstYFactorVar <= vfp->maxYFactorVar + && worstYAddVar <= vfp->maxYAddVar + && worstCbAddVar <= vfp->maxCbCrAddVar + && worstCrAddVar <= vfp->maxCbCrAddVar + ) { + int row; + RemoveYData *lastYData; + RemoveCbCrData *lastCbCrData; + int rows = vfp->darkCapture.captured[0]->height; + int cols = vfp->darkCapture.captured[0]->width; + + removeData = GETMEM(vfp, RemoveData, 1); + removeData->columns = wholeColumns; + removeData->rows = wholeRows; + removeData->column = vfp->scaledLogoCol; + removeData->row = vfp->scaledLogoRow; + + removeData->divergence = + (unsigned) maxFloat(worstYFactorVar * 128, maxFloat(worstYAddVar, maxFloat(worstCbAddVar, worstCrAddVar))); + + lastYData = (RemoveYData *) NULL; + for(row = 0; row < rows; row++) { + int col; + for(col = 0; col < cols; col++) { + RemoveYData *ryd; + + int index = row * cols + col; + + float yFactor = 0; + float yAdd = 0; + unsigned i; + for(i = 0; i < adjustCount; i++) { + yFactor += adjusts[i]->yFactor[index]; + yAdd += adjusts[i]->yAdd[index]; + } + + yFactor /= adjustCount; + yAdd /= adjustCount; + + if(absFloat(1 - yFactor) < vfp->maxYFactorVar / 2 && absFloat(yAdd) < vfp->maxYAddVar / 4.0) + continue; // Could be just noise - We don't even know that this pixel was altered by the logo. + + ryd = GETMEM(vfp, RemoveYData, 1); + ryd->yFactor = (unsigned) ((1 / yFactor + 1.0 / (1 << 12)) * (1 << 11)); + ryd->yAdd = (int) ((yAdd + 1.0 / (1 << 12)) * (1 << 11)); + ryd->rowOffset = row; + ryd->colOffset = col; + + if(lastYData) + lastYData->next = ryd; + else + removeData->yData = ryd; + lastYData = ryd; + } + } + + lastCbCrData = (RemoveCbCrData *) NULL; + for(row = 0; row < rows / 2; row++) { + int col; + for(col = 0; col < cols / 2; col++) { + RemoveCbCrData *cbcrd; + + unsigned index = row * cols / 2 + col; + float cbcrFactor = 0; + float cbAdd = 0; + float crAdd = 0; + + unsigned i; + for(i = 0; i < adjustCount; i++) { + cbcrFactor += adjusts[i]->cbcrFactor[index]; + cbAdd += adjusts[i]->cbAdd[index]; + crAdd += adjusts[i]->crAdd[index]; + } + + cbcrFactor /= adjustCount; + cbAdd /= adjustCount; + crAdd /= adjustCount; + + if( + absFloat(1 - cbcrFactor) < vfp->maxYFactorVar / 4.0 + && absFloat(cbAdd) < vfp->maxCbCrAddVar / 4.0 + && absFloat(crAdd) < vfp->maxCbCrAddVar / 4.0 + ) + continue; // Could be just noise - we don't even know that this pixel was altered by the logo. + + cbcrd = GETMEM(vfp, RemoveCbCrData, 1); + cbcrd->cbcrFactor = (unsigned) ((1 / cbcrFactor + 1.0 / (1 << 12)) * (1 << 11)); + cbcrd->cbAdd = (int) ((cbAdd + 1.0 / (1 << 12)) * (1 << 11)); + cbcrd->crAdd = (int) ((crAdd + 1.0 / (1 << 12)) * (1 << 11)); + + cbcrd->colOffset = col; + cbcrd->rowOffset = row; + + if(lastCbCrData) + lastCbCrData->next = cbcrd; + else + removeData->cbcrData = cbcrd; + lastCbCrData = cbcrd; + } + } + } + + for(i = 0; i < vfp->captureCount * vfp->captureCount; i++) { + if(adjusts[i]) + destroyAdjust(adjusts[i]); + } + + free(adjusts); + + return removeData; +} + +static const char *getFormatName(unsigned int fmt) { + const char *name = NULL; + switch(fmt){ + /* Planar types. */ + case IMGFMT_YV12: + name = "YV12"; + break; + case IMGFMT_I420: + name = "I420"; + break; + case IMGFMT_IYUV: + name = "IYUV"; + break; + case IMGFMT_CLPL: + name = "CLPL"; + break; + case IMGFMT_NV12: + name = "NV12"; + break; + case IMGFMT_NV21: + name = "NV21"; + break; + case IMGFMT_UYVY: + name = "UYVY"; + break; + case IMGFMT_Y422: + name = "Y422"; + break; + case IMGFMT_UYNV: + name = "UYNV"; + break; + case IMGFMT_YUY2: + name = "YUY2"; + break; + case IMGFMT_YUNV: + name = "YUNV"; + break; + case IMGFMT_V422: + name = "V422"; + break; + default: + name = "Unknown"; + } + return name; +} + +static BOOLEAN checkFormat(vf_priv_t *vfp, unsigned int fmt ) { + switch(fmt){ + /* Planar types. */ + case IMGFMT_YV12: + case IMGFMT_I420: + case IMGFMT_IYUV: + case IMGFMT_CLPL: + vfp->yPlane = 0; + vfp->cbPlane = 1; + vfp->crPlane = 2; + vfp->yLineOffset = 0; + vfp->yColShift = 0; + vfp->cbLineOffset = 0; + vfp->crLineOffset = 0; + vfp->cbcrColShift = 0; + vfp->cbcrRepeatCount = 1; + vfp->cbcrRowShift = 0; + return TRUE; + case IMGFMT_NV12: + vfp->yPlane = 0; + vfp->cbPlane = 1; + vfp->crPlane = 1; + vfp->yLineOffset = 0; + vfp->yColShift = 0; + vfp->cbLineOffset = 0; + vfp->crLineOffset = 1; + vfp->cbcrColShift = 1; + vfp->cbcrRepeatCount = 1; + vfp->cbcrRowShift = 0; + return TRUE; + case IMGFMT_NV21: + vfp->yPlane = 0; + vfp->cbPlane = 1; + vfp->crPlane = 1; + vfp->yLineOffset = 0; + vfp->yColShift = 0; + vfp->cbLineOffset = 1; + vfp->crLineOffset = 0; + vfp->cbcrColShift = 1; + vfp->cbcrRepeatCount = 1; + vfp->cbcrRowShift = 0; + return TRUE; + + /* Packed types. */ + case IMGFMT_UYVY: + case IMGFMT_Y422: + case IMGFMT_UYNV: +// case IMGFMT_HDYC: // Not included in header file. + vfp->yPlane = 0; + vfp->cbPlane = 0; + vfp->crPlane = 0; + vfp->yColShift = 1; + vfp->yLineOffset = 1; + vfp->cbLineOffset = 0; + vfp->crLineOffset = 2; + vfp->cbcrColShift = 2; + vfp->cbcrRepeatCount = 2; + vfp->cbcrRowShift = 1; + return TRUE; + + case IMGFMT_YUY2: + case IMGFMT_YUNV: + case IMGFMT_V422: + vfp->yPlane = 0; + vfp->cbPlane = 0; + vfp->crPlane = 0; + vfp->yLineOffset = 0; + vfp->yColShift = 1; + vfp->cbLineOffset = 1; + vfp->crLineOffset = 3; + vfp->cbcrColShift = 2; + vfp->cbcrRepeatCount = 2; + vfp->cbcrRowShift = 1; + + return TRUE; + } + + return FALSE; +} + +static void configAutoFind(vf_priv_t *vfp) { + int x; + int y; + double piBy2 = asin(1.0); // The defined constants for PI seem a bit unreliable as to + // their existence. + vfp->gradientCounts[0] = GETMEM(vfp, int, vfp->configWidth * vfp->configHeight * ARC_SEGMENTS); + vfp->gradientCounts[1] = GETMEM(vfp, int, vfp->configWidth * vfp->configHeight / 4 * ARC_SEGMENTS); + vfp->gradientCounts[2] = GETMEM(vfp, int, vfp->configWidth * vfp->configHeight / 4 * ARC_SEGMENTS); + vfp->aTanTab = GETMEM(vfp, unsigned char, 512 * 512); + + for(x = -256; x < 255; x++) { + for(y = -256; y < 255; y++) { + double angle; + unsigned char segment; + if(x == 0 && y == 0) + continue; // Not defined for 0/0 + + if(abs(y) <= abs(x)) { + angle = atan((double) y / x); + } else { + angle = atan((double) x / y); + if(angle < 0) + angle = -piBy2 - angle; + else + angle = piBy2 - angle; + } + angle += piBy2; + + /* + * Check for residual very small negative angle arising from rounding errors. + * Maybe this can't happen, but I prefer to be sure. + */ + if(angle < 0) + angle = 0; + + segment = (unsigned char) ((double) ARC_SEGMENTS * angle / (piBy2 * 2) + 0.5); + if(segment >= ARC_SEGMENTS) + segment -= ARC_SEGMENTS; // Occurs for x = 0. + + ATANTAB(vfp, x, y) = segment; + } + } + +#if 0 + mp_msg(MSGT_VFILTER, MSGL_INFO, "\nTranslogo 100, 100 %u: \n", ATANTAB(vfp, 100, 100)); + mp_msg(MSGT_VFILTER, MSGL_INFO, "\nTranslogo 50, 150 %u: \n", ATANTAB(vfp, 50, 150)); + mp_msg(MSGT_VFILTER, MSGL_INFO, "\nTranslogo -100, 100 %u: \n", ATANTAB(vfp, -100, 100)); + mp_msg(MSGT_VFILTER, MSGL_INFO, "\nTranslogo -100, -150 %u: \n", ATANTAB(vfp, -100, -150)); + mp_msg(MSGT_VFILTER, MSGL_INFO, "\nTranslogo 150, 50, %u: \n", ATANTAB(vfp, 150, 50)); + mp_msg(MSGT_VFILTER, MSGL_INFO, "\nTranslogo -150, -100, %u: \n", ATANTAB(vfp, -150, -100)); + mp_msg(MSGT_VFILTER, MSGL_INFO, "\nTranslogo 0, -100, %u: \n", ATANTAB(vfp, 0, -100)); + mp_msg(MSGT_VFILTER, MSGL_INFO, "\nTranslogo 0, 100, %u: \n", ATANTAB(vfp, 0, 100)); + mp_msg(MSGT_VFILTER, MSGL_INFO, "\nTranslogo -100, 0, %u: \n", ATANTAB(vfp, -100, 0)); + mp_msg(MSGT_VFILTER, MSGL_INFO, "\nTranslogo 100, 0, %u: \n", ATANTAB(vfp, 100, 0)); +#endif +} + +/** + * \brief Configure the filter and call the next filter's config function. + */ +static int config( + struct vf_instance_s* vf, + int width, + int height, + int d_width, + int d_height, + unsigned int flags, + unsigned int outfmt +) +{ + int retVal; + + vf_priv_t *vfp = (vf_priv_t *) vf->priv; + + vfp->configWidth = width; + vfp->configHeight = height; + + if((vfp->captureMode && outfmt != IMGFMT_YV12) || !checkFormat(vfp, outfmt)) { + /* + * Seems to me this can't happen - prior to calling this function, vf.c has + * called query_format to check that the format is acceptable. + */ + mp_msg(MSGT_VFILTER, MSGL_FATAL, "\nTranslogo: Requested unexpected format: %8x\n", outfmt); + return 0; + } + + mp_msg(MSGT_VFILTER, MSGL_INFO, "\nTranslogo: Processing with format: %s\n", getFormatName(outfmt)); + + retVal = vf_next_config(vf,width,height,d_width,d_height,flags, outfmt); + if(!retVal) + return 0; + vfp->fmt = outfmt; + + if(vfp->autoFindMode) { + configAutoFind(vfp); + } + return retVal; +} + +/* + * \brief Scale into a single row or column of an image. + * + * \param to The destination buffer. + * \param tCount The number of bytes in the destination buffer. + * \parm toInc The increment between successive bytes in the destination buffer. + * \param from The destination buffer. + * \param fromCount The number of bytes in the destination buffer. + * \parm fromInc The increment between successive bytes in the destination buffer. + */ +static void scaleSlice(unsigned char *toBuf, int toCount, int toInc, unsigned char *fromBuf, int fromCount, int fromInc) { + + /* + * Get a number into which both counts divide exactly. The lowest common multiple would + * do, but just multiplying them together is simpler. + */ + int mult = toCount * fromCount; + + /* + * For the 'to' image, we're going to go from 0 to mult in toCount steps, so each + * step is fromCount. Similarly for the from image. So these two assignments are correct + * even though at first sight they look the wrong way around. Indeed, they're made + * to highlight the strangeness here, rather than making the subsequent code even + * more confusing. + */ + int toStep = fromCount; + int fromStep = toCount; + + int toPos = 0; + int fromPos = 0; + + int x; + for(x = 0; x < toCount; x++) { + unsigned pixel = 0; + /* + * If there's some left over from the previous from pixel, then + * include a fraction of it here. (It's a fraction because it gets multiplied + * by less than fromStep). + */ + if(fromPos < toPos) { + fromPos += fromStep; + pixel += *fromBuf * (fromPos - toPos); + fromBuf += fromInc; + } + + /* + * Now include the subsequent from pixels that are wholly, or partly + * covered by the to pixel. Too much of the last from pixel may be included, + * which is dealt with below. + */ + toPos += toStep; + while(fromPos < toPos) { + fromPos += fromStep; + pixel += *fromBuf * fromStep; + fromBuf += fromInc; + } + + /* + * If too much of the last from pixel was included, then subtract off the + * fraction that's not covered by the to pixel. The position and buf pointers + * are stepped back - they'll be advanced again at the start of the next + * iteration. + */ + if(fromPos > toPos) { + fromBuf -= fromInc; + pixel -= *fromBuf * (fromPos - toPos); + fromPos -= fromStep; + } + + *toBuf = pixel / toStep; + toBuf += toInc; + } + + assert(fromPos == mult && toPos == mult); +} + +/** + * \brief Scale an entire plane. + * \param vfp The private data object. + * \param to The destination image, with it's size already set. + * \param from The source image. + * \param plane The plane that is getting scaled. + */ +static void scalePlane(vf_priv_t *vfp, mp_image_t *to, mp_image_t *from, int plane) { + int xShift; + int yShift; + int toWidth; + int toHeight; + int fromWidth; + int fromHeight; + int row; + int col; + unsigned char *iBuf; + + if(plane != 0) { + assert(to->chroma_x_shift == from->chroma_x_shift); + assert(to->chroma_y_shift == from->chroma_y_shift); + xShift = to->chroma_x_shift; + yShift = to->chroma_y_shift; + } else { + xShift = 0; + yShift = 0; + } + + toWidth = to->width >> xShift; + toHeight = to->height >> yShift; + + fromWidth = from->width >> xShift; + fromHeight = from->height >> yShift; + + iBuf = GETMEM(vfp, unsigned char, toWidth * fromHeight); + + /* + * Scale withwise into the intermediate buffer. + */ + for(row = 0; row < fromHeight; row++) { + scaleSlice(iBuf + row * toWidth, toWidth, 1, from->planes[plane] + row * from->stride[plane], fromWidth, 1); + } + + /* + * Scale heightwise from the intermediate buffer into the to image. + */ + for(col = 0; col < toWidth; col++) { + scaleSlice(to->planes[plane] + col, toHeight, to->stride[plane], iBuf + col, fromHeight, toWidth); + } + + free(iBuf); +} + +/** + * \brief Create a new image from an existing one, scaled to the specifid width and height. + * + * \param vfp The private data object. + * \param image The image to be scaled. + * \param width The desired width. + * \param height The desired height. + * + * Returns the newly created scaled image. + */ +static mp_image_t *scaleImage(vf_priv_t *vfp, mp_image_t *image, int width, int height) { + mp_image_t *newImage = alloc_mpi(width, height, image->imgfmt); + if(newImage == NULL) + outOfMemory(vfp); + + scalePlane(vfp, newImage, image, 0); + scalePlane(vfp, newImage, image, 1); + scalePlane(vfp, newImage, image, 2); + + return newImage; +} + +/** + * \brief Set the scaled position and size for the area specified by the user as approximating the + * area containing the logo. + * + * \param vfp The filter's private data. + * \param im The image. + **/ +static void setScaled(vf_priv_t *vfp, mp_image_t *im) { + if(vfp->scaledForRows == im->height && vfp->scaledForCols == im->width) + return; + + vfp->scaledLogoCol = im->width * vfp->cropInfo.cropCol / vfp->cropInfo.fullImage->width; + vfp->scaledLogoRow = im->height * vfp->cropInfo.cropRow / vfp->cropInfo.fullImage->height; + vfp->scaledLogoCols = im->width * vfp->cropInfo.cropCols / vfp->cropInfo.fullImage->width; + vfp->scaledLogoRows = im->height * vfp->cropInfo.cropRows / vfp->cropInfo.fullImage->height; + + if(vfp->scaledLogoCol % 2) { + vfp->scaledLogoCol--; + vfp->scaledLogoCols++; + } + + if(vfp->scaledLogoRow % 2) { + vfp->scaledLogoRow--; + vfp->scaledLogoRows++; + } + + vfp->scaledLogoCols += vfp->scaledLogoCols % 2; + vfp->scaledLogoRows += vfp->scaledLogoRows % 2; + + if(vfp->scaledImage != NULL) + free_mp_image(vfp->scaledImage); + + vfp->scaledImage = scaleImage(vfp, vfp->cropInfo.fullImage, im->width, im->height); + vfp->scaledForCols = im->width; + vfp->scaledForRows = im->height; +} + +static void processFrame(vf_priv_t *vfp, mp_image_t *im) { + if(vfp->logoAttempts >= vfp->maxLogoAttempts) + return; + + if( + (vfp->darkCapture.captureWait == 0 && vfp->darkCapture.captureCount < vfp->captureCount) + || (vfp->lightCapture.captureWait == 0 && vfp->lightCapture.captureCount < vfp->captureCount) + ) { + CaptureInfo *ci = (CaptureInfo *) NULL; + int yAve = getLogoBorderLuminance(vfp, im); + if(yAve != -1) { + if((unsigned) yAve < vfp->darkThreshold) { + if(vfp->darkCapture.captureWait == 0 && vfp->darkCapture.captureCount < vfp->captureCount) { + ci = &vfp->darkCapture; + } + } else if((unsigned) yAve >vfp->lightThreshold) { + if(vfp->lightCapture.captureWait == 0 && vfp->lightCapture.captureCount < vfp->captureCount) { + ci = &vfp->lightCapture; + } + } + + if(ci) { + mp_image_t *captured = cropMpForm( + vfp, im, vfp->scaledLogoCol, vfp->scaledLogoRow, vfp->scaledLogoCols, vfp->scaledLogoRows + ); + if(vfp->dumpCaptured) { + char filename[PATH_MAX]; + const char *type = ci == &vfp->lightCapture ? "Light" : "Dark"; + sprintf(filename, "%s%s_%d_%d.png", vfp->captureFilePrefix, type, vfp->logoAttempts + 1, ci->captureCount + 1); + writePNGImage(vfp, captured, filename); + mp_msg(MSGT_VFILTER, MSGL_INFO, "\nTranslogo: Created image %s at frame %ld\n", filename, vfp->frame); + } + ci->frameNumber[ci->captureCount] = vfp->frame; + ci->captured[ci->captureCount] = captured; + ci->captureWait = vfp->interCaptureCount; + ci->captureCount++; + } + } + + if(vfp->darkCapture.captureWait != 0) + vfp->darkCapture.captureWait--; + + if(vfp->lightCapture.captureWait != 0) + vfp->lightCapture.captureWait--; + + if(ci && vfp->lightCapture.captureCount == vfp->captureCount && vfp->darkCapture.captureCount == vfp->captureCount) { + RemoveData *rd; + vfp->logoAttempts++; + rd = getLogoRemoveData(vfp, im->width, im->height); + if(rd) { + unsigned modifiedYPixels; + unsigned modifiedCbCrPixels; + unsigned modifiedPixels; + RemoveYData *removeYData; + RemoveCbCrData *removeCbCrData; + + modifiedYPixels = 0; + for(removeYData = rd->yData; removeYData; removeYData = removeYData->next) + modifiedYPixels++; + + modifiedCbCrPixels = 0; + for(removeCbCrData = rd->cbcrData; removeCbCrData; removeCbCrData = removeCbCrData->next) + modifiedCbCrPixels += 4; // Because each change to one byte of the CbCr data affects 4 pixels. + + modifiedPixels = max(modifiedYPixels, modifiedCbCrPixels); + if(modifiedPixels < vfp->minLogoPixels) { + if(vfp->dumpCaptured) { + mp_msg(MSGT_VFILTER, MSGL_INFO, "\nTranslogo: Images %sDark_%d* and %sLight_%d* represent a logo with only %u modified pixels\n", + vfp->captureFilePrefix, + vfp->logoAttempts, + vfp->captureFilePrefix, + vfp->logoAttempts, + modifiedPixels + ); + } + } else if(vfp->dumpCaptured) { + char name[PATH_MAX]; + + sprintf(name, "%s_%d.png", vfp->captureFilePrefix, vfp->logoAttempts); + dumpNoLogo(vfp, rd, name, vfp->frame); + sprintf(name, "%s_%d.tlg", vfp->captureFilePrefix, vfp->logoAttempts); + writeRemoveData(vfp, rd, name); + mp_msg(MSGT_VFILTER, MSGL_INFO, "Translogo: Created logo removal data %s\n", name); + } else { + if(vfp->currentRd == NULL) { + mp_msg(MSGT_VFILTER, MSGL_INFO, "\nTranslogo: Found consistent logo removal data\n"); + + vfp->currentRd = rd; + } else { + RemoveData *discard; + if(vfp->currentRd->divergence < rd->divergence) { + discard = rd; + } else { + mp_msg(MSGT_VFILTER, MSGL_INFO, "\nTranslogo: Found better consistent logo removal data\n"); + discard = vfp->currentRd; + vfp->currentRd = rd; + } + + destroyRemoveData(discard); + } + } + } else if(vfp->dumpCaptured) { + char name[PATH_MAX]; + mp_msg( + MSGT_VFILTER, + MSGL_INFO, "Translogo: Images %sDark_%d* and %sLight_%d* do not produce a consistent result\n", + vfp->captureFilePrefix, + vfp->logoAttempts, + vfp->captureFilePrefix, + vfp->logoAttempts + ); + + sprintf(name, "%s_%d.png", vfp->captureFilePrefix, vfp->logoAttempts); + if(!remove(name)) { + mp_msg(MSGT_VFILTER, MSGL_INFO, "Translogo: Removed exising file %s\n", name); + } + + sprintf(name, "%s_%d.tlg", vfp->captureFilePrefix, vfp->logoAttempts); + if(!remove(name)) { + mp_msg(MSGT_VFILTER, MSGL_INFO, "Translogo: Removed exising file %s\n", name); + } + } + + vfp->lightCapture.captureCount = vfp->darkCapture.captureCount = 0; + } + } +} + +/** + * \brief Find a bounding rectangle based on a threshold. + * + * \param vfp The private data object. + * \param threshold The threshold. + * \param limit The limits to where the rectangle could be found. + * \param rect The rectanle to be stored. + * \param defining If not NULL then the counts of the pixels that define the rectangle to be stored. + * + * Finds the bounding rectangle such that the maximum gradient count for all pixels outside the + * rectangle are less than the threshold, and at least one pixel (the defining pixel) in each edge + * of the rectangle has a maximum gradient count greater than or equal to the threshold. + * + * If the defining parameter is not NULL, then the counts for the four defining pixels are stored + * into it. + * + * If there is no such rectangle, then rect->left and rect->top are to INT_MAX. + */ +static void findRectangle(vf_priv_t *vfp, int threshold, Rectangle *limit, Rectangle *rect, Rectangle *defining, int plane) { + + int left = INT_MAX; + int right = 0; + int top = INT_MAX; + int bottom = 0; + int leftFrames = 0; + int rightFrames = 0; + int bottomFrames = 0; + int topFrames = 0; + + int row; + int width; + int height; + int *planeCounts; + + width = vfp->configWidth; + height = vfp->configHeight; + if(plane != 0) { + width /= 2; + height /= 2; + } + +//mp_msg(MSGT_VFILTER, MSGL_INFO, "\nConsidering %d,%d to %d, %d\n", limit->left, limit->top, limit->right, limit->bottom); + + planeCounts = vfp->gradientCounts[plane]; + for(row = limit->top; row <= limit->bottom; row++) { + int col; + for(col = limit->left; col <= limit->right; col++) { + unsigned gradIndex; + int maxCount = 0; + for(gradIndex = 0; gradIndex < ARC_SEGMENTS; gradIndex++) { + /* + * The count is the sum of the two adjacent segments (modulo ARC_SEGMENTS), because otherwise + * we risk splitting a count of interest between two segments because the gradient direction + * is very close to a segment boundary. + */ + int index = (row * width + col) * ARC_SEGMENTS; + int count = + planeCounts[index + gradIndex] + planeCounts[index + ((gradIndex + 1) & ARC_SEGMENT_MASK)]; + if(count > maxCount) + maxCount = count; + } + + if(maxCount >= threshold) { + if(top == INT_MAX) + top = row; + + if(top == row && maxCount > topFrames) + topFrames = maxCount; + + bottom = row; + if(maxCount > bottomFrames) + bottomFrames = maxCount; + + if(col < left) + left = col; + + if(left == col && maxCount > leftFrames) + leftFrames = maxCount; + + if(col > right) + right = col; + + if(right == col && maxCount > rightFrames) + rightFrames = maxCount; + } + } + + /* + * If the rectangle is too wide to be useful, then bail now, treating the rectangle + * as if it extended from the limit left to the limit right, and down to the limit + * bottom so that it can be used for the limit at a higher threshold. + */ + if(right - left + 1 > width / 3) { + left = limit->left; + right = limit->right; + bottom = limit->bottom; + break; + } + } + + rect->left = left; + rect->top = top; + rect->right = right; + rect->bottom = bottom; + + if(defining) { + defining->left = leftFrames; + defining->top = topFrames; + defining->right = rightFrames; + defining->bottom = bottomFrames; + } +} + +/** + * \brief Compare two rectangles for similarity. + * + * \param first The rectangle derived from a lower threshold. + * \param second The rectangle derived from a higher threshold. + * \param variance The permitted change in the rectangle edges. + * \return TRUE if similar, else FALSE. + */ +static BOOLEAN isRectSimilar(Rectangle *first, Rectangle *second, int variance) { + /* + * The second rectangle cannot be larger than the first, and the edges cannot have moved + * in a direction that would make it larger if the other edges stayed the same, so we + * only have to check for variation in one direction for each edge. + */ + return + first->top - second->top >= -variance + && first->left - second->left >= -variance + && first->right - second->right <= variance + && first->bottom - second->bottom <= variance; +} + +/** + * \brief Dump gradient count information in the form of an image. + * \param vfp The vf_priv_t object + * \param plane The plane to dump. + * \param name The output file name. + * \param threshold If non-zero, then pixels while for counts greater than this, otherwise + * black. If zero, then pixel brightness denote count divided by the number + * of frames processsed. + */ +static void dumpCounts(vf_priv_t *vfp, int plane, const char *name, long threshold) { + int row; + int col; + mp_image_t *im; + + int width = vfp->configWidth; + int height = vfp->configHeight; + if(plane != 0) { + width /= 2; + height /= 2; + } + + im = alloc_mpi(width, height, IMGFMT_YV12); + if(im == NULL) + outOfMemory(vfp); + + for(row = 0; row < height; row++) { + for(col = 0; col < width; col++) { + unsigned char pixel; + + int gradIndex; + int maxCount = 0; + for(gradIndex = 0; gradIndex < ARC_SEGMENTS; gradIndex++) { + int index = (row * width + col) * ARC_SEGMENTS; + int count = + vfp->gradientCounts[plane][index + gradIndex] + vfp->gradientCounts[plane][index + ((gradIndex + 1) & ARC_SEGMENT_MASK)]; + if(count > maxCount) + maxCount = count; + } + + pixel = + threshold == 0 + ? (unsigned char) ((maxCount * 224 / vfp->frame) + 16) + : maxCount >= threshold ? 240 : 16; + im->planes[0][row * im->stride[0] + col] = pixel; + } + } + + for(row = 0; row < height / 2; row++) { + for(col = 0; col < width / 2; col++) { + im->planes[1][row * im->stride[1] + col] = 128; + im->planes[2][row * im->stride[2] + col] = 128; + } + } + + writePNGImage(vfp, im, name); + mp_msg(MSGT_VFILTER, MSGL_INFO, "Translogo: Created file %s\n", name); + + free_mp_image(im); +} + +/* + * \brief Determine whether enough luminance gradient information has been gathered to + * allow a determination of the location of the logo. + * \param vfp The vf_priv_t object. + * \param plane The plane being considered. + * \param logo The rectangle containing the logo, if it is found. + */ +static BOOLEAN checkAutoFindComplete(vf_priv_t *vfp, int plane, Rectangle *logo) { + unsigned requiredGapSize = (unsigned) (sqrt(vfp->frame) * 12); + unsigned secondThreshold; + unsigned threshold = INT_MAX; + unsigned step; + Rectangle firstRect; + Rectangle definingCounts; + Rectangle limit; + int divider; + + /* + * Do not perform check of not at a 128 frame boundary, or too few frames have + * been processed. The 128 frame boundary test is required because this method is + * very expensive, performing it frequently slows things down to little useful effect. + */ + if((vfp->frame & 0x7f) || vfp->frame < MIN_AUTOFIND_FRAMES) + return FALSE; + + /* + * How it works: + * + * Visualise an image consisting of pixels which are white where the pixel's most common gradient + * direction count is less than some threshold, and black otherwise. With the threshold set at zero, + * the entire image would be black. With the threshold set at the number of frames processed plus one, + * the entire image would be white. We consider the smallest rectangle that contains all the black + * pixels at some threshold (as well as some white ones). Initially that rectangle occupies the + * entire image. As the threshold is increased, the size of the rectangle reduces. Eventually, we hope, + * the rectangle is the bounding rectangle of the area containing the logo. Then as we increase the + * threshold further, the bounding rectangle stays the same until the threshold is raised high + * enough that the last pixel on an edge of the rectangle turns white. If the algorithm is + * correctly identifying the logo, then there will be a range of thresholds within which + * the bounding rectangle positon and size does not change (or not by more than a pixel or so). + * If the range is suitably large, then we can assume that we've found the logo. The problem + * thus reduces to finding such a range. + * + * Given that we have a predefined required range size, n, we could start by looking at the + * rectangles with threholds 1 thru n-1, 2 thru n, 3 thru n+1, and so on, but it would + * be prohibitively slow. Instead of doing that we note that if there is a range of thresholds t + * thru t+n-1, such that rectangles are the same, then we'll find a subrange s of size (n + 1)/2 + * within that range by considering pairs of rectangles for 0 and s-1, s and 2s-1, 2s and 3s-1, + * and so on. In the code, n is requiredGapSize, s is step. However, to avoid determining + * the rectangles for adjacent thresholds, we increment by s-1 instead of s, and reuse the + * rectangle determined from the previous iteration. + */ + + /* + * Initial trial threshold and limit. Zero clearly cannot work as a threshold. One is not much more + * likely, but we have to start somewhere. + */ + divider = plane == 0 ? 1 : 2; + limit.top = 1; // The edge pixel definitely has no gradient. + limit.bottom = vfp->configHeight / divider - 2; // Bottom would be -1, but again, edge pixels... + limit.left = 1; + limit.right = vfp->configWidth / divider - 2; + findRectangle(vfp, 1, &limit, &firstRect, NULL, plane); + step = (requiredGapSize + 1) / 2; + secondThreshold = step; // Not step - 1, because we skipped zero. + while(secondThreshold <= vfp->frame) { + Rectangle secondRect; + findRectangle(vfp, secondThreshold, &limit, &secondRect, NULL, plane); + + /* + * Check for a rectangle found and sufficiently similar. + */ + if( + firstRect.left != INT_MAX + && secondRect.left != INT_MAX + && firstRect.right - firstRect.left + 1 < vfp->configWidth / divider / 3 + && firstRect.bottom - firstRect.top + 1 < vfp->configHeight / divider / 3 + && isRectSimilar(&firstRect, &secondRect, 1) + ) { + /* + * We have a candidate. The threshold used is probably not at the + * start of the range, so we need to find the start. We use a binary-cut approach. + */ + Rectangle tempRect1; + Rectangle tempRect2; + int first = secondThreshold - (step - 1); + int begin = first - (step - 1); + if(begin < 1) + begin = 1; // Probably can't happen in practice. +// mp_msg(MSGT_VFILTER, MSGL_INFO, "\nFound candidate at %d\n", first); + + while(begin != first) { + int mid = (begin + first) / 2; + findRectangle(vfp, mid, &limit, &tempRect1, NULL, plane); + if(isRectSimilar(&tempRect1, &secondRect, 1)) { + first = mid; + } else { + begin = mid + 1; + } + } + + /* Found the start, so now just need to see whether the required range works. */ + findRectangle(vfp, begin, &limit, &tempRect1, &definingCounts, plane); + findRectangle(vfp, begin + requiredGapSize - 1, &limit, &tempRect2, &definingCounts, plane); + if(isRectSimilar(&tempRect1, &tempRect2, 2)) { + threshold = begin; + break; + } + + /* Apparently not - so go on looking. */ + } + + limit = firstRect; + firstRect = secondRect; + secondThreshold += step - 1; + } + + if(threshold != INT_MAX) { + firstRect.left *= divider; + firstRect.right *= divider; + firstRect.top *= divider; + firstRect.bottom *= divider; + + /* Provide logo rectangle to caller. */ + *logo = firstRect; + + mp_msg( + MSGT_VFILTER, + MSGL_INFO, + "\nTranslogo: Logo is at %d,%d to %d,%d\n", + firstRect.left, + firstRect.top, + firstRect.right, + firstRect.bottom + ); + mp_msg( + MSGT_VFILTER, + MSGL_INFO, + "Translogo: Logo consistency: %ld%%,%ld%% %ld%%,%ld%% Other consistency: %ld%%\n", + definingCounts.left * 100L / vfp->frame, + definingCounts.top * 100L / vfp->frame, + definingCounts.right * 100L / vfp->frame, + definingCounts.bottom * 100L / vfp->frame, + threshold * 100L / vfp->frame + ); + + if(vfp->dumpGradients) { + const char *pn = plane == 0 ? "luminance" : plane == 1 ? "blue colour difference" : "red colour difference"; + mp_msg(MSGT_VFILTER, MSGL_INFO, "Translogo: Used plane %s\n", pn); + dumpCounts(vfp, 0, "luminance.png", 0); + dumpCounts(vfp, 1, "blue.png", 0); + dumpCounts(vfp, 2, "red.png", 0); + dumpCounts(vfp, plane, "threshold.png", threshold); + } + + return TRUE; + } + return FALSE; +} + +static void autoFindUninit(vf_priv_t *vfp) { + int i; + for(i = 0; i < 3; i++) + free(vfp->gradientCounts[i]); + free(vfp->aTanTab); +} + +/* Black borders are not as abrupt as one might expect. */ +#define FADE_IN 8 + +/* + * \brief Perform processing of the frame for auto-finding the logo. + * \param vfp The vf_priv_t object;; + * \param mpi The image. + * + * This function determines for each pixel in the image which way the gradient points. + * For this purpose, gradients at 180 degrees to each other are considered the same. This is + * because we're using the gradients to find the edges of a logo, and at some times the image + * adjacent to a logo pixel will be brighter than the logo (or colour shifted in one sense), and at + * other times darker (or colour shifted in the other sense), thus * reversing the direction. + * + * The gradients are quantised into ARC_SEGMENTS values. + */ +static void processFrameForAutoFind(vf_priv_t *vfp, mp_image_t *mpi) { + int borderTop; + int borderBottom; + int borderLeft; + int borderRight; + int plane; + int row; + int col; + Rectangle logo; + int fadeIn; + + int width = (int) mpi->width; + int height = (int) mpi->h; + + /* + * Before we do gradient calculation, we want to eliminate the image border, because + * it will have a character similar to that of a logo, which is that the + * gradients have a preferred direction. + */ + unsigned char borderVal = vfp->borderVal; + unsigned int stride = mpi->stride[0]; + + for(row = vfp->ignoreLines; row < height - vfp->ignoreLines; row++) { + unsigned char *yp = mpi->planes[0] + row * stride; + for(col = vfp->ignoreColumns; col < width - vfp->ignoreColumns; col++) { + if(*yp++ > borderVal) + break; + } + + if(col != width - vfp->ignoreColumns) + break; + } + + borderTop = row; + + for(row = height - 1 - vfp->ignoreLines; row >= vfp->ignoreLines; row--) { + unsigned char *yp = mpi->planes[0] + row * stride; + for(col = vfp->ignoreColumns; col < width - vfp->ignoreColumns; col++) { + if(*yp++ > borderVal) + break; + } + + if(col != width - vfp->ignoreColumns) + break; + } + + borderBottom = row; + + for(col = vfp->ignoreColumns; col < width - vfp->ignoreColumns; col++) { + unsigned char *yp = mpi->planes[0] + col; + for(row = vfp->ignoreLines; row < height - vfp->ignoreLines; row++) { + if(*yp > borderVal) + break; + yp += stride; + } + + if(row != height - vfp->ignoreLines) + break; + } + + borderLeft = col; + + for(col = width - 1 - vfp->ignoreColumns; col >= vfp->ignoreColumns; col--) { + unsigned char *yp = mpi->planes[0] + col; + for(row = vfp->ignoreLines; row < height - vfp->ignoreLines; row++) { + if(*yp > borderVal) + break; + yp += stride; + } + + if(row != height - vfp->ignoreLines) + break; + } + + borderRight = col; + + switch(vfp->quadrant) { + case QUADRANT_NW: + borderBottom = height / 2; + borderRight = width / 2; + break; + case QUADRANT_NE: + borderBottom = height / 2; + borderLeft = width / 2; + break; + case QUADRANT_SW: + borderTop = height / 2; + borderRight = width / 2; + break; + case QUADRANT_SE: + borderTop = height / 2; + borderLeft = width / 2; + break; + } + +#if 0 +mp_msg( + MSGT_VFILTER, + MSGL_INFO, "Translogo: %d,%d,%d,%d\n", borderLeft, borderTop, borderRight, borderBottom + ); +#endif + /* + * Now calculate the gradients. We omit a couple of pixels at the edge to further ensure + * we escape influences of the border. + */ + fadeIn = FADE_IN; + for(plane = 0; plane < 3; plane++) { + unsigned int stride = mpi->stride[plane]; + + if(plane == 1) { + /* + * For the chrominance gradients, we have to adjust the border data to account for the fact + * that the chrominance data has only half the resolution. + */ + borderLeft = (borderLeft + 1) / 2; + borderRight = borderRight / 2; + borderTop = (borderTop + 1) / 2; + borderBottom = borderBottom / 2; + + /* Similarly the width (and the height, except that it's not used). */ + width /= 2; + + /* Ditto the fade-in. */ + fadeIn /= 2; + } + + for(row = borderTop + 2; row <= borderBottom - 2; row++) { + unsigned char *imagep; + int *countp; + col = borderLeft + fadeIn; + countp = vfp->gradientCounts[plane] + (row * width + col) * ARC_SEGMENTS; + imagep = mpi->planes[plane] + row * stride + col; + + for(; col <= borderRight - fadeIn; col++) { + /* + * Determine the video gradient. + */ + int topV = imagep[-stride]; + int bottomV = imagep[stride]; + int leftV = imagep[-1]; + int rightV = imagep[1]; + + int xDiff = (leftV - rightV); + int yDiff = (topV - bottomV); + + /* + * Determine which segment the gradient is in, and increment the counters for + * it. We exclude small gradients, including the special case where the gradient is zero + * and therefore has no defined direction. + */ + if(abs(xDiff) > 2|| abs(yDiff) > 2) { + int segment = ATANTAB(vfp, xDiff, yDiff); + countp[segment]++; + } + imagep++; + countp+= ARC_SEGMENTS; + } + } + + /* + * See whether we've captured enough information to determine the location of the logo. + */ + if(checkAutoFindComplete(vfp, plane, &logo)) { + logo.left -= vfp->borderWidth * 2; + if(logo.left < 0) + logo.left = 0; + logo.right += vfp->borderWidth * 2; + if(logo.right >= vfp->configWidth) + logo.right = vfp->configWidth - 1; + + logo.top -= vfp->borderWidth * 2; + if(logo.top < 0) + logo.top = 0; + + logo.bottom += vfp->borderWidth * 2; + if(logo.bottom >= vfp->configHeight) + logo.bottom = vfp->configHeight - 1; + + /* + * Ensure that the column, row, width and height are all even. Note that since + * the right column and bottom row are included in the region, this means that they + * must be odd. + */ + logo.left &= -2; + logo.top &= -2; + logo.right += logo.right % 2 == 0; + logo.bottom += logo.bottom % 2 == 0; + + /* + * Handle improbable case where we're at the edge of the image, and the image is an odd + * number of pixels wide or high. + */ + if(logo.right >= vfp->configWidth) + logo.right -= 2; + + if(logo.bottom >= vfp->configHeight) + logo.bottom -= 2; + + vfp->cropInfo.cropCol = logo.left; + vfp->cropInfo.cropRow = logo.top; + vfp->cropInfo.cropCols = logo.right - logo.left + 1; + vfp->cropInfo.cropRows = logo.bottom - logo.top + 1; + vfp->cropInfo.fullImage = alloc_mpi(vfp->configWidth, vfp->configHeight, IMGFMT_YV12); + copy_mpi(vfp->cropInfo.fullImage, mpi); + + if(vfp->dumpCaptured) { + mp_image_t *croppedImage; + writePNGImage(vfp, vfp->cropInfo.fullImage, vfp->fullImageName); + mp_msg(MSGT_VFILTER, MSGL_INFO, "Translogo: Created full image file %s\n", vfp->fullImageName); + + croppedImage = cropMpForm( + vfp, + vfp->cropInfo.fullImage, + vfp->cropInfo.cropCol, + vfp->cropInfo.cropRow, + vfp->cropInfo.cropCols, + vfp->cropInfo.cropRows + ); + writePNGImage(vfp, croppedImage, vfp->croppedImageName); + mp_msg(MSGT_VFILTER, MSGL_INFO, "Translogo: Created logo region image file %s\n", vfp->croppedImageName); + free_mp_image(croppedImage); + } + + /* Switch to capture mode to gather data about the logo's content. */ + vfp->autoFindMode = FALSE; + autoFindUninit(vfp); + vfp->captureMode = TRUE; + break; // Don't continue with other planes. + } + } +} + +/** + * \brief Process a frame. + * + * \param mpi The image sent to use by the previous filter. + * \param dmpi Where we will store the processed output image. + * \param vf This is how the filter gets access to it's persistant data. + * + * \return The return code of the next filter, or 0 on failure/error. + * + * This function processes an entire frame. The frame is sent by the previous + * filter, has the logo removed by the filter, and is then sent to the next + * filter. + */ +static int put_image(struct vf_instance_s* vf, mp_image_t *mpi, double pts) { + mp_image_t *dmpi; + vf_priv_t *vfp = vf->priv; + jmp_buf exitJmpBuf; + vfp->exitJmpBuf = &exitJmpBuf; + if(setjmp(*vfp->exitJmpBuf)) { + return 0; + } + +if(mpi->h != vfp->configHeight) { + mp_msg(MSGT_GLOBAL, MSGL_INFO, "mpi->h = %d, vfp->configHeight = %d\n", mpi->h, vfp->configHeight); +} + + assert(mpi->h == vfp->configHeight); + assert(mpi->width == vfp->configWidth); + + dmpi = (mp_image_t *)mpi->priv; + if (mpi->flags & MP_IMGFLAG_DRAW_CALLBACK) + dmpi = vf->dmpi; + else { + if(!(mpi->flags&MP_IMGFLAG_DIRECT)){ + dmpi=vf_get_image( + vf->next,mpi->imgfmt, MP_IMGTYPE_EXPORT, 0, mpi->width, mpi->height + ); + vf_clone_mpi_attributes(dmpi, mpi); + dmpi->planes[0]=mpi->planes[0]; + dmpi->planes[1]=mpi->planes[1]; + dmpi->planes[2]=mpi->planes[2]; + dmpi->stride[0]=mpi->stride[0]; + dmpi->stride[1]=mpi->stride[1]; + dmpi->stride[2]=mpi->stride[2]; + dmpi->width=mpi->width; + dmpi->height=mpi->height; + } + } + + vfp->frame++; + + dmpi = vf_get_image( + vf->next, + vfp->fmt, + MP_IMGTYPE_TEMP, + MP_IMGFLAG_ACCEPT_STRIDE, + mpi->w, + mpi->h + ); + + if(!(mpi->flags & MP_IMGFLAG_DIRECT)) { + /* Not filtering in place, so copy from the source to the destination image. */ + memcpy_pic(dmpi->planes[0], mpi->planes[0], mpi->width, mpi->height, dmpi->stride[0], mpi->stride[0]); + memcpy_pic(dmpi->planes[1], mpi->planes[1], mpi->width / 2, mpi->height / 2, dmpi->stride[1], mpi->stride[1]); + memcpy_pic(dmpi->planes[2], mpi->planes[2], mpi->width / 2, mpi->height / 2, dmpi->stride[2], mpi->stride[2]); + dmpi->fields = mpi->fields; + } + + if(vfp->processMode) { + removeLogo(vfp, dmpi, vfp->currentRd); + } else if(vfp->captureMode) { + setScaled(vfp, dmpi); + processFrame(vfp, dmpi); + } else { + processFrameForAutoFind(vfp, dmpi); + } + + return vf_next_put_image(vf, dmpi, pts); +} + +/** + * \brief If the format as one we can handle, then check that the next filter + * can handle it. + */ +static int query_format(struct vf_instance_s * vf, unsigned int fmt) { + vf_priv_t *vfp = vf->priv; + if(!vfp->processMode) { + /* In capture mode, we need YV12. */ + if(fmt != IMGFMT_YV12) + return 0; + } + + /* + * In process mode, we can cope with a variety of YUV formats. Well, hopefully. I've + * only found one example of hardware that uses a packed rather than planar format. The + * functioning of the others depends in the numbers set up by checkFormat being correct. + */ + if(checkFormat(vfp, fmt)) + return vf_next_query_format(vf, fmt); + return 0; +} + +/** + * \brief Find a character in a string, or the nul at the end. + * + * \param s The string. + * \param c The character to search for. + * + * If the character is found, then a pointer to it is returned. Otherwise + * a pointer to the nul terminating byte is returned. + * + * There's A GNU function like this, but it may not always be + * available. + */ +static char *mystrchrnul(char *s, int c) { + char *r = strchr(s, c); + if(!r) + r = s + strlen(s); + + return r; +} + +/** + * \brief Split an option string into its colon separated parts. + * + * \param The options string. + * + * Returns a link list that can be passed to optionsGet and optionsEnd. + */ +static option_t *optionsStart(vf_priv_t *vfp, char *options) { + option_t *head = NULL; + option_t **next = &head; + if(options == NULL) + return head; + + while(*options) { + int nameLen; + char *name; + char *value; + option_t *option; + + char *colon = mystrchrnul(options, ':'); + char *equals = mystrchrnul(options, '='); + if(equals - options > colon - options) + equals = colon; + + nameLen = equals - options; + name = GETMEM(vfp, char, nameLen + 1); + memcpy(name, options, nameLen); + + if(equals == colon) + value = (char *) NULL; + else { + int valueLen = colon - equals - 1; + value = GETMEM(vfp, char, valueLen + 1); + memcpy(value, equals + 1, valueLen); + } + + option = GETMEM(vfp, option_t, 1); + option->name = name; + option->value = value; + *next = option; + next = &option->next; + options = colon + (*colon == ':'); + } + + return head; +} + +/** + * \brief Find an option element. + * + * \param vfp The vf_priv_t object. + * \param head The list of options returned by optionStart. + * \param name The option name. + * \param needsValue If TRUE then a value must be included with the option, + * and otherwise a value must not be included. + * + * Returns the option element if it is found, else NULL. Name + * comparison is case insenstive. Also marks the option as + * having been requested, to that optionEnd does not + * flag it as an error. + */ +option_t *optionFind(vf_priv_t *vfp, option_t *head, const char *name, BOOLEAN needsValue) { + while(head && strcasecmp(head->name, name)) + head = head->next; + if(head != NULL) { + option_t *rest = head; + if(needsValue && head->value == NULL) { + mp_msg(MSGT_VFILTER, MSGL_FATAL, "Translogo: Option %s requires a value\n", name); + longjmp(*vfp->exitJmpBuf, 1); + } else if(!needsValue && head->value != NULL) { + mp_msg(MSGT_VFILTER, MSGL_FATAL, "Translogo: Option %s should not be given a value\n", name); + longjmp(*vfp->exitJmpBuf, 1); + } + + /* Mark the found option and any duplicates as requested. */ + while(rest) { + if(!strcasecmp(rest->name, name)) + rest->requested = TRUE; + rest = rest->next; + } + } + + return head; +} + +/** + * \brief Get the string value of an option. + * + * \param vfp The vf_priv_t object. + * \param head The list returned by optionStart. + * \param name The option name. + * \param defaultValue The default, or NULL for no default. + * + * The value returned, if not NULL, and whether obtained from the options, or defaulted, is a newly + * created string created by calling strdup(). It should ultimately be freed by the caller. + */ +static char *optionGetString(vf_priv_t *vfp, option_t *head, const char *name, const char *defaultValue) { + option_t *opt = optionFind(vfp, head, name, TRUE); + const char *ret = opt ? opt->value : defaultValue; + + return ret ? strdup(ret) : NULL; +} + +/** + * \brief Get the signed value of an option expressed as decimal number. + * + * \param vfp The vf_priv_t object. + * \param head The list returned by optionStart. + * \param name The option name. + * \param defaultValue The value if the option is not present. + * \param minValue The minimum permitted value. + * \param maxValue The maximum permitted value. + * + * The default value can lie outside the minValue and maxValue range. + */ +#if 0 +static long optionGetSigned(vf_priv_t *vfp, option_t *head, const char *name, long defaultValue, long minValue, long maxValue) { + option_t *opt = optionFind(vfp, head, name, TRUE); + long retValue; + if(opt) { + unsigned len; + int count = sscanf(opt->value, "%ld%n", &retValue, &len); + if(count < 1 || len != strlen(opt->value)) { + mp_msg(MSGT_VFILTER, MSGL_FATAL, "Translogo: Invalid value %s for option %s\n", opt->value, name); + longjmp(*vfp->exitJmpBuf, 1); + } + + if(retValue < minValue || retValue > maxValue) { + mp_msg(MSGT_VFILTER, MSGL_FATAL, "Translogo: Invalid value %s for option %s - should be between %ld and %ld\n", opt->value, name, minValue, maxValue); + longjmp(*vfp->exitJmpBuf, 1); + } + } else { + retValue = defaultValue; + } + + return retValue; +} +#endif + +/** + * \brief Get the unsigned value of an option expressed as decimal number. + * + * \param vfp The vf_priv_t object. + * \param head The list returned by optionStart. + * \param name The option name. + * \param defaultValue The value if the option is not present. + * \param minValue The minimum permitted value. + * \param maxValue The maximum permitted value. + * + * The minValue and maxValue parameters are ignored if they are both zero. + * + * The default value can lie outside the minValue and maxValue range. + */ +static unsigned long optionGetUnsigned(vf_priv_t *vfp, option_t *head, const char *name, unsigned long defaultValue, unsigned long minValue, unsigned long maxValue) { + option_t *opt = optionFind(vfp, head, name, TRUE); + unsigned long retValue; + if(opt) { + unsigned len; + int count = sscanf(opt->value, "%lu%n", &retValue, &len); + if(count < 1 || len != strlen(opt->value)) { + mp_msg(MSGT_VFILTER, MSGL_FATAL, "Translogo: Invalid value %s for Option %s\n", opt->value, name); + longjmp(*vfp->exitJmpBuf, 1); + } + + if(retValue < minValue || retValue > maxValue) { + mp_msg(MSGT_VFILTER, MSGL_FATAL, "Translogo: Invalid value %s for Option %s - should be between %lu and %lu\n", opt->value, name, minValue, maxValue); + longjmp(*vfp->exitJmpBuf, 1); + } + } else { + retValue = defaultValue; + } + + return retValue; +} + +/** + * \brief Determine whether an option is present. + * + * \param head The list returned by optionStart. + * \param name The name of the option. + * \param valueRequired TRUE if the option must have a value, and FALSE if it must not. + * + * Return TRUE if the option is present, else FALSE. + */ +static BOOLEAN optionIsPresent(vf_priv_t *vfp, option_t *head, const char *name, BOOLEAN valueRequired) { + return optionFind(vfp, head, name, valueRequired) != NULL; +} + +/** + * \brief Terminate option processing. + * + * \param vfp The vf_priv_t object. + * + * \param head The list returned by optionStart. + * + * This function reports the existence of any option that have not been requested, + * and uses the longjmp exit to vfp->exitJmpBuf if there are any. The list of + * options is discarded and the options... methods can no longer be called. + */ +static void optionEnd(vf_priv_t *vfp, option_t *head) { + char *badName = NULL; + while(head) { + option_t *nextHead; + if(!head->requested && !badName) + badName = strdup(head->name); + + nextHead = head->next; + free(head->name); + if(head->value) + free(head->value); + free(head); + head = nextHead; + } + + if(badName) { + mp_msg(MSGT_VFILTER, MSGL_FATAL, "Translogo: Unknown option %s\n", badName); + free(badName); + longjmp(*vfp->exitJmpBuf, 1); + } +} + +/** + * \brief Add options from a file. + * + * \param vfp The private data. + * \param fileName The file name + * \param head The had of the options list. + * + * Reads options from a file, and adds them to the tail of the options list. + */ +static void optionAddFromFile(vf_priv_t *vfp, const char *fileName, option_t **head) { + char name[1024]; + char value[1024]; + char buf[1024]; + char *fullPath = NULL; + option_t **next; + FILE *f; + int c; + int line = 0; + + /* Try first using the specified path. */ + f = fopen(fileName, "r"); + if(!f) { + /* + * Not found. If the path is just a name, then try using the path + * specified by mencoder's get_path method. + */ + if(strchr(fileName, '/') == NULL && strchr(fileName, '\\') == NULL) { + /* Just a name. */ + fullPath = get_path(fileName); + fileName = fullPath; + } + + f = fopen(fileName, "r"); + } + + if(!f) { + if(fullPath != NULL) + free(fullPath); + mp_msg(MSGT_VFILTER, MSGL_FATAL, "Tranlogo: Cannot open file %s: %s\n", fileName, strerror(errno)); + longjmp(*vfp->exitJmpBuf, 1); + } + + /* Find the tail of the list. */ + next = head; + while(*next) { + next = &(*next)->next; + } + + for(;;) { + int count; + option_t *option; + + line++; + while(c = fgetc(f), isspace(c)) {} + if(c == EOF) + break; + + /* Blank lines are ignored. */ + if(c == '\n') + continue; + + /* Lines with # as the first non-blank character are ignored. */ + if(c == '#') { + while(c != '\n' && c != EOF) + c = fgetc(f); + continue; + } + + ungetc(c, f); + buf[0] = 0; + fgets(buf, sizeof(buf), f); + + count = sscanf(buf, "%1023[^ \t\n=] = %1023s", name, value); + if(count <= 0) { + mp_msg( + MSGT_VFILTER, + MSGL_FATAL, + "Translogo: Error in file %s at about line %d: Did not expect character '%c'\n", + fileName, + line, + c + ); + fclose(f); + if(fullPath != NULL) + free(fullPath); + longjmp(*vfp->exitJmpBuf, 1); + } + + option = GETMEM(vfp, option_t, 1); + option->name = strdup(name); + if(count > 1) + option->value = strdup(value); + *next = option; + next = &option->next; + } + + if(fullPath != NULL) + free(fullPath); + fclose(f); +} + +#define MAX_COLOUR_DIFFERENCE 10 +#define MAX_LUMINANCE_DIFFERENCE 6 +#define MINIMUM_AREA_SIZE 50 + +static void destroyCaptureInfo(CaptureInfo ci) { + unsigned i; + for(i = 0; i < ci.captureCount; i++) { + free_mp_image(ci.captured[i]); + } + + free(ci.captured); + free(ci.frameNumber); +} + +static void uninit(vf_instance_t *vf) { + vf_priv_t *vfp = vf->priv; + jmp_buf exitJmpBuf; + vfp->exitJmpBuf = &exitJmpBuf; + if(setjmp(*vfp->exitJmpBuf)) + return; + + if(vfp->captureMode) { + if(!vfp->dumpCaptured) { + if(!vfp->currentRd && !vfp->autoFindMode) { + mp_msg(MSGT_VFILTER, MSGL_WARN, "Translogo: Could not find consistent logo removal data\n"); + } else { + const char *dotPos; + char name[PATH_MAX]; + writeRemoveData(vfp, vfp->currentRd, vfp->tlgFile); + mp_msg(MSGT_VFILTER, MSGL_INFO, "Translogo: Created logo removal data %s\n", vfp->tlgFile); + dotPos = mystrchrnul(vfp->fullImageName, '.'); + sprintf(name, "%.*sNoLogo.png", (int) (dotPos - vfp->fullImageName), vfp->fullImageName); + dumpNoLogo(vfp, vfp->currentRd, name, -1); + } + } + + destroyCaptureInfo(vfp->lightCapture); + destroyCaptureInfo(vfp->darkCapture); + + free_mp_image(vfp->scaledImage); + } + + if(vfp->autoFindMode) { + mp_msg(MSGT_VFILTER, MSGL_WARN, "Translogo: Could not determine where the logo is located.\n"); + if(vfp->dumpGradients) { + dumpCounts(vfp, 0, "luminance.png", 0); + dumpCounts(vfp, 1, "blue.png", 0); + dumpCounts(vfp, 2, "red.png", 0); + } + autoFindUninit(vfp); + } + + if(vfp->currentRd) { + // Destroy all the remove data objects. In process mode they are linked in a circular list. + // In capture mode there is at most one, and its next element is NULL. + RemoveData *first = vfp->currentRd; + RemoveData *rd = first->next; + destroyRemoveData(first); + while(rd && rd != first) { + RemoveData *nextRd = rd->next; + destroyRemoveData(rd); + rd = nextRd; + } + } + + /* Destroy our private structure that had been used to store those masks and images. */ + free(vfp); +} + +static int logo_control_cb(int key) { + vf_priv_t *vfp = logoControlCtx; + char *newName = NULL; + RemoveData *rd = vfp->currentRd; + + jmp_buf exitJmpBuf; + vfp->exitJmpBuf = &exitJmpBuf; + if(setjmp(*vfp->exitJmpBuf)) { + if(newName) + free(newName); + return 1; + } + + switch(key) { + case KEY_LEFT: + if(rd->column != 0) + rd->column--; + break; + case KEY_RIGHT: + if(rd->column + rd->maxColumnOffset < vfp->configWidth) + rd->column++; + break; + case KEY_UP: + if(rd->row != 0) + rd->row--; + break; + case KEY_DOWN: + if(rd->row + rd->maxRowOffset < vfp->configHeight) + rd->row++; + break; + case 'S': + case 's': { + newName = malloc(strlen(rd->fileName) + 5); + strcpy(newName, rd->fileName); + strcat(newName, ".new"); + remove(newName); + writeRemoveData(vfp, rd, newName); +#ifdef WIN32 + remove(rd->fileName); // Cannot rename over an existing file under Windows. +#endif + if(rename(newName, rd->fileName)) { + mp_msg(MSGT_VFILTER, MSGL_INFO, "Translogo: cannot rename %s as %s\n", newName, rd->fileName); + remove(newName); + } + + free(newName); + break; + } + case KEY_PAGE_UP: + case KEY_KP9: + { + RemoveData *rd = vfp->currentRd; + while(rd->next != vfp->currentRd) + rd = rd->next; + vfp->currentRd = rd; + break; + } + case KEY_KP3: + case KEY_PAGE_DOWN: + vfp->currentRd = vfp->currentRd->next; + break; + case KEY_ENTER: + case KEY_KPENTER: + mp_input_key_cb = NULL; + break; + default: + set_osd_msg(OSD_MSG_TRANSLOGO, 1, osd_duration, "Translogo"); + mp_msg(MSGT_VFILTER, MSGL_INFO, "Key not meaningful to Translogo%c\n", (char) 7); + } + + return 1; +} + +static int control (vf_instance_t *vf, int request, void *data) +{ + vf_priv_t *vfp = vf->priv; + if(vfp->processMode && request==VFCTRL_LOGO_CONTROL) { + logoControlCtx = vfp; + mp_input_key_cb = logo_control_cb; + set_osd_msg(OSD_MSG_TRANSLOGO, 1, osd_duration, "Translogo"); + return CONTROL_TRUE; + } + return vf_next_control (vf, request, data); +} + +/** + * \brief Initializes our filter. + * + * \param args The arguments passed in from the command line go here. This + * filter expects only a single argument telling it where the PGM + * or PPM file that describes the logo region is. + * + * This sets up our instance variables and parses the arguments to the filter. + */ +static int open(vf_instance_t * vf, char * args) { + vf_priv_t *vfp; + BOOLEAN maxLogoAttemptsSet; + BOOLEAN noAutoFind; + char *quadrant; + unsigned maxYFactorVar; + option_t *head; + jmp_buf exitJmpBuf; + char *fromFile; + char *saveArgs = args; // Using args lower down produces warnings about its being clobbered by + // longjmp or vfork. + + vf->priv = (vf_priv_t *) calloc(sizeof(vf_priv_t) , 1); + vfp = vf->priv; + if(vfp == NULL) { + errMessage("Out of memory"); + exit(1); + } + + vfp->exitJmpBuf = &exitJmpBuf; + if(setjmp(*vfp->exitJmpBuf)) { + if(!vfp->noAbort) + exit(1); + return 0; + } + + maxLogoAttemptsSet = FALSE; + + head = optionsStart(vfp, saveArgs); + vfp->noAbort = optionIsPresent(vfp, head, "noAbort", FALSE); + + fromFile = optionGetString(vfp, head, "fromFile", NULL); + if(fromFile) + optionAddFromFile(vfp, fromFile, &head); + + vfp->mode = optionGetString(vfp, head, "mode", "process"); + noAutoFind = optionIsPresent(vfp, head, "noAutoFind", FALSE); + vfp->tlgFile = optionGetString(vfp, head, "tlg", NULL); + vfp->interCaptureCount = (unsigned) optionGetUnsigned(vfp, head, "interCapture", 1, 0, UINT_MAX); + + vfp->maxBorderVariation = (unsigned) optionGetUnsigned(vfp, head, "maxBorderVar", DEFAULT_MAX_BORDER_VARIATION, 1, 255); + vfp->captureCount = (unsigned) optionGetUnsigned(vfp, head, "captureCount", 2, 2, UINT_MAX); + maxYFactorVar = (unsigned) optionGetUnsigned(vfp, head, "maxLumincanceFactorVar", DEFAULT_MAX_YFACTOR_VAR, 0, 100); + vfp->maxYFactorVar = maxYFactorVar / 100.0; + vfp->maxYAddVar = (unsigned) optionGetUnsigned(vfp, head, "maxLuminanceVar", DEFAULT_MAX_YADD_VAR, 0, 255); + + if(optionIsPresent(vfp, head, "maxColorAddVar", TRUE)) + vfp->maxCbCrAddVar = (unsigned) optionGetUnsigned(vfp, head, "maxColorAddVar", DEFAULT_MAX_CBCR_ADD_VAR, 0, 255); + vfp->maxCbCrAddVar = (unsigned) optionGetUnsigned(vfp, head, "maxColourAddVar", DEFAULT_MAX_CBCR_ADD_VAR, 0, 255); // 'Cos I'm English. + + vfp->borderWidth = optionGetUnsigned(vfp, head, "borderWidth", DEFAULT_BORDER_WIDTH, 1, UINT_MAX); + vfp->dumpCaptured = optionIsPresent(vfp, head, "dumpCapture", FALSE); + vfp->dumpGradients = optionIsPresent(vfp, head, "dumpGradients", FALSE); + + vfp->captureFilePrefix = optionGetString(vfp, head, "capturePrefix", "capture"); + vfp->lightThreshold = (unsigned) optionGetUnsigned(vfp, head, "lightThreshold", 110, 0, 255); + vfp->darkThreshold = (unsigned) optionGetUnsigned(vfp, head, "darkThreshold", vfp->lightThreshold / 2, 0, vfp->lightThreshold - 1); + + vfp->fullImageName = optionGetString(vfp, head, "fullImage", NULL); + vfp->croppedImageName = optionGetString(vfp, head, "croppedImage", NULL); + vfp->minLogoPixels = (unsigned) optionGetUnsigned(vfp, head, "minLogoPixels", 50, 1, UINT_MAX); + vfp->ignoreLines = (int) optionGetUnsigned(vfp, head, "ignoreLines", 0, 0, INT_MAX); + vfp->ignoreColumns = (int) optionGetUnsigned(vfp, head, "ignoreColumns", 0, 0, INT_MAX); + vfp->maxLogoAttempts = (unsigned) optionGetUnsigned(vfp, head, "maxLogoAttempts", 5, 1, UINT_MAX); + if(optionIsPresent(vfp, head, "maxLogoAttempts", TRUE)) + maxLogoAttemptsSet = TRUE; + vfp->borderVal = (unsigned char) optionGetUnsigned(vfp, head, "maxBorderVal", 50, 0, 255); + quadrant = optionGetString(vfp, head, "quadrant", "all"); + + optionEnd(vfp, head); + + if(!strcasecmp(vfp->mode, "capture")) { + if(noAutoFind) + vfp->captureMode = TRUE; + else + vfp->autoFindMode = TRUE; + + if(!maxLogoAttemptsSet) + vfp->maxLogoAttempts = (unsigned) -1; // Unlimited attempts. + + if(!vfp->dumpCaptured) { + if(!vfp->tlgFile) { + mp_msg(MSGT_VFILTER, MSGL_FATAL, "Translogo: Missing option \"tlg\"\n"); + longjmp(*vfp->exitJmpBuf, 1); + } + } + + if(noAutoFind || vfp->dumpCaptured) { + if(!vfp->fullImageName) { + mp_msg(MSGT_VFILTER, MSGL_FATAL, "Translogo: Missing option \"fullImage\"\n"); + longjmp(*vfp->exitJmpBuf, 1); + } + + if(!vfp->croppedImageName) { + mp_msg(MSGT_VFILTER, MSGL_FATAL, "Translogo: Missing option \"croppedImage\"\n"); + longjmp(*vfp->exitJmpBuf, 1); + } + } + + vfp->lightCapture.captured = GETMEM(vfp, mp_image_t *, vfp->captureCount); + vfp->lightCapture.frameNumber = GETMEM(vfp, long, vfp->captureCount); + vfp->darkCapture.captured = GETMEM(vfp, mp_image_t *, vfp->captureCount); + vfp->darkCapture.frameNumber = GETMEM(vfp, long, vfp->captureCount); + + if(noAutoFind) { + vfp->cropInfo = getCropInfo(vfp, vfp->fullImageName, vfp->croppedImageName); + mp_msg( + MSGT_VFILTER, + MSGL_INFO, + "Translogo: The cropped region is at %d, %d and has size %d * %d\n", + vfp->cropInfo.cropCol, + vfp->cropInfo.cropRow, + vfp->cropInfo.cropCols, + vfp->cropInfo.cropRows + ); + } + + if(!strcasecmp(quadrant, "all")) { + vfp->quadrant = QUADRANT_ALL; + } else if(!strcasecmp(quadrant, "nw")) { + vfp->quadrant = QUADRANT_NW; + } else if(!strcasecmp(quadrant, "ne")) { + vfp->quadrant = QUADRANT_NE; + } else if(!strcasecmp(quadrant, "sw")) { + vfp->quadrant = QUADRANT_SW; + } else if(!strcasecmp(quadrant, "se")) { + vfp->quadrant = QUADRANT_SE; + } else { + mp_msg(MSGT_VFILTER, MSGL_FATAL, "Translogo: Invalid quadrant: %s\n", quadrant); + longjmp(*vfp->exitJmpBuf, 1); + } + } else if(!strcasecmp(vfp->mode, "process")) { + RemoveData **rdp; + char *name; + char dash; + char *cp; + + vfp->processMode = TRUE; + if(!vfp->tlgFile) { + mp_msg(MSGT_VFILTER, MSGL_FATAL, "Translogo: Missing option \"tlg\"\n"); + longjmp(*vfp->exitJmpBuf, 1); + } + + rdp = &vfp->currentRd; + name = strdup(vfp->tlgFile); + cp = name; + do { + char *dashLoc = mystrchrnul(cp, '-'); + dash = *dashLoc; + *dashLoc = 0; + *rdp = readRemoveData(vfp, cp); + rdp = &(*rdp)->next; + cp = dashLoc + 1; + } while(dash); + *rdp = vfp->currentRd; + free(name); + + } else { + mp_msg(MSGT_VFILTER, MSGL_FATAL, "Translogo: The mode must be \"capture\" or \"process\", not \"%s\"\n", vfp->mode); + longjmp(*vfp->exitJmpBuf, 1); + } + + vf->config=config; + vf->put_image=put_image; + vf->query_format=query_format; + vf->uninit=uninit; + vf->default_reqs=VFCAP_ACCEPT_STRIDE; + vf->control = control; + + return 1; +} + +/** + * \brief Meta data about our filter. + */ +vf_info_t vf_info_translogo = { + "Removes a transparent TV logo.", + "translogo", + "Sylvia Else", + "", + open, + NULL +}; diff -NpuBr -Xexcludes.txt asLoadedMPlayer/Makefile MPlayerWithTranslogo/Makefile --- asLoadedMPlayer/Makefile 2009-07-01 14:15:09.000000000 +1000 +++ MPlayerWithTranslogo/Makefile 2009-07-02 13:31:37.000000000 +1000 @@ -143,6 +143,7 @@ SRCS_COMMON-$(LIBAVCODEC) += libmpcodecs/vf_lavc.c \ libmpcodecs/vf_lavcdeint.c \ libmpcodecs/vf_screenshot.c \ + libmpcodecs/vf_translogo.c \ # These filters use private headers and do not work with shared libavcodec. SRCS_COMMON-$(LIBAVCODEC_A) += libaf/af_lavcac3enc.c \ diff -NpuBr -Xexcludes.txt asLoadedMPlayer/mencoder.c MPlayerWithTranslogo/mencoder.c --- asLoadedMPlayer/mencoder.c 2009-07-02 13:37:43.000000000 +1000 +++ MPlayerWithTranslogo/mencoder.c 2009-07-02 13:39:14.000000000 +1000 @@ -1714,3 +1714,23 @@ static int edl_seek(edl_record_ptr next_ return slowseek(next_edl_record->stop_sec, demuxer->video, d_audio, mux_a, frame_data, framecopy, 1); } + +/* Dummy stuff required by vf_translogo.c. */ +int (*mp_input_key_cb)(int); + +int osd_duration; + +void +set_osd_msg(int id, int level, int time, const char* fmt, ...) {} + +void +exit_player() { + exit(1); +} + +void +set_filter_provides_sync() {} + +void +rm_osd_msg(int id) {} + diff -NpuBr -Xexcludes.txt asLoadedMPlayer/mp_osd.h MPlayerWithTranslogo/mp_osd.h --- asLoadedMPlayer/mp_osd.h 2009-07-01 14:15:09.000000000 +1000 +++ MPlayerWithTranslogo/mp_osd.h 2009-07-02 13:18:47.000000000 +1000 @@ -6,6 +6,7 @@ #define OSD_MSG_SUB_DELAY 2 #define OSD_MSG_SPEED 3 #define OSD_MSG_OSD_STATUS 4 +#define OSD_MSG_TRANSLOGO 8 #define OSD_MSG_BAR 5 #define OSD_MSG_PAUSE 6 #define OSD_MSG_RADIO_CHANNEL 7