diff mbox

[FFmpeg-devel,PATCHv3] add signature filter for MPEG7 video signature

Message ID 2644280.rG1UgZZ4cT@gump
State Superseded
Headers show

Commit Message

Gerion Entrup Jan. 4, 2017, 4:05 p.m. UTC
On Dienstag, 3. Januar 2017 16:58:32 CET Moritz Barsnick wrote:
> > > The English opposite of "fine" is "coarse", not "course". :)
> > Oops.
> 
> You still have a few "courses". (The actual variables, not the types. I
> don't care *too* much, but might be better for consistency.)
You're right. Fixed version attached.


> From my side - mostly style and docs - it looks fine. Technically, I
> can't judge too much. You went through a long review cycle already, so
> I assume it's fine for those previous reviewers. They have the last
> call anyway.

What about FATE? I'm willing to write a test, but don't know the best way. 
There are official conditions, whether the signature is standard compliant. I've 
written a python script to proof that (see previous emails). Nevertheless the 
checks take relatively long and need 3GB inputvideo, so I assume this is not 
usable for FATE.

One way would be, to take a short input video, take the calculated signature 
and use this as reference, but the standard allow some bitflips and my code
has some of them in comparison to the reference signatures.

Gerion

Comments

Michael Niedermayer Jan. 5, 2017, 1:26 a.m. UTC | #1
On Wed, Jan 04, 2017 at 05:05:41PM +0100, Gerion Entrup wrote:
> On Dienstag, 3. Januar 2017 16:58:32 CET Moritz Barsnick wrote:
> > > > The English opposite of "fine" is "coarse", not "course". :)
> > > Oops.
> > 
> > You still have a few "courses". (The actual variables, not the types. I
> > don't care *too* much, but might be better for consistency.)
> You're right. Fixed version attached.
> 
> 
> > From my side - mostly style and docs - it looks fine. Technically, I
> > can't judge too much. You went through a long review cycle already, so
> > I assume it's fine for those previous reviewers. They have the last
> > call anyway.
> 
> What about FATE? I'm willing to write a test, but don't know the best way. 
> There are official conditions, whether the signature is standard compliant. I've 
> written a python script to proof that (see previous emails). Nevertheless the 
> checks take relatively long and need 3GB inputvideo, so I assume this is not 
> usable for FATE.

yes, a 3gb reference is not ok for fate


> 
> One way would be, to take a short input video, take the calculated signature 
> and use this as reference, but the standard allow some bitflips and my code
> has some of them in comparison to the reference signatures.

then the fate test could/should compare and pass if its within what
we expect of our code. (which may be stricter than the reference
allowance)
there are already tests that do similar for comparing PCM/RAW


[...]

> +#define OFFSET(x) offsetof(SignatureContext, x)

> +#define FLAGS AV_OPT_FLAG_VIDEO_PARAM

should contin also  AV_OPT_FLAG_FILTERING_PARAM

[...]
> +static int export(AVFilterContext *ctx, StreamContext *sc, int input)
> +{
> +    SignatureContext* sic = ctx->priv;
> +    char filename[1024];
> +
> +    if (sic->nb_inputs > 1) {

> +        /* error already handled */
> +        av_get_frame_filename(filename, sizeof(filename), sic->filename, input);

its more robust to use a av_assert*() on the return code if its assumed
to be never failing than just a comment as a comment cannot be
automatcially checked for validity currently.

[...]
diff mbox

Patch

From 0eaf0e247aa01a469624c713d4b106472b99c92a Mon Sep 17 00:00:00 2001
From: Gerion Entrup <gerion.entrup@flump.de>
Date: Mon, 2 Jan 2017 02:08:57 +0100
Subject: [PATCH] add signature filter for MPEG7 video signature

This filter does not implement all features of MPEG7. Missing features:
- compression of signature files
- work only on (cropped) parts of the video
---
 Changelog                      |   1 +
 configure                      |   1 +
 doc/filters.texi               |  89 +++++
 libavfilter/Makefile           |   1 +
 libavfilter/allfilters.c       |   1 +
 libavfilter/signature.h        | 569 ++++++++++++++++++++++++++++++
 libavfilter/signature_lookup.c | 573 ++++++++++++++++++++++++++++++
 libavfilter/version.h          |   2 +-
 libavfilter/vf_signature.c     | 768 +++++++++++++++++++++++++++++++++++++++++
 9 files changed, 2004 insertions(+), 1 deletion(-)
 create mode 100644 libavfilter/signature.h
 create mode 100644 libavfilter/signature_lookup.c
 create mode 100644 libavfilter/vf_signature.c

diff --git a/Changelog b/Changelog
index aff9ab0..687131c 100644
--- a/Changelog
+++ b/Changelog
@@ -12,6 +12,7 @@  version <next>:
 - 16.8 floating point pcm decoder
 - 24.0 floating point pcm decoder
 - Apple Pixlet decoder
+- MPEG-7 Video Signature filter
 
 version 3.2:
 - libopenmpt demuxer
diff --git a/configure b/configure
index f035f35..3e68a44 100755
--- a/configure
+++ b/configure
@@ -3126,6 +3126,7 @@  showspectrum_filter_deps="avcodec"
 showspectrum_filter_select="fft"
 showspectrumpic_filter_deps="avcodec"
 showspectrumpic_filter_select="fft"
+signature_filter_deps="gpl avcodec avformat"
 smartblur_filter_deps="gpl swscale"
 sofalizer_filter_deps="netcdf avcodec"
 sofalizer_filter_select="fft"
diff --git a/doc/filters.texi b/doc/filters.texi
index 42cdd2e..1722d65 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -12543,6 +12543,95 @@  saturation maximum: %@{metadata:lavfi.signalstats.SATMAX@}
 @end example
 @end itemize
 
+@anchor{signature}
+@section signature
+
+Calculates the MPEG-7 Video Signature. The filter can handle more than one
+input. In this case the matching between the inputs can be calculated additionally.
+The filter always passes through the first input. The signature of each stream can
+be written into a file.
+
+It accepts the following options:
+
+@table @option
+@item detectmode
+Enable or disable the matching process.
+
+Available values are:
+
+@table @samp
+@item off
+Disable the calculation of a matching (default).
+@item full
+Calculate the matching for the whole video and output whether the whole video
+matches or only parts.
+@item fast
+Calculate only until a matching is found or the video ends. Should be faster in
+some cases.
+@end table
+
+@item nb_inputs
+Set the number of inputs. The option value must be a non negative integer.
+Default value is 1.
+
+@item filename
+Set the path to which the output is written. If there is more than one input,
+the path must be a prototype, i.e. must contain %d or %0nd (where n is a positive
+integer), that will be replaced with the input number. If no filename is
+specified, no output will be written. This is the default.
+
+@item format
+Choose the output format.
+
+Available values are:
+
+@table @samp
+@item binary
+Use the specified binary representation (default).
+@item xml
+Use the specified xml representation.
+@end table
+
+@item th_d
+Set threshold to detect one word as similar. The option value must be an integer
+greater than zero. The default value is 9000.
+
+@item th_dc
+Set threshold to detect all words as similar. The option value must be an integer
+greater than zero. The default value is 60000.
+
+@item th_xh
+Set threshold to detect frames as similar. The option value must be an integer
+greater than zero. The default value is 116.
+
+@item th_di
+Set the minimum length of a sequence in frames to recognize it as matching
+sequence. The option value must be a non negative integer value.
+The default value is 0.
+
+@item th_it
+Set the minimum relation, that matching frames to all frames must have.
+The option value must be a double value between 0 and 1. The default value is 0.5.
+@end table
+
+@subsection Examples
+
+@itemize
+@item
+To calculate the signature of an input video and store it in signature.bin:
+@example
+ffmpeg -i input.mkv -vf signature=filename=signature.bin -map 0:v -f null -
+@end example
+
+@item
+To detect whether two videos match and store the signatures in XML format in
+signature0.xml and signature1.xml:
+@example
+ffmpeg -i input1.mkv -i input2.mkv -filter_complex "[0:v][1:v] signature=nb_inputs=2:detectmode=full:format=xml:filename=signature%d.xml" -map :v -f null -
+@end example
+
+@end itemize
+
 @anchor{smartblur}
 @section smartblur
 
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 9ab65eb..32b4f6a 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -275,6 +275,7 @@  OBJS-$(CONFIG_SHUFFLEFRAMES_FILTER)          += vf_shuffleframes.o
 OBJS-$(CONFIG_SHUFFLEPLANES_FILTER)          += vf_shuffleplanes.o
 OBJS-$(CONFIG_SIDEDATA_FILTER)               += f_sidedata.o
 OBJS-$(CONFIG_SIGNALSTATS_FILTER)            += vf_signalstats.o
+OBJS-$(CONFIG_SIGNATURE_FILTER)              += vf_signature.o
 OBJS-$(CONFIG_SMARTBLUR_FILTER)              += vf_smartblur.o
 OBJS-$(CONFIG_SOBEL_FILTER)                  += vf_convolution.o
 OBJS-$(CONFIG_SPLIT_FILTER)                  += split.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index 2c37818..1987d53 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -290,6 +290,7 @@  void avfilter_register_all(void)
     REGISTER_FILTER(SHUFFLEPLANES,  shuffleplanes,  vf);
     REGISTER_FILTER(SIDEDATA,       sidedata,       vf);
     REGISTER_FILTER(SIGNALSTATS,    signalstats,    vf);
+    REGISTER_FILTER(SIGNATURE,      signature,      vf);
     REGISTER_FILTER(SMARTBLUR,      smartblur,      vf);
     REGISTER_FILTER(SOBEL,          sobel,          vf);
     REGISTER_FILTER(SPLIT,          split,          vf);
diff --git a/libavfilter/signature.h b/libavfilter/signature.h
new file mode 100644
index 0000000..2659c87
--- /dev/null
+++ b/libavfilter/signature.h
@@ -0,0 +1,569 @@ 
+/*
+ * Copyright (c) 2017 Gerion Entrup
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg 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.
+ *
+ * FFmpeg 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 FFmpeg; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/**
+ * @file
+ * MPEG-7 video signature calculation and lookup filter
+ */
+
+#ifndef AVFILTER_SIGNATURE_H
+#define AVFILTER_SIGNATURE_H
+
+#include <float.h>
+#include "libavutil/common.h"
+#include "libavutil/opt.h"
+#include "libavutil/timestamp.h"
+#include "avfilter.h"
+#include "internal.h"
+
+#define ELEMENT_COUNT 10
+#define SIGELEM_SIZE 380
+#define DIFFELEM_SIZE 348 /* SIGELEM_SIZE - elem_a1 - elem_a2 */
+#define COARSE_SIZE 90
+
+enum lookup_mode {
+    MODE_OFF,
+    MODE_FULL,
+    MODE_FAST,
+    NB_LOOKUP_MODE
+};
+
+enum formats {
+    FORMAT_BINARY,
+    FORMAT_XML,
+    NB_FORMATS
+};
+
+typedef struct {
+    uint8_t x;
+    uint8_t y;
+} Point;
+
+typedef struct {
+    Point up;
+    Point to;
+} Block;
+
+typedef struct {
+    int av_elem; /* average element category */
+    short left_count; /* count of blocks that will be added together */
+    short block_count; /* count of blocks per element */
+    short elem_count;
+    const Block* blocks;
+} ElemCat;
+
+typedef struct FineSignature {
+    struct FineSignature* next;
+    struct FineSignature* prev;
+    uint64_t pts;
+    uint32_t index; /* needed for xmlexport */
+    uint8_t confidence;
+    uint8_t words[5];
+    uint8_t framesig[SIGELEM_SIZE/5];
+} FineSignature;
+
+typedef struct CoarseSignature {
+    uint8_t data[5][31]; /* 5 words with min. 243 bit */
+    struct FineSignature* first; /* associated Finesignatures */
+    struct FineSignature* last;
+    struct CoarseSignature* next;
+} CoarseSignature;
+
+/* lookup types */
+typedef struct MatchingInfo {
+    double meandist;
+    double framerateratio; /* second/first */
+    int score;
+    int offset;
+    int matchframes; /* number of matching frames */
+    int whole;
+    struct FineSignature* first;
+    struct FineSignature* second;
+    struct MatchingInfo* next;
+} MatchingInfo;
+
+typedef struct {
+    AVRational time_base;
+    /* needed for xml_export */
+    int w; /* height */
+    int h; /* width */
+
+    /* overflow protection */
+    int divide;
+
+    FineSignature* finesiglist;
+    FineSignature* curfinesig;
+
+    CoarseSignature* coarsesiglist;
+    CoarseSignature* coarseend; /* needed for xml export */
+    /* helpers to store the alternating signatures */
+    CoarseSignature* curcoarsesig1;
+    CoarseSignature* curcoarsesig2;
+
+    int coarsecount; /* counter from 0 to 89 */
+    int midcoarse;   /* whether it is a coarsesignature beginning from 45 + i * 90 */
+    uint32_t lastindex; /* helper to store amount of frames */
+
+    int exported; /* boolean whether stream already exported */
+} StreamContext;
+
+typedef struct {
+    const AVClass *class;
+    /* input parameters */
+    int mode;
+    int nb_inputs;
+    char *filename;
+    int format;
+    int thworddist;
+    int thcomposdist;
+    int thl1;
+    int thdi;
+    int thit;
+    /* end input parameters */
+
+    uint8_t l1distlut[243*242/2]; /* 243 + 242 + 241 ... */
+    StreamContext* streamcontexts;
+} SignatureContext;
+
+
+static const Block elem_a1_data[] = {
+    {{ 0, 0},{ 7, 7}},
+    {{ 8, 0},{15, 7}},
+    {{ 0, 8},{ 7,15}},
+    {{ 8, 8},{15,15}},
+    {{16, 0},{23, 7}},
+    {{24, 0},{31, 7}},
+    {{16, 8},{23,15}},
+    {{24, 8},{31,15}},
+    {{ 0,16},{ 7,23}},
+    {{ 8,16},{15,23}},
+    {{ 0,24},{ 7,31}},
+    {{ 8,24},{15,31}},
+    {{16,16},{23,23}},
+    {{24,16},{31,23}},
+    {{16,24},{23,31}},
+    {{24,24},{31,31}},
+    {{ 0, 0},{15,15}},
+    {{16, 0},{31,15}},
+    {{ 0,16},{15,31}},
+    {{16,16},{31,31}}
+};
+static const ElemCat elem_a1 = { 1, 1, 1, 20, elem_a1_data };
+
+static const Block elem_a2_data[] = {
+    {{ 2, 2},{ 9, 9}},
+    {{12, 2},{19, 9}},
+    {{22, 2},{29, 9}},
+    {{ 2,12},{ 9,19}},
+    {{12,12},{19,19}},
+    {{22,12},{29,19}},
+    {{ 2,22},{ 9,29}},
+    {{12,22},{19,29}},
+    {{22,22},{29,29}},
+    {{ 9, 9},{22,22}},
+    {{ 6, 6},{25,25}},
+    {{ 3, 3},{28,28}}
+};
+static const ElemCat elem_a2 = { 1, 1, 1, 12, elem_a2_data };
+
+static const Block elem_d1_data[] = {
+    {{ 0, 0},{ 1, 3}},{{ 2, 0},{ 3, 3}},
+    {{ 4, 0},{ 7, 1}},{{ 4, 2},{ 7, 3}},
+    {{ 0, 6},{ 3, 7}},{{ 0, 4},{ 3, 5}},
+    {{ 6, 4},{ 7, 7}},{{ 4, 4},{ 5, 7}},
+    {{ 8, 0},{ 9, 3}},{{10, 0},{11, 3}},
+    {{12, 0},{15, 1}},{{12, 2},{15, 3}},
+    {{ 8, 6},{11, 7}},{{ 8, 4},{11, 5}},
+    {{14, 4},{15, 7}},{{12, 4},{13, 7}},
+    {{ 0, 8},{ 1,11}},{{ 2, 8},{ 3,11}},
+    {{ 4, 8},{ 7, 9}},{{ 4,10},{ 7,11}},
+    {{ 0,14},{ 3,15}},{{ 0,12},{ 3,13}},
+    {{ 6,12},{ 7,15}},{{ 4,12},{ 5,15}},
+    {{ 8, 8},{ 9,11}},{{10, 8},{11,11}},
+    {{12, 8},{15, 9}},{{12,10},{15,11}},
+    {{ 8,14},{11,15}},{{ 8,12},{11,13}},
+    {{14,12},{15,15}},{{12,12},{13,15}},
+    {{16, 0},{19, 1}},{{16, 2},{19, 3}},
+    {{22, 0},{23, 3}},{{20, 0},{21, 3}},
+    {{16, 4},{17, 7}},{{18, 4},{19, 7}},
+    {{20, 6},{23, 7}},{{20, 4},{23, 5}},
+    {{24, 0},{27, 1}},{{24, 2},{27, 3}},
+    {{30, 0},{31, 3}},{{28, 0},{29, 3}},
+    {{24, 4},{25, 7}},{{26, 4},{27, 7}},
+    {{28, 6},{31, 7}},{{28, 4},{31, 5}},
+    {{16, 8},{19, 9}},{{16,10},{19,11}},
+    {{22, 8},{23,11}},{{20, 8},{21,11}},
+    {{16,12},{17,15}},{{18,12},{19,15}},
+    {{20,14},{23,15}},{{20,12},{23,13}},
+    {{24, 8},{27, 9}},{{24,10},{27,11}},
+    {{30, 8},{31,11}},{{28, 8},{29,11}},
+    {{24,12},{25,15}},{{26,12},{27,15}},
+    {{28,14},{31,15}},{{28,12},{31,13}},
+    {{ 0,16},{ 3,17}},{{ 0,18},{ 3,19}},
+    {{ 6,16},{ 7,19}},{{ 4,16},{ 5,19}},
+    {{ 0,20},{ 1,23}},{{ 2,20},{ 3,23}},
+    {{ 4,22},{ 7,23}},{{ 4,20},{ 7,21}},
+    {{ 8,16},{11,17}},{{ 8,18},{11,19}},
+    {{14,16},{15,19}},{{12,16},{13,19}},
+    {{ 8,20},{ 9,23}},{{10,20},{11,23}},
+    {{12,22},{15,23}},{{12,20},{15,21}},
+    {{ 0,24},{ 3,25}},{{ 0,26},{ 3,27}},
+    {{ 6,24},{ 7,27}},{{ 4,24},{ 5,27}},
+    {{ 0,28},{ 1,31}},{{ 2,28},{ 3,31}},
+    {{ 4,30},{ 7,31}},{{ 4,28},{ 7,29}},
+    {{ 8,24},{11,25}},{{ 8,26},{11,27}},
+    {{14,24},{15,27}},{{12,24},{13,27}},
+    {{ 8,28},{ 9,31}},{{10,28},{11,31}},
+    {{12,30},{15,31}},{{12,28},{15,29}},
+    {{16,16},{17,19}},{{18,16},{19,19}},
+    {{20,16},{23,17}},{{20,18},{23,19}},
+    {{16,22},{19,23}},{{16,20},{19,21}},
+    {{22,20},{23,23}},{{20,20},{21,23}},
+    {{24,16},{25,19}},{{26,16},{27,19}},
+    {{28,16},{31,17}},{{28,18},{31,19}},
+    {{24,22},{27,23}},{{24,20},{27,21}},
+    {{30,20},{31,23}},{{28,20},{29,23}},
+    {{16,24},{17,27}},{{18,24},{19,27}},
+    {{20,24},{23,25}},{{20,26},{23,27}},
+    {{16,30},{19,31}},{{16,28},{19,29}},
+    {{22,28},{23,31}},{{20,28},{21,31}},
+    {{24,24},{25,27}},{{26,24},{27,27}},
+    {{28,24},{31,25}},{{28,26},{31,27}},
+    {{24,30},{27,31}},{{24,28},{27,29}},
+    {{30,28},{31,31}},{{28,28},{29,31}},
+    {{ 2, 2},{ 3, 5}},{{ 4, 2},{ 5, 5}},
+    {{ 6, 2},{ 9, 3}},{{ 6, 4},{ 9, 5}},
+    {{ 2, 8},{ 5, 9}},{{ 2, 6},{ 5, 7}},
+    {{ 8, 6},{ 9, 9}},{{ 6, 6},{ 7, 9}},
+    {{12, 2},{13, 5}},{{14, 2},{15, 5}},
+    {{16, 2},{19, 3}},{{16, 4},{19, 5}},
+    {{12, 8},{15, 9}},{{12, 6},{15, 7}},
+    {{18, 6},{19, 9}},{{16, 6},{17, 9}},
+    {{22, 2},{23, 5}},{{24, 2},{25, 5}},
+    {{26, 2},{29, 3}},{{26, 4},{29, 5}},
+    {{22, 8},{25, 9}},{{22, 6},{25, 7}},
+    {{28, 6},{29, 9}},{{26, 6},{27, 9}},
+    {{ 2,12},{ 3,15}},{{ 4,12},{ 5,15}},
+    {{ 6,12},{ 9,13}},{{ 6,14},{ 9,15}},
+    {{ 2,18},{ 5,19}},{{ 2,16},{ 5,17}},
+    {{ 8,16},{ 9,19}},{{ 6,16},{ 7,19}},
+    {{12,12},{15,13}},{{12,14},{15,15}},
+    {{16,12},{19,13}},{{16,14},{19,15}},
+    {{12,18},{15,19}},{{12,16},{15,17}},
+    {{16,18},{19,19}},{{16,16},{19,17}},
+    {{22,12},{23,15}},{{24,12},{25,15}},
+    {{26,12},{29,13}},{{26,14},{29,15}},
+    {{22,18},{25,19}},{{22,16},{25,17}},
+    {{28,16},{29,19}},{{26,16},{27,19}},
+    {{ 2,22},{ 3,25}},{{ 4,22},{ 5,25}},
+    {{ 6,22},{ 9,23}},{{ 6,24},{ 9,25}},
+    {{ 2,28},{ 5,29}},{{ 2,26},{ 5,27}},
+    {{ 8,26},{ 9,29}},{{ 6,26},{ 7,29}},
+    {{12,22},{13,25}},{{14,22},{15,25}},
+    {{16,22},{19,23}},{{16,24},{19,25}},
+    {{12,28},{15,29}},{{12,26},{15,27}},
+    {{18,26},{19,29}},{{16,26},{17,29}},
+    {{22,22},{23,25}},{{24,22},{25,25}},
+    {{26,22},{29,23}},{{26,24},{29,25}},
+    {{22,28},{25,29}},{{22,26},{25,27}},
+    {{28,26},{29,29}},{{26,26},{27,29}},
+    {{ 7, 7},{10, 8}},{{ 7, 9},{10,10}},
+    {{11, 7},{12,10}},{{13, 7},{14,10}},
+    {{ 7,11},{ 8,14}},{{ 9,11},{10,14}},
+    {{11,11},{14,12}},{{11,13},{14,14}},
+    {{17, 7},{20, 8}},{{17, 9},{20,10}},
+    {{21, 7},{22,10}},{{23, 7},{24,10}},
+    {{17,11},{18,14}},{{19,11},{20,14}},
+    {{21,11},{24,12}},{{21,13},{24,14}},
+    {{ 7,17},{10,18}},{{ 7,19},{10,20}},
+    {{11,17},{12,20}},{{13,17},{14,20}},
+    {{ 7,21},{ 8,24}},{{ 9,21},{10,24}},
+    {{11,21},{14,22}},{{11,23},{14,24}},
+    {{17,17},{20,18}},{{17,19},{20,20}},
+    {{21,17},{22,20}},{{23,17},{24,20}},
+    {{17,21},{18,24}},{{19,21},{20,24}},
+    {{21,21},{24,22}},{{21,23},{24,24}}
+};
+static const ElemCat elem_d1 = { 0, 1, 2, 116, elem_d1_data };
+
+static const Block elem_d2_data[] = {
+    {{ 0, 0},{ 3, 3}},{{ 4, 4},{ 7, 7}},{{ 4, 0},{ 7, 3}},{{ 0, 4},{ 3, 7}},
+    {{ 8, 0},{11, 3}},{{12, 4},{15, 7}},{{12, 0},{15, 3}},{{ 8, 4},{11, 7}},
+    {{16, 0},{19, 3}},{{20, 4},{23, 7}},{{20, 0},{23, 3}},{{16, 4},{19, 7}},
+    {{24, 0},{27, 3}},{{28, 4},{31, 7}},{{28, 0},{31, 3}},{{24, 4},{27, 7}},
+    {{ 0, 8},{ 3,11}},{{ 4,12},{ 7,15}},{{ 4, 8},{ 7,11}},{{ 0,12},{ 3,15}},
+    {{ 8, 8},{11,11}},{{12,12},{15,15}},{{12, 8},{15,11}},{{ 8,12},{11,15}},
+    {{16, 8},{19,11}},{{20,12},{23,15}},{{20, 8},{23,11}},{{16,12},{19,15}},
+    {{24, 8},{27,11}},{{28,12},{31,15}},{{28, 8},{31,11}},{{24,12},{27,15}},
+    {{ 0,16},{ 3,19}},{{ 4,20},{ 7,23}},{{ 4,16},{ 7,19}},{{ 0,20},{ 3,23}},
+    {{ 8,16},{11,19}},{{12,20},{15,23}},{{12,16},{15,19}},{{ 8,20},{11,23}},
+    {{16,16},{19,19}},{{20,20},{23,23}},{{20,16},{23,19}},{{16,20},{19,23}},
+    {{24,16},{27,19}},{{28,20},{31,23}},{{28,16},{31,19}},{{24,20},{27,23}},
+    {{ 0,24},{ 3,27}},{{ 4,28},{ 7,31}},{{ 4,24},{ 7,27}},{{ 0,28},{ 3,31}},
+    {{ 8,24},{11,27}},{{12,28},{15,31}},{{12,24},{15,27}},{{ 8,28},{11,31}},
+    {{16,24},{19,27}},{{20,28},{23,31}},{{20,24},{23,27}},{{16,28},{19,31}},
+    {{24,24},{27,27}},{{28,28},{31,31}},{{28,24},{31,27}},{{24,28},{27,31}},
+    {{ 4, 4},{ 7, 7}},{{ 8, 8},{11,11}},{{ 8, 4},{11, 7}},{{ 4, 8},{ 7,11}},
+    {{12, 4},{15, 7}},{{16, 8},{19,11}},{{16, 4},{19, 7}},{{12, 8},{15,11}},
+    {{20, 4},{23, 7}},{{24, 8},{27,11}},{{24, 4},{27, 7}},{{20, 8},{23,11}},
+    {{ 4,12},{ 7,15}},{{ 8,16},{11,19}},{{ 8,12},{11,15}},{{ 4,16},{ 7,19}},
+    {{12,12},{15,15}},{{16,16},{19,19}},{{16,12},{19,15}},{{12,16},{15,19}},
+    {{20,12},{23,15}},{{24,16},{27,19}},{{24,12},{27,15}},{{20,16},{23,19}},
+    {{ 4,20},{ 7,23}},{{ 8,24},{11,27}},{{ 8,20},{11,23}},{{ 4,24},{ 7,27}},
+    {{12,20},{15,23}},{{16,24},{19,27}},{{16,20},{19,23}},{{12,24},{15,27}},
+    {{20,20},{23,23}},{{24,24},{27,27}},{{24,20},{27,23}},{{20,24},{23,27}}
+};
+static const ElemCat elem_d2 = { 0, 2, 4, 25, elem_d2_data };
+
+static const Block elem_d3_data[] = {
+    {{ 1, 1},{10,10}},{{11, 1},{20,10}},
+    {{ 1, 1},{10,10}},{{21, 1},{30,10}},
+    {{ 1, 1},{10,10}},{{ 1,11},{10,20}},
+    {{ 1, 1},{10,10}},{{11,11},{20,20}},
+    {{ 1, 1},{10,10}},{{21,11},{30,20}},
+    {{ 1, 1},{10,10}},{{ 1,21},{10,30}},
+    {{ 1, 1},{10,10}},{{11,21},{20,30}},
+    {{ 1, 1},{10,10}},{{21,21},{30,30}},
+    {{11, 1},{20,10}},{{21, 1},{30,10}},
+    {{11, 1},{20,10}},{{ 1,11},{10,20}},
+    {{11, 1},{20,10}},{{11,11},{20,20}},
+    {{11, 1},{20,10}},{{21,11},{30,20}},
+    {{11, 1},{20,10}},{{ 1,21},{10,30}},
+    {{11, 1},{20,10}},{{11,21},{20,30}},
+    {{11, 1},{20,10}},{{21,21},{30,30}},
+    {{21, 1},{30,10}},{{ 1,11},{10,20}},
+    {{21, 1},{30,10}},{{11,11},{20,20}},
+    {{21, 1},{30,10}},{{21,11},{30,20}},
+    {{21, 1},{30,10}},{{ 1,21},{10,30}},
+    {{21, 1},{30,10}},{{11,21},{20,30}},
+    {{21, 1},{30,10}},{{21,21},{30,30}},
+    {{ 1,11},{10,20}},{{11,11},{20,20}},
+    {{ 1,11},{10,20}},{{21,11},{30,20}},
+    {{ 1,11},{10,20}},{{ 1,21},{10,30}},
+    {{ 1,11},{10,20}},{{11,21},{20,30}},
+    {{ 1,11},{10,20}},{{21,21},{30,30}},
+    {{11,11},{20,20}},{{21,11},{30,20}},
+    {{11,11},{20,20}},{{ 1,21},{10,30}},
+    {{11,11},{20,20}},{{11,21},{20,30}},
+    {{11,11},{20,20}},{{21,21},{30,30}},
+    {{21,11},{30,20}},{{ 1,21},{10,30}},
+    {{21,11},{30,20}},{{11,21},{20,30}},
+    {{21,11},{30,20}},{{21,21},{30,30}},
+    {{ 1,21},{10,30}},{{11,21},{20,30}},
+    {{ 1,21},{10,30}},{{21,21},{30,30}},
+    {{11,21},{20,30}},{{21,21},{30,30}}
+};
+static const ElemCat elem_d3 = { 0, 1, 2, 36, elem_d3_data };
+
+static const Block elem_d4_data[] = {
+    {{ 7,13},{12,18}},{{19,13},{24,18}},
+    {{13, 7},{18,12}},{{13,19},{18,24}},
+    {{ 7, 7},{12,12}},{{19,19},{24,24}},
+    {{19, 7},{24,12}},{{ 7,19},{12,24}},
+    {{13, 7},{18,12}},{{19,13},{24,18}},
+    {{19,13},{24,18}},{{13,19},{18,24}},
+    {{13,19},{18,24}},{{ 7,13},{12,18}},
+    {{ 7,13},{12,18}},{{13, 7},{18,12}},
+    {{ 7, 7},{12,12}},{{19, 7},{24,12}},
+    {{19, 7},{24,12}},{{19,19},{24,24}},
+    {{19,19},{24,24}},{{ 7,19},{12,24}},
+    {{ 7,19},{12,24}},{{ 7, 7},{12,12}},
+    {{13,13},{18,18}},{{13, 1},{18, 6}},
+    {{13,13},{18,18}},{{25,13},{30,18}},
+    {{13,13},{18,18}},{{13,25},{18,30}},
+    {{13,13},{18,18}},{{ 1,13},{ 6,18}},
+    {{13, 1},{18, 6}},{{13,25},{18,30}},
+    {{ 1,13},{ 6,18}},{{25,13},{30,18}},
+    {{ 7, 1},{12, 6}},{{19, 1},{24, 6}},
+    {{ 7,25},{12,30}},{{19,25},{24,30}},
+    {{ 1, 7},{ 6,12}},{{ 1,19},{ 6,24}},
+    {{25, 7},{30,12}},{{25,19},{30,24}},
+    {{ 7, 1},{12, 6}},{{ 1, 7},{ 6,12}},
+    {{19, 1},{24, 6}},{{25, 7},{30,12}},
+    {{25,19},{30,24}},{{19,25},{24,30}},
+    {{ 1,19},{ 6,24}},{{ 7,25},{12,30}},
+    {{ 1, 1},{ 6, 6}},{{25, 1},{30, 6}},
+    {{25, 1},{30, 6}},{{25,25},{30,30}},
+    {{25,25},{30,30}},{{ 1,25},{ 6,30}},
+    {{ 1,25},{ 6,30}},{{ 1, 1},{ 6, 6}}
+};
+static const ElemCat elem_d4 = { 0, 1, 2, 30, elem_d4_data };
+
+static const Block elem_d5_data[] = {
+    {{ 1, 1},{10, 3}},{{ 1, 4},{ 3, 7}},{{ 8, 4},{10, 7}},{{ 1, 8},{10,10}},{{ 4, 4},{ 7, 7}},
+    {{11, 1},{20, 3}},{{11, 4},{13, 7}},{{18, 4},{20, 7}},{{11, 8},{20,10}},{{14, 4},{17, 7}},
+    {{21, 1},{30, 3}},{{21, 4},{23, 7}},{{28, 4},{30, 7}},{{21, 8},{30,10}},{{24, 4},{27, 7}},
+    {{ 1,11},{10,13}},{{ 1,14},{ 3,17}},{{ 8,14},{10,17}},{{ 1,18},{10,20}},{{ 4,14},{ 7,17}},
+    {{11,11},{20,13}},{{11,14},{13,17}},{{18,14},{20,17}},{{11,18},{20,20}},{{14,14},{17,17}},
+    {{21,11},{30,13}},{{21,14},{23,17}},{{28,14},{30,17}},{{21,18},{30,20}},{{24,14},{27,17}},
+    {{ 1,21},{10,23}},{{ 1,24},{ 3,27}},{{ 8,24},{10,27}},{{ 1,28},{10,30}},{{ 4,24},{ 7,27}},
+    {{11,21},{20,23}},{{11,24},{13,27}},{{18,24},{20,27}},{{11,28},{20,30}},{{14,24},{17,27}},
+    {{21,21},{30,23}},{{21,24},{23,27}},{{28,24},{30,27}},{{21,28},{30,30}},{{24,24},{27,27}},
+    {{ 6, 6},{15, 8}},{{ 6, 9},{ 8,12}},{{13, 9},{15,12}},{{ 6,13},{15,15}},{{ 9, 9},{12,12}},
+    {{16, 6},{25, 8}},{{16, 9},{18,12}},{{23, 9},{25,12}},{{16,13},{25,15}},{{19, 9},{22,12}},
+    {{ 6,16},{15,18}},{{ 6,19},{ 8,22}},{{13,19},{15,22}},{{ 6,23},{15,25}},{{ 9,19},{12,22}},
+    {{16,16},{25,18}},{{16,19},{18,22}},{{23,19},{25,22}},{{16,23},{25,25}},{{19,19},{22,22}},
+    {{ 6, 1},{15, 3}},{{ 6, 4},{ 8, 7}},{{13, 4},{15, 7}},{{ 6, 8},{15,10}},{{ 9, 4},{12, 7}},
+    {{16, 1},{25, 3}},{{16, 4},{18, 7}},{{23, 4},{25, 7}},{{16, 8},{25,10}},{{19, 4},{22, 7}},
+    {{ 1, 6},{10, 8}},{{ 1, 9},{ 3,12}},{{ 8, 9},{10,12}},{{ 1,13},{10,15}},{{ 4, 9},{ 7,12}},
+    {{11, 6},{20, 8}},{{11, 9},{13,12}},{{18, 9},{20,12}},{{11,13},{20,15}},{{14, 9},{17,12}},
+    {{21, 6},{30, 8}},{{21, 9},{23,12}},{{28, 9},{30,12}},{{21,13},{30,15}},{{24, 9},{27,12}},
+    {{ 6,11},{15,13}},{{ 6,14},{ 8,17}},{{13,14},{15,17}},{{ 6,18},{15,20}},{{ 9,14},{12,17}},
+    {{16,11},{25,13}},{{16,14},{18,17}},{{23,14},{25,17}},{{16,18},{25,20}},{{19,14},{22,17}},
+    {{ 1,16},{10,18}},{{ 1,19},{ 3,22}},{{ 8,19},{10,22}},{{ 1,23},{10,25}},{{ 4,19},{ 7,22}},
+    {{11,16},{20,18}},{{11,19},{13,22}},{{18,19},{20,22}},{{11,23},{20,25}},{{14,19},{17,22}},
+    {{21,16},{30,18}},{{21,19},{23,22}},{{28,19},{30,22}},{{21,23},{30,25}},{{24,19},{27,22}},
+    {{ 6,21},{15,23}},{{ 6,24},{ 8,27}},{{13,24},{15,27}},{{ 6,28},{15,30}},{{ 9,24},{12,27}},
+    {{16,21},{25,23}},{{16,24},{18,27}},{{23,24},{25,27}},{{16,28},{25,30}},{{19,24},{22,27}},
+    {{ 2, 2},{14, 6}},{{ 2, 7},{ 6, 9}},{{10, 7},{14, 9}},{{ 2,10},{14,14}},{{ 7, 7},{ 9, 9}},
+    {{ 7, 2},{19, 6}},{{ 7, 7},{11, 9}},{{15, 7},{19, 9}},{{ 7,10},{19,14}},{{12, 7},{14, 9}},
+    {{12, 2},{24, 6}},{{12, 7},{16, 9}},{{20, 7},{24, 9}},{{12,10},{24,14}},{{17, 7},{19, 9}},
+    {{17, 2},{29, 6}},{{17, 7},{21, 9}},{{25, 7},{29, 9}},{{17,10},{29,14}},{{22, 7},{24, 9}},
+    {{ 2, 7},{14,11}},{{ 2,12},{ 6,14}},{{10,12},{14,14}},{{ 2,15},{14,19}},{{ 7,12},{ 9,14}},
+    {{ 7, 7},{19,11}},{{ 7,12},{11,14}},{{15,12},{19,14}},{{ 7,15},{19,19}},{{12,12},{14,14}},
+    {{12, 7},{24,11}},{{12,12},{16,14}},{{20,12},{24,14}},{{12,15},{24,19}},{{17,12},{19,14}},
+    {{17, 7},{29,11}},{{17,12},{21,14}},{{25,12},{29,14}},{{17,15},{29,19}},{{22,12},{24,14}},
+    {{ 2,12},{14,16}},{{ 2,17},{ 6,19}},{{10,17},{14,19}},{{ 2,20},{14,24}},{{ 7,17},{ 9,19}},
+    {{ 7,12},{19,16}},{{ 7,17},{11,19}},{{15,17},{19,19}},{{ 7,20},{19,24}},{{12,17},{14,19}},
+    {{12,12},{24,16}},{{12,17},{16,19}},{{20,17},{24,19}},{{12,20},{24,24}},{{17,17},{19,19}},
+    {{17,12},{29,16}},{{17,17},{21,19}},{{25,17},{29,19}},{{17,20},{29,24}},{{22,17},{24,19}},
+    {{ 2,17},{14,21}},{{ 2,22},{ 6,24}},{{10,22},{14,24}},{{ 2,25},{14,29}},{{ 7,22},{ 9,24}},
+    {{ 7,17},{19,21}},{{ 7,22},{11,24}},{{15,22},{19,24}},{{ 7,25},{19,29}},{{12,22},{14,24}},
+    {{12,17},{24,21}},{{12,22},{16,24}},{{20,22},{24,24}},{{12,25},{24,29}},{{17,22},{19,24}},
+    {{17,17},{29,21}},{{17,22},{21,24}},{{25,22},{29,24}},{{17,25},{29,29}},{{22,22},{24,24}},
+    {{ 8, 3},{13, 4}},{{ 8, 5},{ 9, 6}},{{12, 5},{13, 6}},{{ 8, 7},{13, 8}},{{10, 5},{11, 6}},
+    {{13, 3},{18, 4}},{{13, 5},{14, 6}},{{17, 5},{18, 6}},{{13, 7},{18, 8}},{{15, 5},{16, 6}},
+    {{18, 3},{23, 4}},{{18, 5},{19, 6}},{{22, 5},{23, 6}},{{18, 7},{23, 8}},{{20, 5},{21, 6}},
+    {{ 3, 8},{ 8, 9}},{{ 3,10},{ 4,11}},{{ 7,10},{ 8,11}},{{ 3,12},{ 8,13}},{{ 5,10},{ 6,11}},
+    {{ 8, 8},{13, 9}},{{ 8,10},{ 9,11}},{{12,10},{13,11}},{{ 8,12},{13,13}},{{10,10},{11,11}},
+    {{13, 8},{18, 9}},{{13,10},{14,11}},{{17,10},{18,11}},{{13,12},{18,13}},{{15,10},{16,11}},
+    {{18, 8},{23, 9}},{{18,10},{19,11}},{{22,10},{23,11}},{{18,12},{23,13}},{{20,10},{21,11}},
+    {{23, 8},{28, 9}},{{23,10},{24,11}},{{27,10},{28,11}},{{23,12},{28,13}},{{25,10},{26,11}},
+    {{ 3,13},{ 8,14}},{{ 3,15},{ 4,16}},{{ 7,15},{ 8,16}},{{ 3,17},{ 8,18}},{{ 5,15},{ 6,16}},
+    {{ 8,13},{13,14}},{{ 8,15},{ 9,16}},{{12,15},{13,16}},{{ 8,17},{13,18}},{{10,15},{11,16}},
+    {{13,13},{18,14}},{{13,15},{14,16}},{{17,15},{18,16}},{{13,17},{18,18}},{{15,15},{16,16}},
+    {{18,13},{23,14}},{{18,15},{19,16}},{{22,15},{23,16}},{{18,17},{23,18}},{{20,15},{21,16}},
+    {{23,13},{28,14}},{{23,15},{24,16}},{{27,15},{28,16}},{{23,17},{28,18}},{{25,15},{26,16}},
+    {{ 3,18},{ 8,19}},{{ 3,20},{ 4,21}},{{ 7,20},{ 8,21}},{{ 3,22},{ 8,23}},{{ 5,20},{ 6,21}},
+    {{ 8,18},{13,19}},{{ 8,20},{ 9,21}},{{12,20},{13,21}},{{ 8,22},{13,23}},{{10,20},{11,21}},
+    {{13,18},{18,19}},{{13,20},{14,21}},{{17,20},{18,21}},{{13,22},{18,23}},{{15,20},{16,21}},
+    {{18,18},{23,19}},{{18,20},{19,21}},{{22,20},{23,21}},{{18,22},{23,23}},{{20,20},{21,21}},
+    {{23,18},{28,19}},{{23,20},{24,21}},{{27,20},{28,21}},{{23,22},{28,23}},{{25,20},{26,21}},
+    {{ 8,23},{13,24}},{{ 8,25},{ 9,26}},{{12,25},{13,26}},{{ 8,27},{13,28}},{{10,25},{11,26}},
+    {{13,23},{18,24}},{{13,25},{14,26}},{{17,25},{18,26}},{{13,27},{18,28}},{{15,25},{16,26}},
+    {{18,23},{23,24}},{{18,25},{19,26}},{{22,25},{23,26}},{{18,27},{23,28}},{{20,25},{21,26}}
+};
+static const ElemCat elem_d5 = { 0, 4, 5, 62, elem_d5_data };
+
+static const Block elem_d6_data[] = {
+    {{ 3, 5},{12,10}},{{ 5, 3},{10,12}},
+    {{11, 5},{20,10}},{{13, 3},{18,12}},
+    {{19, 5},{28,10}},{{21, 3},{26,12}},
+    {{ 3,13},{12,18}},{{ 5,11},{10,20}},
+    {{11,13},{20,18}},{{13,11},{18,20}},
+    {{19,13},{28,18}},{{21,11},{26,20}},
+    {{ 3,21},{12,26}},{{ 5,19},{10,28}},
+    {{11,21},{20,26}},{{13,19},{18,28}},
+    {{19,21},{28,26}},{{21,19},{26,28}}
+};
+static const ElemCat elem_d6 = { 0, 1, 2, 9, elem_d6_data };
+
+static const Block elem_d7_data[] = {
+    {{ 0, 4},{ 3, 7}},{{ 8, 4},{11, 7}},{{ 4, 4},{ 7, 7}},
+    {{ 4, 0},{ 7, 3}},{{ 4, 8},{ 7,11}},{{ 4, 4},{ 7, 7}},
+    {{ 5, 4},{ 8, 7}},{{13, 4},{16, 7}},{{ 9, 4},{12, 7}},
+    {{ 9, 0},{12, 3}},{{ 9, 8},{12,11}},{{ 9, 4},{12, 7}},
+    {{10, 4},{13, 7}},{{18, 4},{21, 7}},{{14, 4},{17, 7}},
+    {{14, 0},{17, 3}},{{14, 8},{17,11}},{{14, 4},{17, 7}},
+    {{15, 4},{18, 7}},{{23, 4},{26, 7}},{{19, 4},{22, 7}},
+    {{19, 0},{22, 3}},{{19, 8},{22,11}},{{19, 4},{22, 7}},
+    {{20, 4},{23, 7}},{{28, 4},{31, 7}},{{24, 4},{27, 7}},
+    {{24, 0},{27, 3}},{{24, 8},{27,11}},{{24, 4},{27, 7}},
+    {{ 0, 9},{ 3,12}},{{ 8, 9},{11,12}},{{ 4, 9},{ 7,12}},
+    {{ 4, 5},{ 7, 8}},{{ 4,13},{ 7,16}},{{ 4, 9},{ 7,12}},
+    {{ 5, 9},{ 8,12}},{{13, 9},{16,12}},{{ 9, 9},{12,12}},
+    {{ 9, 5},{12, 8}},{{ 9,13},{12,16}},{{ 9, 9},{12,12}},
+    {{10, 9},{13,12}},{{18, 9},{21,12}},{{14, 9},{17,12}},
+    {{14, 5},{17, 8}},{{14,13},{17,16}},{{14, 9},{17,12}},
+    {{15, 9},{18,12}},{{23, 9},{26,12}},{{19, 9},{22,12}},
+    {{19, 5},{22, 8}},{{19,13},{22,16}},{{19, 9},{22,12}},
+    {{20, 9},{23,12}},{{28, 9},{31,12}},{{24, 9},{27,12}},
+    {{24, 5},{27, 8}},{{24,13},{27,16}},{{24, 9},{27,12}},
+    {{ 0,14},{ 3,17}},{{ 8,14},{11,17}},{{ 4,14},{ 7,17}},
+    {{ 4,10},{ 7,13}},{{ 4,18},{ 7,21}},{{ 4,14},{ 7,17}},
+    {{ 5,14},{ 8,17}},{{13,14},{16,17}},{{ 9,14},{12,17}},
+    {{ 9,10},{12,13}},{{ 9,18},{12,21}},{{ 9,14},{12,17}},
+    {{10,14},{13,17}},{{18,14},{21,17}},{{14,14},{17,17}},
+    {{14,10},{17,13}},{{14,18},{17,21}},{{14,14},{17,17}},
+    {{15,14},{18,17}},{{23,14},{26,17}},{{19,14},{22,17}},
+    {{19,10},{22,13}},{{19,18},{22,21}},{{19,14},{22,17}},
+    {{20,14},{23,17}},{{28,14},{31,17}},{{24,14},{27,17}},
+    {{24,10},{27,13}},{{24,18},{27,21}},{{24,14},{27,17}},
+    {{ 0,19},{ 3,22}},{{ 8,19},{11,22}},{{ 4,19},{ 7,22}},
+    {{ 4,15},{ 7,18}},{{ 4,23},{ 7,26}},{{ 4,19},{ 7,22}},
+    {{ 5,19},{ 8,22}},{{13,19},{16,22}},{{ 9,19},{12,22}},
+    {{ 9,15},{12,18}},{{ 9,23},{12,26}},{{ 9,19},{12,22}},
+    {{10,19},{13,22}},{{18,19},{21,22}},{{14,19},{17,22}},
+    {{14,15},{17,18}},{{14,23},{17,26}},{{14,19},{17,22}},
+    {{15,19},{18,22}},{{23,19},{26,22}},{{19,19},{22,22}},
+    {{19,15},{22,18}},{{19,23},{22,26}},{{19,19},{22,22}},
+    {{20,19},{23,22}},{{28,19},{31,22}},{{24,19},{27,22}},
+    {{24,15},{27,18}},{{24,23},{27,26}},{{24,19},{27,22}},
+    {{ 0,24},{ 3,27}},{{ 8,24},{11,27}},{{ 4,24},{ 7,27}},
+    {{ 4,20},{ 7,23}},{{ 4,28},{ 7,31}},{{ 4,24},{ 7,27}},
+    {{ 5,24},{ 8,27}},{{13,24},{16,27}},{{ 9,24},{12,27}},
+    {{ 9,20},{12,23}},{{ 9,28},{12,31}},{{ 9,24},{12,27}},
+    {{10,24},{13,27}},{{18,24},{21,27}},{{14,24},{17,27}},
+    {{14,20},{17,23}},{{14,28},{17,31}},{{14,24},{17,27}},
+    {{15,24},{18,27}},{{23,24},{26,27}},{{19,24},{22,27}},
+    {{19,20},{22,23}},{{19,28},{22,31}},{{19,24},{22,27}},
+    {{20,24},{23,27}},{{28,24},{31,27}},{{24,24},{27,27}},
+    {{24,20},{27,23}},{{24,28},{27,31}},{{24,24},{27,27}}
+};
+static const ElemCat elem_d7 = { 0, 2, 3, 50, elem_d7_data };
+
+static const Block elem_d8_data[] = {
+    {{ 0, 0},{ 7, 3}},{{ 0, 4},{ 7, 7}},
+    {{ 8, 0},{11, 7}},{{12, 0},{15, 7}},
+    {{ 0, 8},{ 3,15}},{{ 4, 8},{ 7,15}},
+    {{ 8, 8},{15,11}},{{ 8,12},{15,15}},
+    {{16, 0},{19, 7}},{{20, 0},{23, 7}},
+    {{24, 0},{31, 3}},{{24, 4},{31, 7}},
+    {{16, 8},{23,11}},{{16,12},{23,15}},
+    {{24, 8},{27,15}},{{28, 8},{31,15}},
+    {{ 0,16},{ 3,23}},{{ 4,16},{ 7,23}},
+    {{ 8,16},{15,19}},{{ 8,20},{15,23}},
+    {{ 0,24},{ 7,27}},{{ 0,28},{ 7,31}},
+    {{ 8,24},{11,31}},{{12,24},{15,31}},
+    {{16,16},{23,19}},{{16,20},{23,23}},
+    {{24,16},{27,23}},{{28,16},{31,23}},
+    {{16,24},{19,31}},{{20,24},{23,31}},
+    {{24,24},{31,27}},{{24,28},{31,31}},
+    {{ 0, 0},{ 7,15}},{{ 8, 0},{15,15}},
+    {{16, 0},{31, 7}},{{16, 8},{31,15}},
+    {{ 0,16},{15,23}},{{ 0,24},{15,31}},
+    {{16,16},{23,31}},{{24,16},{31,31}}
+};
+static const ElemCat elem_d8 = { 0, 1, 2, 20, elem_d8_data };
+
+static const ElemCat* elements[ELEMENT_COUNT] = { &elem_a1, &elem_a2,
+                                                  &elem_d1, &elem_d2, &elem_d3, &elem_d4,
+                                                  &elem_d5, &elem_d6, &elem_d7, &elem_d8 };
+#endif /* AVFILTER_SIGNATURE_H */
diff --git a/libavfilter/signature_lookup.c b/libavfilter/signature_lookup.c
new file mode 100644
index 0000000..5bc2904
--- /dev/null
+++ b/libavfilter/signature_lookup.c
@@ -0,0 +1,573 @@ 
+/*
+ * Copyright (c) 2017 Gerion Entrup
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg 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.
+ *
+ * FFmpeg 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 FFmpeg; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/**
+ * @file
+ * MPEG-7 video signature calculation and lookup filter
+ */
+
+#include "signature.h"
+
+#define HOUGH_MAX_OFFSET 90
+#define MAX_FRAMERATE 60
+
+#define DIR_PREV 0
+#define DIR_NEXT 1
+#define DIR_PREV_END 2
+#define DIR_NEXT_END 3
+
+#define STATUS_NULL 0
+#define STATUS_END_REACHED 1
+#define STATUS_BEGIN_REACHED 2
+
+static void fill_l1distlut(uint8_t lut[])
+{
+    int i, j, tmp_i, tmp_j,count;
+    uint8_t dist;
+
+    for (i = 0, count = 0; i < 242; i++) {
+        for (j = i + 1; j < 243; j++, count++) {
+            /* ternary distance between i and j */
+            dist = 0;
+            tmp_i = i; tmp_j = j;
+            do {
+                dist += FFABS((tmp_j % 3) - (tmp_i % 3));
+                tmp_j /= 3;
+                tmp_i /= 3;
+            } while (tmp_i > 0 || tmp_j > 0);
+            lut[count] = dist;
+        }
+    }
+}
+
+static unsigned int intersection_word(const uint8_t *first, const uint8_t *second)
+{
+    unsigned int val=0,i;
+    for (i = 0; i < 28; i += 4) {
+        val += av_popcount( (first[i]   & second[i]  ) << 24 |
+                            (first[i+1] & second[i+1]) << 16 |
+                            (first[i+2] & second[i+2]) << 8  |
+                            (first[i+3] & second[i+3]) );
+    }
+    val += av_popcount( (first[28] & second[28]) << 16 |
+                        (first[29] & second[29]) << 8  |
+                        (first[30] & second[30]) );
+    return val;
+}
+
+static unsigned int union_word(const uint8_t *first, const uint8_t *second)
+{
+    unsigned int val=0,i;
+    for (i = 0; i < 28; i += 4) {
+        val += av_popcount( (first[i]   | second[i]  ) << 24 |
+                            (first[i+1] | second[i+1]) << 16 |
+                            (first[i+2] | second[i+2]) << 8  |
+                            (first[i+3] | second[i+3]) );
+    }
+    val += av_popcount( (first[28] | second[28]) << 16 |
+                        (first[29] | second[29]) << 8  |
+                        (first[30] | second[30]) );
+    return val;
+}
+
+static unsigned int get_l1dist(AVFilterContext *ctx, SignatureContext *sc, const uint8_t *first, const uint8_t *second)
+{
+    unsigned int i;
+    unsigned int dist = 0;
+    uint8_t f, s;
+
+    for (i = 0; i < SIGELEM_SIZE/5; i++) {
+        if (first[i] != second[i]) {
+            f = first[i];
+            s = second[i];
+            if (f > s) {
+                /* little variation of gauss sum formula */
+                dist += sc->l1distlut[243*242/2 - (243-s)*(242-s)/2 + f - s - 1];
+            } else {
+                dist += sc->l1distlut[243*242/2 - (243-f)*(242-f)/2 + s - f - 1];
+            }
+        }
+    }
+    return dist;
+}
+
+/**
+ * calculates the jaccard distance and evaluates a pair of coarse signatures as good
+ * @return 0 if pair is bad, 1 otherwise
+ */
+static int get_jaccarddist(SignatureContext *sc, CoarseSignature *first, CoarseSignature *second)
+{
+    int jaccarddist, i, composdist = 0, cwthcount = 0;
+    for (i = 0; i < 5; i++) {
+        if ((jaccarddist = intersection_word(first->data[i], second->data[i])) > 0) {
+            jaccarddist /= union_word(first->data[i], second->data[i]);
+        }
+        if (jaccarddist >= sc->thworddist) {
+            if (++cwthcount > 2) {
+                /* more than half (5/2) of distances are too wide */
+                return 0;
+            }
+        }
+        composdist += jaccarddist;
+        if (composdist > sc->thcomposdist) {
+            return 0;
+        }
+    }
+    return 1;
+}
+
+/**
+ * step through the coarsesignatures as long as a good candidate is found
+ * @return 0 if no candidate is found, 1 otherwise
+ */
+static int find_next_coarsecandidate(SignatureContext *sc, CoarseSignature *secondstart, CoarseSignature **first, CoarseSignature **second, int start)
+{
+    /* go one coarsesignature foreword */
+    if (!start) {
+        if ((*second)->next) {
+            *second = (*second)->next;
+        } else if ((*first)->next) {
+            *second = secondstart;
+            *first = (*first)->next;
+        } else {
+            return 0;
+        }
+    }
+
+    while (1) {
+        if (get_jaccarddist(sc, *first, *second))
+            return 1;
+
+        /* next signature */
+        if ((*second)->next) {
+            *second = (*second)->next;
+        } else if ((*first)->next) {
+            *second = secondstart;
+            *first = (*first)->next;
+        } else {
+            return 0;
+        }
+    }
+}
+
+/**
+ * compares framesignatures and sorts out signatures with a l1 distance above a given threshold.
+ * Then tries to find out offset and differences between framerates with a hough transformation
+ */
+static MatchingInfo* get_matching_parameters(AVFilterContext *ctx, SignatureContext *sc, FineSignature *first, FineSignature *second)
+{
+    FineSignature *f, *s;
+    size_t i, j, k, l, hmax = 0, score;
+    int framerate, offset, l1dist;
+    double m;
+    MatchingInfo *cands = NULL, *c = NULL;
+
+    struct {
+        uint8_t size;
+        unsigned int dist;
+        FineSignature *a;
+        uint8_t b_pos[COARSE_SIZE];
+        FineSignature *b[COARSE_SIZE];
+    } pairs[COARSE_SIZE];
+
+    typedef struct {
+        int dist;
+        size_t score;
+        FineSignature *a;
+        FineSignature *b;
+    } hspace_elem;
+
+    /* houghspace */
+    hspace_elem** hspace = av_malloc_array(MAX_FRAMERATE, sizeof(hspace_elem *));
+
+    /* initialize houghspace */
+    for (i = 0; i < MAX_FRAMERATE; i++) {
+        hspace[i] = av_malloc_array(2 * HOUGH_MAX_OFFSET + 1, sizeof(hspace_elem));
+        for (j = 0; j < HOUGH_MAX_OFFSET; j++) {
+            hspace[i][j].score = 0;
+            hspace[i][j].dist = 99999;
+        }
+    }
+
+    /* l1 distances */
+    for (i = 0, f = first; i < COARSE_SIZE && f->next; i++, f = f->next) {
+        pairs[i].size = 0;
+        pairs[i].dist = 99999;
+        pairs[i].a = f;
+        for (j = 0, s = second; j < COARSE_SIZE && s->next; j++, s = s->next) {
+            /* l1 distance of finesignature */
+            l1dist = get_l1dist(ctx, sc, f->framesig, s->framesig);
+            if (l1dist < sc->thl1) {
+                if (l1dist < pairs[i].dist) {
+                    pairs[i].size = 1;
+                    pairs[i].dist = l1dist;
+                    pairs[i].b_pos[0] = j;
+                    pairs[i].b[0] = s;
+                } else if (l1dist == pairs[i].dist) {
+                    pairs[i].b[pairs[i].size] = s;
+                    pairs[i].b_pos[pairs[i].size] = j;
+                    pairs[i].size++;
+                }
+            }
+        }
+    }
+    /* last incomplete coarsesignature */
+    if (f->next == NULL) {
+        for (; i < COARSE_SIZE; i++) {
+            pairs[i].size = 0;
+            pairs[i].dist = 99999;
+        }
+    }
+
+    /* hough transformation */
+    for (i = 0; i < COARSE_SIZE; i++) {
+        for (j = 0; j < pairs[i].size; j++) {
+            for (k = i + 1; k < COARSE_SIZE; k++) {
+                for (l = 0; l < pairs[k].size; l++) {
+                    if (pairs[i].b[j] != pairs[k].b[l]) {
+                        /* linear regression */
+                        m = (pairs[k].b_pos[l]-pairs[i].b_pos[j]) / (k-i); /* good value between 0.0 - 2.0 */
+                        framerate = (int) m*30 + 0.5; /* round up to 0 - 60 */
+                        if (framerate>0 && framerate <= MAX_FRAMERATE) {
+                            offset = pairs[i].b_pos[j] - ((int) m*i + 0.5); /* only second part has to be rounded up */
+                            if (offset > -HOUGH_MAX_OFFSET && offset < HOUGH_MAX_OFFSET) {
+                                if (pairs[i].dist < pairs[k].dist) {
+                                    if (pairs[i].dist < hspace[framerate-1][offset+HOUGH_MAX_OFFSET].dist) {
+                                        hspace[framerate-1][offset+HOUGH_MAX_OFFSET].dist = pairs[i].dist;
+                                        hspace[framerate-1][offset+HOUGH_MAX_OFFSET].a = pairs[i].a;
+                                        hspace[framerate-1][offset+HOUGH_MAX_OFFSET].b = pairs[i].b[j];
+                                    }
+                                } else {
+                                    if (pairs[k].dist < hspace[framerate-1][offset+HOUGH_MAX_OFFSET].dist) {
+                                        hspace[framerate-1][offset+HOUGH_MAX_OFFSET].dist = pairs[k].dist;
+                                        hspace[framerate-1][offset+HOUGH_MAX_OFFSET].a = pairs[k].a;
+                                        hspace[framerate-1][offset+HOUGH_MAX_OFFSET].b = pairs[k].b[l];
+                                    }
+                                }
+
+                                score = hspace[framerate-1][offset+HOUGH_MAX_OFFSET].score + 1;
+                                if (score > hmax )
+                                    hmax = score;
+                                hspace[framerate-1][offset+HOUGH_MAX_OFFSET].score = score;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    if (hmax > 0) {
+        hmax = (int) (0.7*hmax);
+        for (i = 0; i < MAX_FRAMERATE; i++) {
+            for (j = 0; j < HOUGH_MAX_OFFSET; j++) {
+                if (hmax < hspace[i][j].score) {
+                    if (c == NULL) {
+                        c = av_malloc(sizeof(MatchingInfo));
+                        if (!c)
+                            av_log(ctx, AV_LOG_FATAL, "Could not allocate memory");
+                        cands = c;
+                    } else {
+                        c->next = av_malloc(sizeof(MatchingInfo));
+                        if (!c->next)
+                            av_log(ctx, AV_LOG_FATAL, "Could not allocate memory");
+                        c = c->next;
+                    }
+                    c->framerateratio = (i+1.0) / 30;
+                    c->score = hspace[i][j].score;
+                    c->offset = j-90;
+                    c->first = hspace[i][j].a;
+                    c->second = hspace[i][j].b;
+                    c->next = NULL;
+
+                    /* not used */
+                    c->meandist = 0;
+                    c->matchframes = 0;
+                    c->whole = 0;
+                }
+            }
+        }
+    }
+    for (i = 0; i < MAX_FRAMERATE; i++) {
+        av_freep(&hspace[i]);
+    }
+    av_freep(&hspace);
+    return cands;
+}
+
+static int iterate_frame(double frr, FineSignature **a, FineSignature **b, int fcount, int *bcount, int dir)
+{
+    int step;
+
+    /* between 1 and 2, because frr is between 1 and 2 */
+    step = ((int) 0.5 + fcount     * frr) /* current frame */
+          -((int) 0.5 + (fcount-1) * frr);/* last frame */
+
+    if (dir == DIR_NEXT) {
+        if (frr >= 1.0) {
+            if ((*a)->next) {
+                *a = (*a)->next;
+            } else {
+                return DIR_NEXT_END;
+            }
+
+            if (step == 1) {
+                if ((*b)->next) {
+                    *b = (*b)->next;
+                    (*bcount)++;
+                } else {
+                    return DIR_NEXT_END;
+                }
+            } else {
+                if ((*b)->next && (*b)->next->next) {
+                    *b = (*b)->next->next;
+                    (*bcount)++;
+                } else {
+                    return DIR_NEXT_END;
+                }
+            }
+        } else {
+            if ((*b)->next) {
+                *b = (*b)->next;
+                (*bcount)++;
+            } else {
+                return DIR_NEXT_END;
+            }
+
+            if (step == 1) {
+                if ((*a)->next) {
+                    *a = (*a)->next;
+                } else {
+                    return DIR_NEXT_END;
+                }
+            } else {
+                if ((*a)->next && (*a)->next->next) {
+                    *a = (*a)->next->next;
+                } else {
+                    return DIR_NEXT_END;
+                }
+            }
+        }
+        return DIR_NEXT;
+    } else {
+        if (frr >= 1.0) {
+            if ((*a)->prev) {
+                *a = (*a)->prev;
+            } else {
+                return DIR_PREV_END;
+            }
+
+            if (step == 1) {
+                if ((*b)->prev) {
+                    *b = (*b)->prev;
+                    (*bcount)++;
+                } else {
+                    return DIR_PREV_END;
+                }
+            } else {
+                if ((*b)->prev && (*b)->prev->prev) {
+                    *b = (*b)->prev->prev;
+                    (*bcount)++;
+                } else {
+                    return DIR_PREV_END;
+                }
+            }
+        } else {
+            if ((*b)->prev) {
+                *b = (*b)->prev;
+                (*bcount)++;
+            } else {
+                return DIR_PREV_END;
+            }
+
+            if (step == 1) {
+                if ((*a)->prev) {
+                    *a = (*a)->prev;
+                } else {
+                    return DIR_PREV_END;
+                }
+            } else {
+                if ((*a)->prev && (*a)->prev->prev) {
+                    *a = (*a)->prev->prev;
+                } else {
+                    return DIR_PREV_END;
+                }
+            }
+        }
+        return DIR_PREV;
+    }
+}
+
+static MatchingInfo evaluate_parameters(AVFilterContext *ctx, SignatureContext *sc, MatchingInfo *infos, MatchingInfo bestmatch, int mode)
+{
+    int dist, distsum = 0, bcount = 1, dir = DIR_NEXT;
+    int fcount = 0, goodfcount = 0, gooda = 0, goodb = 0;
+    double meandist, minmeandist = bestmatch.meandist;
+    int tolerancecount = 0;
+    FineSignature *a, *b, *aprev, *bprev;
+    int status = STATUS_NULL;
+
+    for (; infos != NULL; infos = infos->next) {
+        a = infos->first;
+        b = infos->second;
+        while (1) {
+            dist = get_l1dist(ctx, sc, a->framesig, b->framesig);
+
+            if (dist > sc->thl1) {
+                if (a->confidence >= 1 || b->confidence >= 1) {
+                    /* bad frame (because high different information) */
+                    tolerancecount++;
+                }
+
+                if (tolerancecount > 2) {
+                    a = aprev;
+                    b = bprev;
+                    if (dir == DIR_NEXT) {
+                        /* turn around */
+                        a = infos->first;
+                        b = infos->second;
+                        dir = DIR_PREV;
+                    } else {
+                        break;
+                    }
+                }
+            } else {
+                /* good frame */
+                distsum += dist;
+                goodfcount++;
+                tolerancecount=0;
+
+                aprev = a;
+                bprev = b;
+
+                if (a->confidence < 1) gooda++;
+                if (b->confidence < 1) goodb++;
+            }
+
+            fcount++;
+
+            dir = iterate_frame(infos->framerateratio, &a, &b, fcount, &bcount, dir);
+            if (dir == DIR_NEXT_END) {
+                status = STATUS_END_REACHED;
+                a = infos->first;
+                b = infos->second;
+                dir = iterate_frame(infos->framerateratio, &a, &b, fcount, &bcount, DIR_PREV);
+            }
+
+            if (dir == DIR_PREV_END) {
+                status |= STATUS_BEGIN_REACHED;
+                break;
+            }
+
+            if (sc->thdi != 0 && bcount >= sc->thdi) {
+                break; /* enough frames found */
+            }
+        }
+
+        if (bcount < sc->thdi)
+            continue; /* matching sequence is too short */
+        if ((double) goodfcount / (double) fcount < sc->thit)
+            continue;
+        if ((double) goodfcount*0.5 < FFMAX(gooda, goodb))
+            continue;
+
+        meandist = (double) goodfcount / (double) distsum;
+
+        if (meandist < minmeandist ||
+                status == STATUS_END_REACHED | STATUS_BEGIN_REACHED ||
+                mode == MODE_FAST){
+            minmeandist = meandist;
+            /* bestcandidate in this iteration */
+            bestmatch.meandist = meandist;
+            bestmatch.matchframes = bcount;
+            bestmatch.framerateratio = infos->framerateratio;
+            bestmatch.score = infos->score;
+            bestmatch.offset = infos->offset;
+            bestmatch.first = infos->first;
+            bestmatch.second = infos->second;
+            bestmatch.whole = 0; /* will be set to true later */
+            bestmatch.next = NULL;
+        }
+
+        /* whole sequence is automatically best match */
+        if (status == (STATUS_END_REACHED | STATUS_BEGIN_REACHED)) {
+            bestmatch.whole = 1;
+            break;
+        }
+
+        /* first matching sequence is enough, finding the best one is not necessary */
+        if (mode == MODE_FAST) {
+            break;
+        }
+    }
+    return bestmatch;
+}
+
+static void sll_free(MatchingInfo *sll)
+{
+    void *tmp;
+    while (sll) {
+        tmp = sll;
+        sll = sll->next;
+        av_freep(&tmp);
+    }
+}
+
+static MatchingInfo lookup_signatures(AVFilterContext *ctx, SignatureContext *sc, StreamContext *first, StreamContext *second, int mode)
+{
+    CoarseSignature *cs, *cs2;
+    MatchingInfo *infos;
+    MatchingInfo bestmatch;
+    MatchingInfo *i;
+
+    cs = first->coarsesiglist;
+    cs2 = second->coarsesiglist;
+
+    /* score of bestmatch is 0, if no match is found */
+    bestmatch.score = 0;
+    bestmatch.meandist = 99999;
+    bestmatch.whole = 0;
+
+    fill_l1distlut(sc->l1distlut);
+
+    /* stage 1: coarsesignature matching */
+    if (find_next_coarsecandidate(sc, second->coarsesiglist, &cs, &cs2, 1) == 0)
+        return bestmatch; /* no candidate found */
+    do {
+        av_log(ctx, AV_LOG_DEBUG, "Stage 1: got coarsesignature pair. indices of first frame: %d and %d\n", cs->first->index, cs2->first->index);
+        /* stage 2: l1-distance and hough-transform */
+        av_log(ctx, AV_LOG_DEBUG, "Stage 2: calculate matching parameters\n");
+        infos = get_matching_parameters(ctx, sc, cs->first, cs2->first);
+        if (av_log_get_level() == AV_LOG_DEBUG) {
+            for (i = infos; i != NULL; i = i->next) {
+                av_log(ctx, AV_LOG_DEBUG, "Stage 2: matching pair at %d and %d, ratio %f, offset %d\n", i->first->index, i->second->index, i->framerateratio, i->offset);
+            }
+        }
+        /* stage 3: evaluation */
+        av_log(ctx, AV_LOG_DEBUG, "Stage 3: evaluate\n");
+        if (infos) {
+            bestmatch = evaluate_parameters(ctx, sc, infos, bestmatch, mode);
+            av_log(ctx, AV_LOG_DEBUG, "Stage 3: best matching pair at %d and %d, ratio %f, offset %d, score %d, %d frames matching\n", bestmatch.first->index, bestmatch.second->index, bestmatch.framerateratio, bestmatch.offset, bestmatch.score, bestmatch.matchframes);
+            sll_free(infos);
+        }
+    } while (find_next_coarsecandidate(sc, second->coarsesiglist, &cs, &cs2, 0) && !bestmatch.whole);
+    return bestmatch;
+
+}
diff --git a/libavfilter/version.h b/libavfilter/version.h
index e3bd8d0..4fdb487 100644
--- a/libavfilter/version.h
+++ b/libavfilter/version.h
@@ -30,7 +30,7 @@ 
 #include "libavutil/version.h"
 
 #define LIBAVFILTER_VERSION_MAJOR   6
-#define LIBAVFILTER_VERSION_MINOR  68
+#define LIBAVFILTER_VERSION_MINOR  69
 #define LIBAVFILTER_VERSION_MICRO 100
 
 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \
diff --git a/libavfilter/vf_signature.c b/libavfilter/vf_signature.c
new file mode 100644
index 0000000..3e62c68
--- /dev/null
+++ b/libavfilter/vf_signature.c
@@ -0,0 +1,768 @@ 
+/*
+ * Copyright (c) 2017 Gerion Entrup
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg 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.
+ *
+ * FFmpeg 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 FFmpeg; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/**
+ * @file
+ * MPEG-7 video signature calculation and lookup filter
+ * @see http://epubs.surrey.ac.uk/531590/1/MPEG-7%20Video%20Signature%20Author%27s%20Copy.pdf
+ */
+
+#include <float.h>
+#include "libavcodec/put_bits.h"
+#include "libavformat/avformat.h"
+#include "libavutil/opt.h"
+#include "libavutil/avstring.h"
+#include "libavutil/intreadwrite.h"
+#include "libavutil/timestamp.h"
+#include "avfilter.h"
+#include "internal.h"
+#include "signature.h"
+#include "signature_lookup.c"
+
+#define OFFSET(x) offsetof(SignatureContext, x)
+#define FLAGS AV_OPT_FLAG_VIDEO_PARAM
+#define BLOCK_LCM (int64_t) 476985600
+
+static const AVOption signature_options[] = {
+    { "detectmode", "set the detectmode",
+        OFFSET(mode),         AV_OPT_TYPE_INT,    {.i64 = MODE_OFF}, 0, NB_LOOKUP_MODE-1, FLAGS, "mode" },
+        { "off",  NULL, 0, AV_OPT_TYPE_CONST, {.i64 = MODE_OFF},  0, 0, .flags = FLAGS, "mode" },
+        { "full", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = MODE_FULL}, 0, 0, .flags = FLAGS, "mode" },
+        { "fast", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = MODE_FAST}, 0, 0, .flags = FLAGS, "mode" },
+    { "nb_inputs",  "number of inputs",
+        OFFSET(nb_inputs),    AV_OPT_TYPE_INT,    {.i64 = 1},        1, INT_MAX,          FLAGS },
+    { "filename",   "filename for output files",
+        OFFSET(filename),     AV_OPT_TYPE_STRING, {.str = ""},       0, NB_FORMATS-1,     FLAGS },
+    { "format",     "set output format",
+        OFFSET(format),       AV_OPT_TYPE_INT,    {.i64 = FORMAT_BINARY}, 0, 1,           FLAGS , "format" },
+        { "binary", 0, 0, AV_OPT_TYPE_CONST, {.i64=FORMAT_BINARY}, 0, 0, FLAGS, "format" },
+        { "xml",    0, 0, AV_OPT_TYPE_CONST, {.i64=FORMAT_XML},    0, 0, FLAGS, "format" },
+    { "th_d",       "threshold to detect one word as similar",
+        OFFSET(thworddist),   AV_OPT_TYPE_INT,    {.i64 = 9000},     1, INT_MAX,          FLAGS },
+    { "th_dc",      "threshold to detect all words as similar",
+        OFFSET(thcomposdist), AV_OPT_TYPE_INT,    {.i64 = 60000},    1, INT_MAX,          FLAGS },
+    { "th_xh",      "threshold to detect frames as similar",
+        OFFSET(thl1),         AV_OPT_TYPE_INT,    {.i64 = 116},      1, INT_MAX,          FLAGS },
+    { "th_di",      "minimum length of matching sequence in frames",
+        OFFSET(thdi),         AV_OPT_TYPE_INT,    {.i64 = 0},        0, INT_MAX,          FLAGS },
+    { "th_it",      "threshold for relation of good to all frames",
+        OFFSET(thit),         AV_OPT_TYPE_DOUBLE, {.dbl = 0.5},    0.0, 1.0,              FLAGS },
+    { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(signature);
+
+static int query_formats(AVFilterContext *ctx)
+{
+    /* all formats with a seperate gray value */
+    static const enum AVPixelFormat pix_fmts[] = {
+        AV_PIX_FMT_GRAY8,
+        AV_PIX_FMT_YUV410P, AV_PIX_FMT_YUV411P,
+        AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV422P,
+        AV_PIX_FMT_YUV440P, AV_PIX_FMT_YUV444P,
+        AV_PIX_FMT_YUVJ411P, AV_PIX_FMT_YUVJ420P,
+        AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ444P,
+        AV_PIX_FMT_YUVJ440P,
+        AV_PIX_FMT_NV12, AV_PIX_FMT_NV21,
+        AV_PIX_FMT_NONE
+    };
+
+    return ff_set_common_formats(ctx, ff_make_format_list(pix_fmts));
+}
+
+static int config_input(AVFilterLink *inlink)
+{
+    AVFilterContext *ctx = inlink->dst;
+    SignatureContext *sic = ctx->priv;
+    StreamContext *sc = &(sic->streamcontexts[FF_INLINK_IDX(inlink)]);
+
+    sc->time_base = inlink->time_base;
+    /* test for overflow */
+    sc->divide = (((uint64_t) inlink->w/32) * (inlink->w/32 + 1) * (inlink->h/32 * inlink->h/32 + 1) > INT64_MAX / (BLOCK_LCM * 255));
+    if (sc->divide) {
+        av_log(ctx, AV_LOG_WARNING, "Input dimension too high for precise calculation, numbers will be rounded.\n");
+    }
+    sc->w = inlink->w;
+    sc->h = inlink->h;
+    return 0;
+}
+
+static int get_block_size(const Block *b)
+{
+    return (b->to.y - b->up.y + 1) * (b->to.x - b->up.x + 1);
+}
+
+static uint64_t get_block_sum(StreamContext *sc, uint64_t intpic[32][32], const Block *b)
+{
+    uint64_t sum = 0;
+
+    int x0, y0, x1, y1;
+
+    x0 = b->up.x;
+    y0 = b->up.y;
+    x1 = b->to.x;
+    y1 = b->to.y;
+
+    if (x0-1 >= 0 && y0-1 >= 0) {
+        sum = intpic[y1][x1] + intpic[y0-1][x0-1] - intpic[y1][x0-1] - intpic[y0-1][x1];
+    } else if (x0-1 >= 0) {
+        sum = intpic[y1][x1] - intpic[y1][x0-1];
+    } else if (y0-1 >= 0) {
+        sum = intpic[y1][x1] - intpic[y0-1][x1];
+    } else {
+        sum = intpic[y1][x1];
+    }
+    return sum;
+}
+
+static int cmp(const uint64_t *a, const uint64_t *b)
+{
+    return *a < *b ? -1 : ( *a > *b ? 1 : 0 );
+}
+
+/**
+ * sets the bit at position pos to 1 in data
+ */
+static void set_bit(uint8_t* data, size_t pos)
+{
+    uint8_t mask = 1 << 7-(pos%8);
+    data[pos/8] |= mask;
+}
+
+static int filter_frame(AVFilterLink *inlink, AVFrame *picref)
+{
+    AVFilterContext *ctx = inlink->dst;
+    SignatureContext *sic = ctx->priv;
+    StreamContext *sc = &(sic->streamcontexts[FF_INLINK_IDX(inlink)]);
+    FineSignature* fs;
+
+    static const uint8_t pot3[5] = { 3*3*3*3, 3*3*3, 3*3, 3, 1 };
+    /* indexes of words : 210,217,219,274,334  44,175,233,270,273  57,70,103,237,269  100,285,295,337,354  101,102,111,275,296
+    s2usw = sorted to unsorted wordvec: 44 is at index 5, 57 at index 10...
+    */
+    static const unsigned int wordvec[25] = {44,57,70,100,101,102,103,111,175,210,217,219,233,237,269,270,273,274,275,285,295,296,334,337,354};
+    static const uint8_t      s2usw[25]   = { 5,10,11, 15, 20, 21, 12, 22,  6,  0,  1,  2,  7, 13, 14,  8,  9,  3, 23, 16, 17, 24,  4, 18, 19};
+
+    uint8_t wordt2b[5] = { 0, 0, 0, 0, 0 }; /* word ternary to binary */
+    uint64_t intpic[32][32];
+    uint64_t rowcount;
+    uint8_t *p = picref->data[0];
+    int inti, intj;
+    int *intjlut;
+
+    uint64_t conflist[DIFFELEM_SIZE];
+    int f = 0, g = 0, w = 0;
+    int32_t dh1 = 1, dh2 = 1, dw1 = 1, dw2 = 1, a, b;
+    int64_t denom;
+    int i, j, k, ternary;
+    uint64_t blocksum;
+    int blocksize;
+    int64_t th; /* threshold */
+    int64_t sum;
+
+    int64_t precfactor = (sc->divide) ? 65536 : BLOCK_LCM;
+
+    /* initialize fs */
+    if (sc->curfinesig) {
+        fs = av_mallocz(sizeof(FineSignature));
+        if (!fs)
+            return AVERROR(ENOMEM);
+        sc->curfinesig->next = fs;
+        fs->prev = sc->curfinesig;
+        sc->curfinesig = fs;
+    } else {
+        fs = sc->curfinesig = sc->finesiglist;
+        sc->curcoarsesig1->first = fs;
+    }
+
+    fs->pts = picref->pts;
+    fs->index = sc->lastindex++;
+
+    memset(intpic, 0, sizeof(uint64_t)*32*32);
+    intjlut = av_malloc_array(inlink->w, sizeof(int));
+    if (!intjlut)
+        return AVERROR(ENOMEM);
+    for (i = 0; i < inlink->w; i++) {
+        intjlut[i] = (i*32)/inlink->w;
+    }
+
+    for (i = 0; i < inlink->h; i++) {
+        inti = (i*32)/inlink->h;
+        for (j = 0; j < inlink->w; j++) {
+            intj = intjlut[j];
+            intpic[inti][intj] += p[j];
+        }
+        p += picref->linesize[0];
+    }
+    av_freep(&intjlut);
+
+    /* The following calculates a summed area table (intpic) and brings the numbers
+     * in intpic to the same denominator.
+     * So you only have to handle the numinator in the following sections.
+     */
+    dh1 = inlink->h / 32;
+    if (inlink->h % 32)
+        dh2 = dh1 + 1;
+    dw1 = inlink->w / 32;
+    if (inlink->w % 32)
+        dw2 = dw1 + 1;
+    denom = (sc->divide) ? dh1 * dh2 * dw1 * dw2 : 1;
+
+    for (i = 0; i < 32; i++) {
+        rowcount = 0;
+        a = 1;
+        if (dh2 > 1) {
+            a = ((inlink->h*(i+1))%32 == 0) ? (inlink->h*(i+1))/32 - 1 : (inlink->h*(i+1))/32;
+            a -= ((inlink->h*i)%32 == 0) ? (inlink->h*i)/32 - 1 : (inlink->h*i)/32;
+            a = (a == dh1)? dh2 : dh1;
+        }
+        for (j = 0; j < 32; j++) {
+            b = 1;
+            if (dw2 > 1) {
+                b = ((inlink->w*(j+1))%32 == 0) ? (inlink->w*(j+1))/32 - 1 : (inlink->w*(j+1))/32;
+                b -= ((inlink->w*j)%32 == 0) ? (inlink->w*j)/32 - 1 : (inlink->w*j)/32;
+                b = (b == dw1)? dw2 : dw1;
+            }
+            rowcount += intpic[i][j] * a * b * precfactor / denom;
+            if (i > 0) {
+                intpic[i][j] = intpic[i-1][j] + rowcount;
+            } else {
+                intpic[i][j] = rowcount;
+            }
+        }
+    }
+
+    denom = (sc->divide) ? 1 : dh1 * dh2 * dw1 * dw2;
+
+    for (i = 0; i < ELEMENT_COUNT; i++) {
+        const ElemCat* elemcat = elements[i];
+        int64_t* elemsignature;
+        uint64_t* sortsignature;
+
+        elemsignature = av_malloc_array(elemcat->elem_count, sizeof(int64_t));
+        if (!elemsignature)
+            return AVERROR(ENOMEM);
+        sortsignature = av_malloc_array(elemcat->elem_count, sizeof(int64_t));
+        if (!sortsignature)
+            return AVERROR(ENOMEM);
+
+        for (j = 0; j < elemcat->elem_count; j++) {
+            blocksum = 0;
+            blocksize = 0;
+            for (k = 0; k < elemcat->left_count; k++) {
+                blocksum += get_block_sum(sc, intpic, &elemcat->blocks[j*elemcat->block_count+k]);
+                blocksize += get_block_size(&elemcat->blocks[j*elemcat->block_count+k]);
+            }
+            sum = blocksum / blocksize;
+            if (elemcat->av_elem) {
+                sum -= 128 * precfactor * denom;
+            } else {
+                blocksum = 0;
+                blocksize = 0;
+                for (; k < elemcat->block_count; k++) {
+                    blocksum += get_block_sum(sc, intpic, &elemcat->blocks[j*elemcat->block_count+k]);
+                    blocksize += get_block_size(&elemcat->blocks[j*elemcat->block_count+k]);
+                }
+                sum -= blocksum / blocksize;
+                conflist[g++] = FFABS(sum * 8 / (precfactor * denom));
+            }
+
+            elemsignature[j] = sum;
+            sortsignature[j] = FFABS(sum);
+        }
+
+        /* get threshold */
+        qsort(sortsignature, elemcat->elem_count, sizeof(uint64_t), (void*) cmp);
+        th = sortsignature[(int) (elemcat->elem_count*0.333)];
+
+        /* ternarize */
+        for (j = 0; j < elemcat->elem_count; j++) {
+            if (elemsignature[j] < -th) {
+                ternary = 0;
+            } else if (elemsignature[j] <= th) {
+                ternary = 1;
+            } else {
+                ternary = 2;
+            }
+            fs->framesig[f/5] += ternary * pot3[f%5];
+
+            if (f == wordvec[w]) {
+                fs->words[s2usw[w]/5] += ternary * pot3[wordt2b[s2usw[w]/5]++];
+                if (w < 24)
+                    w++;
+            }
+            f++;
+        }
+        av_freep(&elemsignature);
+        av_freep(&sortsignature);
+    }
+
+    /* confidence */
+    qsort(conflist, DIFFELEM_SIZE, sizeof(uint64_t), (void*) cmp);
+    fs->confidence = FFMIN(conflist[DIFFELEM_SIZE/2], 255);
+
+    /* coarsesignature */
+    if (sc->coarsecount == 0) {
+        if (sc->curcoarsesig2) {
+            sc->curcoarsesig1 = av_mallocz(sizeof(CoarseSignature));
+            if (!sc->curcoarsesig1)
+                return AVERROR(ENOMEM);
+            sc->curcoarsesig1->first = fs;
+            sc->curcoarsesig2->next = sc->curcoarsesig1;
+            sc->coarseend = sc->curcoarsesig1;
+        }
+    }
+    if (sc->coarsecount == 45) {
+        sc->midcoarse = 1;
+        sc->curcoarsesig2 = av_mallocz(sizeof(CoarseSignature));
+        if (!sc->curcoarsesig2)
+            return AVERROR(ENOMEM);
+        sc->curcoarsesig2->first = fs;
+        sc->curcoarsesig1->next = sc->curcoarsesig2;
+        sc->coarseend = sc->curcoarsesig2;
+    }
+    for (i = 0; i < 5; i++) {
+        set_bit(sc->curcoarsesig1->data[i], fs->words[i]);
+    }
+    /* assuming the actual frame is the last */
+    sc->curcoarsesig1->last = fs;
+    if (sc->midcoarse) {
+        for (i = 0; i < 5; i++) {
+            set_bit(sc->curcoarsesig2->data[i], fs->words[i]);
+        }
+        sc->curcoarsesig2->last = fs;
+    }
+
+    sc->coarsecount = (sc->coarsecount+1)%90;
+
+    /* debug printing finesignature */
+    if (av_log_get_level() == AV_LOG_DEBUG) {
+        av_log(ctx, AV_LOG_DEBUG, "input %d, confidence: %d\n", FF_INLINK_IDX(inlink), fs->confidence);
+
+        av_log(ctx, AV_LOG_DEBUG, "words:");
+        for (i = 0; i < 5; i++) {
+            av_log(ctx, AV_LOG_DEBUG, " %d:", fs->words[i] );
+            av_log(ctx, AV_LOG_DEBUG, " %d", fs->words[i] / pot3[0] );
+            for (j = 1; j < 5; j++)
+                av_log(ctx, AV_LOG_DEBUG, ",%d", fs->words[i] % pot3[j-1] / pot3[j] );
+            av_log(ctx, AV_LOG_DEBUG, ";");
+        }
+        av_log(ctx, AV_LOG_DEBUG, "\n");
+
+        av_log(ctx, AV_LOG_DEBUG, "framesignature:");
+        for (i = 0; i < SIGELEM_SIZE/5; i++) {
+            av_log(ctx, AV_LOG_DEBUG, " %d", fs->framesig[i] / pot3[0] );
+            for (j = 1; j < 5; j++)
+                av_log(ctx, AV_LOG_DEBUG, ",%d", fs->framesig[i] % pot3[j-1] / pot3[j] );
+        }
+        av_log(ctx, AV_LOG_DEBUG, "\n");
+    }
+
+    if (FF_INLINK_IDX(inlink) == 0)
+        return ff_filter_frame(inlink->dst->outputs[0], picref);
+    return 1;
+}
+
+static int xml_export(AVFilterContext *ctx, StreamContext *sc, const char* filename)
+{
+    FineSignature* fs;
+    CoarseSignature* cs;
+    int i, j;
+    FILE* f;
+    unsigned int pot3[5] = { 3*3*3*3, 3*3*3, 3*3, 3, 1 };
+
+    f = fopen(filename, "w");
+    if (!f) {
+        int err = AVERROR(EINVAL);
+        char buf[128];
+        av_strerror(err, buf, sizeof(buf));
+        av_log(ctx, AV_LOG_ERROR, "cannot open xml file %s: %s\n", filename, buf);
+        return err;
+    }
+
+    /* header */
+    fprintf(f, "<?xml version='1.0' encoding='ASCII' ?>\n");
+    fprintf(f, "<Mpeg7 xmlns=\"urn:mpeg:mpeg7:schema:2001\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"urn:mpeg:mpeg7:schema:2001 schema/Mpeg7-2001.xsd\">\n");
+    fprintf(f, "  <DescriptionUnit xsi:type=\"DescriptorCollectionType\">\n");
+    fprintf(f, "    <Descriptor xsi:type=\"VideoSignatureType\">\n");
+    fprintf(f, "      <VideoSignatureRegion>\n");
+    fprintf(f, "        <VideoSignatureSpatialRegion>\n");
+    fprintf(f, "          <Pixel>0 0 </Pixel>\n");
+    fprintf(f, "          <Pixel>%d %d </Pixel>\n", sc->w - 1, sc->h - 1);
+    fprintf(f, "        </VideoSignatureSpatialRegion>\n");
+    fprintf(f, "        <StartFrameOfSpatialRegion>0</StartFrameOfSpatialRegion>\n");
+    /* hoping num is 1, other values are vague */
+    fprintf(f, "        <MediaTimeUnit>%d</MediaTimeUnit>\n", sc->time_base.den / sc->time_base.num);
+    fprintf(f, "        <MediaTimeOfSpatialRegion>\n");
+    fprintf(f, "          <StartMediaTimeOfSpatialRegion>0</StartMediaTimeOfSpatialRegion>\n");
+    fprintf(f, "          <EndMediaTimeOfSpatialRegion>%" PRIu64 "</EndMediaTimeOfSpatialRegion>\n", sc->coarseend->last->pts);
+    fprintf(f, "        </MediaTimeOfSpatialRegion>\n");
+
+    /* coarsesignatures */
+    for (cs = sc->coarsesiglist; cs; cs = cs->next) {
+        fprintf(f, "        <VSVideoSegment>\n");
+        fprintf(f, "          <StartFrameOfSegment>%" PRIu32 "</StartFrameOfSegment>\n", cs->first->index);
+        fprintf(f, "          <EndFrameOfSegment>%" PRIu32 "</EndFrameOfSegment>\n", cs->last->index);
+        fprintf(f, "          <MediaTimeOfSegment>\n");
+        fprintf(f, "            <StartMediaTimeOfSegment>%" PRIu64 "</StartMediaTimeOfSegment>\n", cs->first->pts);
+        fprintf(f, "            <EndMediaTimeOfSegment>%" PRIu64 "</EndMediaTimeOfSegment>\n", cs->last->pts);
+        fprintf(f, "          </MediaTimeOfSegment>\n");
+        for (i = 0; i < 5; i++) {
+            fprintf(f, "          <BagOfWords>");
+            for (j = 0; j < 31; j++) {
+                uint8_t n = cs->data[i][j];
+                if (j < 30) {
+                    fprintf(f, "%d  %d  %d  %d  %d  %d  %d  %d  ", (n & 0x80) >> 7,
+                                                                   (n & 0x40) >> 6,
+                                                                   (n & 0x20) >> 5,
+                                                                   (n & 0x10) >> 4,
+                                                                   (n & 0x08) >> 3,
+                                                                   (n & 0x04) >> 2,
+                                                                   (n & 0x02) >> 1,
+                                                                   (n & 0x01));
+                } else {
+                    /* print only 3 bit in last byte */
+                    fprintf(f, "%d  %d  %d ", (n & 0x80) >> 7,
+                                              (n & 0x40) >> 6,
+                                              (n & 0x20) >> 5);
+                }
+            }
+            fprintf(f, "</BagOfWords>\n");
+        }
+        fprintf(f, "        </VSVideoSegment>\n");
+    }
+
+    /* finesignatures */
+    for (fs = sc->finesiglist; fs; fs = fs->next) {
+        fprintf(f, "        <VideoFrame>\n");
+        fprintf(f, "          <MediaTimeOfFrame>%" PRIu64 "</MediaTimeOfFrame>\n", fs->pts);
+        /* confidence */
+        fprintf(f, "          <FrameConfidence>%d</FrameConfidence>\n", fs->confidence);
+        /* words */
+        fprintf(f, "          <Word>");
+        for (i = 0; i < 5; i++) {
+            fprintf(f, "%d ", fs->words[i]);
+            if (i < 4) {
+                fprintf(f, " ");
+            }
+        }
+        fprintf(f, "</Word>\n");
+        /* framesignature */
+        fprintf(f, "          <FrameSignature>");
+        for (i = 0; i< SIGELEM_SIZE/5; i++) {
+            if (i > 0) {
+                fprintf(f, " ");
+            }
+            fprintf(f, "%d ", fs->framesig[i] / pot3[0]);
+            for (j = 1; j < 5; j++)
+                fprintf(f, " %d ", fs->framesig[i] % pot3[j-1] / pot3[j] );
+        }
+        fprintf(f, "</FrameSignature>\n");
+        fprintf(f, "        </VideoFrame>\n");
+    }
+    fprintf(f, "      </VideoSignatureRegion>\n");
+    fprintf(f, "    </Descriptor>\n");
+    fprintf(f, "  </DescriptionUnit>\n");
+    fprintf(f, "</Mpeg7>\n");
+
+    fclose(f);
+    return 0;
+}
+
+static int binary_export(AVFilterContext *ctx, StreamContext *sc, const char* filename)
+{
+    FILE* f;
+    FineSignature* fs;
+    CoarseSignature* cs;
+    uint32_t numofsegments = (sc->lastindex + 44)/45;
+    int i, j;
+    PutBitContext buf;
+    /* buffer + header + coarsesignatures + finesignature */
+    int len = (512 + 6 * 32 + 3*16 + 2 +
+        numofsegments * (4*32 + 1 + 5*243) +
+        sc->lastindex * (2 + 32 + 6*8 + 608)) / 8;
+    uint8_t* buffer = av_malloc_array(len, sizeof(uint8_t));
+    if (!buffer)
+        return AVERROR(ENOMEM);
+
+    f = fopen(filename, "wb");
+    if (!f) {
+        int err = AVERROR(EINVAL);
+        char buf[128];
+        av_strerror(err, buf, sizeof(buf));
+        av_log(ctx, AV_LOG_ERROR, "cannot open file %s: %s\n", filename, buf);
+        return err;
+    }
+    init_put_bits(&buf, buffer, len);
+
+    put_bits32(&buf, 1); /* NumOfSpatial Regions, only 1 supported */
+    put_bits(&buf, 1, 1); /* SpatialLocationFlag, always the whole image */
+    put_bits32(&buf, 0); /* PixelX,1 PixelY,1, 0,0 */
+    put_bits(&buf, 16, sc->w-1 & 0xFFFF); /* PixelX,2 */
+    put_bits(&buf, 16, sc->h-1 & 0xFFFF); /* PixelY,2 */
+    put_bits32(&buf, 0); /* StartFrameOfSpatialRegion */
+    put_bits32(&buf, sc->lastindex); /* NumOfFrames */
+    /* hoping num is 1, other values are vague */
+    /* den/num might be greater than 16 bit, so cutting it */
+    put_bits(&buf, 16, 0xFFFF & (sc->time_base.den / sc->time_base.num)); /* MediaTimeUnit */
+    put_bits(&buf, 1, 1); /* MediaTimeFlagOfSpatialRegion */
+    put_bits32(&buf, 0); /* StartMediaTimeOfSpatialRegion */
+    put_bits32(&buf, 0xFFFFFFFF & sc->coarseend->last->pts); /* EndMediaTimeOfSpatialRegion */
+    put_bits32(&buf, numofsegments); /* NumOfSegments */
+    /* coarsesignatures */
+    for (cs = sc->coarsesiglist; cs; cs = cs->next) {
+        put_bits32(&buf, cs->first->index); /* StartFrameOfSegment */
+        put_bits32(&buf, cs->last->index); /* EndFrameOfSegment */
+        put_bits(&buf, 1, 1); /* MediaTimeFlagOfSegment */
+        put_bits32(&buf, 0xFFFFFFFF & cs->first->pts); /* StartMediaTimeOfSegment */
+        put_bits32(&buf, 0xFFFFFFFF & cs->last->pts); /* EndMediaTimeOfSegment */
+        for (i = 0; i < 5; i++) {
+            /* put 243 bits ( = 7 * 32 + 19 = 8 * 28 + 19) into buffer */
+            for (j = 0; j < 30; j++) {
+                put_bits(&buf, 8, cs->data[i][j]);
+            }
+            put_bits(&buf, 3, cs->data[i][30] >> 5);
+        }
+    }
+    /* finesignatures */
+    put_bits(&buf, 1, 0); /* CompressionFlag, only 0 supported */
+    for (fs = sc->finesiglist; fs; fs = fs->next) {
+        put_bits(&buf, 1, 1); /* MediaTimeFlagOfFrame */
+        put_bits32(&buf, 0xFFFFFFFF & fs->pts); /* MediaTimeOfFrame */
+        put_bits(&buf, 8, fs->confidence); /* FrameConfidence */
+        for (i = 0; i < 5; i++) {
+            put_bits(&buf, 8, fs->words[i]); /* Words */
+        }
+        /* framesignature */
+        for (i = 0; i < SIGELEM_SIZE/5; i++) {
+            put_bits(&buf, 8, fs->framesig[i]);
+        }
+    }
+
+    avpriv_align_put_bits(&buf);
+    flush_put_bits(&buf);
+    fwrite(buffer, 1, put_bits_count(&buf)/8, f);
+    fclose(f);
+    av_freep(&buffer);
+    return 0;
+}
+
+static int export(AVFilterContext *ctx, StreamContext *sc, int input)
+{
+    SignatureContext* sic = ctx->priv;
+    char filename[1024];
+
+    if (sic->nb_inputs > 1) {
+        /* error already handled */
+        av_get_frame_filename(filename, sizeof(filename), sic->filename, input);
+    } else {
+        strcpy(filename, sic->filename);
+    }
+    if (sic->format == FORMAT_XML) {
+        return xml_export(ctx, sc, filename);
+    } else {
+        return binary_export(ctx, sc, filename);
+    }
+
+}
+
+static int request_frame(AVFilterLink *outlink)
+{
+    AVFilterContext *ctx = outlink->src;
+    SignatureContext *sic = ctx->priv;
+    StreamContext *sc, *sc2;
+    MatchingInfo match;
+    int i, j, ret;
+    int lookup = 1; /* indicates wheather EOF of all files is reached */
+
+    /* process all inputs */
+    for (i = 0; i < sic->nb_inputs; i++){
+        sc = &(sic->streamcontexts[i]);
+
+        ret = ff_request_frame(ctx->inputs[i]);
+
+        /* return if unexpected error occurs in input stream */
+        if (ret < 0 && ret != AVERROR_EOF)
+            return ret;
+
+        /* export signature at EOF */
+        if (ret == AVERROR_EOF) {
+            /* export if wanted */
+            if (strlen(sic->filename) > 0) {
+                if (export(ctx, sc, i) < 0)
+                    return ret;
+            }
+            sc->exported = 1;
+        }
+        lookup &= sc->exported;
+    }
+
+    /* signature lookup */
+    if (lookup && sic->mode != MODE_OFF) {
+        /* iterate over every pair */
+        for (i = 0; i < sic->nb_inputs; i++) {
+            sc = &(sic->streamcontexts[i]);
+            for (j = i+1; j < sic->nb_inputs; j++) {
+                sc2 = &(sic->streamcontexts[j]);
+                match = lookup_signatures(ctx, sic, sc, sc2, sic->mode);
+                if (match.score != 0) {
+                    av_log(ctx, AV_LOG_INFO, "matching of video %d at %f and %d at %f, %d frames matching\n",
+                            i, ((double) match.first->pts * sc->time_base.num) / sc->time_base.den,
+                            j, ((double) match.second->pts * sc2->time_base.num) / sc2->time_base.den,
+                            match.matchframes);
+                    if (match.whole)
+                        av_log(ctx, AV_LOG_INFO, "whole video matching\n");
+                } else {
+                    av_log(ctx, AV_LOG_INFO, "no matching of video %d and %d\n", i, j);
+                }
+            }
+        }
+    }
+
+    return ret;
+}
+
+static av_cold int init(AVFilterContext *ctx)
+{
+
+    SignatureContext *sic = ctx->priv;
+    StreamContext *sc;
+    int i, ret;
+    char tmp[1024];
+
+    sic->streamcontexts = av_mallocz(sic->nb_inputs * sizeof(StreamContext));
+    if (!sic->streamcontexts)
+        return AVERROR(ENOMEM);
+
+    for (i = 0; i < sic->nb_inputs; i++) {
+        AVFilterPad pad = {
+            .type = AVMEDIA_TYPE_VIDEO,
+            .name = av_asprintf("in%d", i),
+            .config_props = config_input,
+            .filter_frame = filter_frame,
+        };
+
+        if (!pad.name)
+            return AVERROR(ENOMEM);
+
+        sc = &(sic->streamcontexts[i]);
+
+        sc->lastindex = 0;
+        sc->finesiglist = av_mallocz(sizeof(FineSignature));
+        if (!sc->finesiglist)
+            return AVERROR(ENOMEM);
+        sc->curfinesig = NULL;
+
+        sc->coarsesiglist = av_mallocz(sizeof(CoarseSignature));
+        if (!sc->coarsesiglist)
+            return AVERROR(ENOMEM);
+        sc->curcoarsesig1 = sc->coarsesiglist;
+        sc->coarseend = sc->coarsesiglist;
+        sc->coarsecount = 0;
+        sc->midcoarse = 0;
+
+        if ((ret = ff_insert_inpad(ctx, i, &pad)) < 0) {
+            av_freep(&pad.name);
+            return ret;
+        }
+    }
+
+    /* check filename */
+    if (sic->nb_inputs > 1 && strlen(sic->filename) > 0 && av_get_frame_filename(tmp, sizeof(tmp), sic->filename, 0) == -1) {
+        av_log(ctx, AV_LOG_ERROR, "The filename must contain %%d or %%0nd, if you have more than one input.\n");
+        return AVERROR(EINVAL);
+    }
+
+    return 0;
+}
+
+
+
+static av_cold void uninit(AVFilterContext *ctx)
+{
+    SignatureContext *sic = ctx->priv;
+    StreamContext *sc;
+    void* tmp;
+    FineSignature* finsig;
+    CoarseSignature* cousig;
+    int i;
+
+
+    /* free the lists */
+    if (sic->streamcontexts != NULL) {
+        for (i = 0; i < sic->nb_inputs; i++) {
+            sc = &(sic->streamcontexts[i]);
+            finsig = sc->finesiglist;
+            cousig = sc->coarsesiglist;
+
+            while (finsig) {
+                tmp = finsig;
+                finsig = finsig->next;
+                av_freep(&tmp);
+            }
+            sc->finesiglist = NULL;
+
+            while (cousig) {
+                tmp = cousig;
+                cousig = cousig->next;
+                av_freep(&tmp);
+            }
+            sc->coarsesiglist = NULL;
+        }
+        av_freep(&sic->streamcontexts);
+    }
+}
+
+static int config_output(AVFilterLink *outlink)
+{
+    AVFilterContext *ctx = outlink->src;
+    AVFilterLink *inlink = ctx->inputs[0];
+
+    outlink->time_base = inlink->time_base;
+    outlink->frame_rate = inlink->frame_rate;
+    outlink->sample_aspect_ratio = inlink->sample_aspect_ratio;
+    outlink->w = inlink->w;
+    outlink->h = inlink->h;
+
+    return 0;
+}
+
+static const AVFilterPad signature_outputs[] = {
+    {
+        .name          = "default",
+        .type          = AVMEDIA_TYPE_VIDEO,
+        .request_frame = request_frame,
+        .config_props  = config_output,
+    },
+    { NULL }
+};
+
+AVFilter ff_vf_signature = {
+    .name          = "signature",
+    .description   = NULL_IF_CONFIG_SMALL("Calculate the MPEG-7 video signature"),
+    .priv_size     = sizeof(SignatureContext),
+    .priv_class    = &signature_class,
+    .init          = init,
+    .uninit        = uninit,
+    .query_formats = query_formats,
+    .outputs       = signature_outputs,
+    .inputs        = NULL,
+    .flags         = AVFILTER_FLAG_DYNAMIC_INPUTS,
+};
-- 
2.10.2