https://bitbucket.org/daniel_fort/magic-lantern
Raw File
Tip revision: 2196a02f1ec317613c0f3cf3d38431968dc60280 authored by Daniel Fort on 02 December 2016, 15:53:23 UTC
Close branch 1200D.
Tip revision: 2196a02
mlv_dump.c
/*
 * Copyright (C) 2013 Magic Lantern Team
 *
 * 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.
 *
 * 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.,
 * 51 Franklin Street, Fifth Floor,
 * Boston, MA  02110-1301, USA.
 */

/* system includes */
#include <math.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <strings.h>
#include <getopt.h>
#include <inttypes.h>
#include <time.h>

/* dng related headers */
#include <chdk-dng.h>
#include "../dual_iso/wirth.h"  /* fast median, generic implementation (also kth_smallest) */
#include "../dual_iso/optmed.h" /* fast median for small common array sizes (3, 7, 9...) */

#ifdef __WIN32
#define FMT_SIZE "%u"
#else
#define FMT_SIZE "%zd"
#endif

#define ERR_OK              0
#define ERR_STRUCT_ALIGN    1
#define ERR_PARAM           2
#define ERR_FILE            3
#define ERR_INDEX_REQ       4
#define ERR_MALLOC          5

#if defined(USE_LUA)
#define LUA_LIB
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
#else
typedef void lua_State;
#endif

/* helper macros */
#define MAX(a,b) \
   ({ __typeof__ (a) _a = (a); \
       __typeof__ (b) _b = (b); \
     _a > _b ? _a : _b; })


#define MIN(a,b) \
   ({ __typeof__ (a) _a = (a); \
       __typeof__ (b) _b = (b); \
     _a < _b ? _a : _b; })

#define ABS(a) \
   ({ __typeof__ (a) _a = (a); \
     _a > 0 ? _a : -_a; })

#define SGN(a) \
   ((a) > 0 ? 1 : -1 )

#define COERCE(val,min,max) MIN(MAX((val),(min)),(max))
#define COUNT(x)        ((int)(sizeof(x)/sizeof((x)[0])))

#define MSG_INFO     0
#define MSG_ERROR    1
#define MSG_PROGRESS 2


/* some compile warning, why? */
char *strdup(const char *s);

#ifdef MLV_USE_LZMA
#include <LzmaLib.h>
#endif

/* project includes */
#include "../lv_rec/lv_rec.h"
#include "../../src/raw.h"
#include "mlv.h"

enum bug_id
{
    BUG_ID_NONE = 0,
    /* 
        this bug results in wrong block sizes in a VIDF, even with unaligned lenghs. 
        when this fix is enabled and an unknown block is encountered, scan the area 
        for a NULL block which should follow right after the VIDF.
        introduced: 9058cbc13fa4 
        fixed in  : 2da80f3de3d1 
        */
    BUG_ID_BLOCKSIZE_WRONG = 1,
    /* 
        dont know yet where this bug comes from. was reported in http://www.magiclantern.fm/forum/index.php?topic=14703
    */
    BUG_ID_FRAMEDATA_MISALIGN = 2
};

int batch_mode = 0;

void print_msg(uint32_t type, const char* format, ... )
{
    va_list args;
    va_start( args, format );
    char *fmt_str = malloc(strlen(format) + 32);

    switch(type)
    {
        case MSG_INFO:
            if(!batch_mode)
            {
                vfprintf(stdout, format, args);
            }
            else
            {
                strcpy(fmt_str, "[I] ");
                strcat(fmt_str, format);
                vfprintf(stdout, fmt_str, args);
            }
            break;

        case MSG_ERROR:
            if(!batch_mode)
            {
                strcpy(fmt_str, "[ERROR] ");
                strcat(fmt_str, format);
                vfprintf(stderr, fmt_str, args);
            }
            else
            {
                strcpy(fmt_str, "[E] ");
                strcat(fmt_str, format);
                vfprintf(stdout, fmt_str, args);
                fflush(stdout);
            }
            break;

        case MSG_PROGRESS:
            if(!batch_mode)
            {
            }
            else
            {
                strcpy(fmt_str, "[P] ");
                strcat(fmt_str, format);
                vfprintf(stdout, fmt_str, args);
            }
            break;
    }

    free(fmt_str);
    va_end( args );
}


/* based on http://www.lua.org/pil/25.3.html */
int32_t lua_call_va(lua_State *L, const char *func, const char *sig, ...)
{
    va_list vl;

    va_start(vl, sig);
#if defined(USE_LUA)
    int narg, nres;  /* number of arguments and results */
    int verbose = 0;
    
    lua_getglobal(L, func);  /* get function */

    /* push arguments */
    narg = 0;
    while (*sig)
    {
        /* push arguments */
        switch (*sig++)
        {
            case 'v':
                verbose = 1;
                break;

            case 'd':  /* double argument */
                lua_pushnumber(L, va_arg(vl, double));
                break;

            case 'i':  /* int argument */
                lua_pushnumber(L, va_arg(vl, int));
                break;

            case 's':  /* string argument */
                lua_pushstring(L, va_arg(vl, char *));
                break;

            case 'l':  /* lstring argument */
            {
                char *ptr = va_arg(vl, char *);
                int len = va_arg(vl, int);
                lua_pushlstring(L, ptr, len);
                break;
            }

            case '>':
                goto endwhile;

            default:
                return -5;
        }
        narg++;
        luaL_checkstack(L, 1, "too many arguments");
    }
    endwhile:

    /* do the call */
    nres = strlen(sig);  /* number of expected results */
    int ret = lua_pcall(L, narg, nres, 0);

    if (ret != 0)  /* do the call */
    {
        if (lua_isstring(L, -1))
        {
            //if(1 ||verbose)
            {
                print_msg(MSG_INFO, "LUA: Error while calling '%s': '%s'\n", func, lua_tostring(L, -1));
            }
        }
        lua_pop(L, -1);
        return -4;
    }


    /* retrieve results */
    nres = -nres;  /* stack index of first result */
    while (lua_gettop(L) && *sig)
    {
        /* get results */
        switch (*sig++)
        {
            case 'd':  /* double result */
                if (!lua_isnumber(L, nres))
                {
                    if(verbose)
                    {
                        print_msg(MSG_INFO, "LUA: Error while calling '%s': Expected double, got %d\n", func, lua_type(L, nres));
                    }
                    lua_pop(L, -1);
                    break;
                }
                *va_arg(vl, double *) = lua_tonumber(L, nres);
                lua_pop(L, -1);
                break;

            case 'i':  /* int result */
                if (!lua_isnumber(L, nres))
                {
                    if(verbose)
                    {
                        print_msg(MSG_INFO, "LUA: Error while calling '%s': Expected number, got %d\n", func, lua_type(L, nres));
                    }
                    lua_pop(L, -1);
                    break;
                }
                *va_arg(vl, int *) = (int)lua_tonumber(L, nres);
                lua_pop(L, -1);
                break;

            case 's':  /* string result */
                if (!lua_isstring(L, nres))
                {
                    if(verbose)
                    {
                        print_msg(MSG_INFO, "LUA: Error while calling '%s': Expected string, got %d\n", func, lua_type(L, nres));
                    }
                    lua_pop(L, -1);
                    break;
                }
                *va_arg(vl, const char **) = lua_tostring(L, nres);
                lua_pop(L, -1);
                break;

            case 'l':  /* lstring result */
            {
                if (!lua_isstring(L, nres))
                {
                    if(verbose)
                    {
                        print_msg(MSG_INFO, "LUA: Error while calling '%s': Expected string, got %d\n", func, lua_type(L, nres));
                    }
                    lua_pop(L, -1);
                    break;
                }
                const char **ret_data = va_arg(vl, const char **);
                size_t *ret_length = va_arg(vl, size_t *);

                *ret_data = lua_tolstring(L, nres, ret_length);
                lua_pop(L, -1);
                break;
            }

            default:
                print_msg(MSG_INFO, "LUA: Error while calling '%s': Expected unknown, got %d\n", func, lua_type(L, nres));
                lua_pop(L, -1);
                break;
        }
        nres++;
    }
#else
    (void)L;
    (void)func;
    (void)sig;

    /* consume all vararg arguments */
    while (*sig)
    {
        switch (*sig++)
        {
            case 'd':
                va_arg(vl, double);
                break;
            case 'i':
                va_arg(vl, int);
                break;
            case 's':
                va_arg(vl, char *);
                break;
            case 'l':
                va_arg(vl, char *);
                va_arg(vl, int);
                break;
            case '>':
                break;
        }
    }
#endif

    va_end(vl);

    return 0;
}


int32_t lua_handle_hdr_suffix(lua_State *lua_state, uint8_t *type, char *suffix, void *hdr, int hdr_len, void *data, int data_len)
{
#if defined(USE_LUA)
    uint8_t *ret_hdr = NULL;
    uint8_t *ret_data = NULL;
    int ret_hdr_len = 0;
    int ret_data_len = 0;
    char func[128];

    snprintf(func, sizeof(func), "handle_%.4s%s", type, suffix);

    if(data)
    {
        lua_call_va(lua_state, func, "ll>ll", hdr, hdr_len, data, data_len, &ret_hdr, &ret_hdr_len, &ret_data, &ret_data_len);
    }
    else
    {
        lua_call_va(lua_state, func, "l>l", hdr, hdr_len, &ret_hdr, &ret_hdr_len);
    }

    /* callee updated block header */
    if(ret_hdr_len > 0 && ret_hdr_len == hdr_len)
    {
        print_msg(MSG_INFO, "LUA: Function '%s' updated hdr data\n", func);
        memcpy(hdr, ret_hdr, hdr_len);
    }
    else if(ret_hdr_len)
    {
        print_msg(MSG_INFO, "LUA: Error while calling '%s': Returned header size mismatch - %d instead of %d\n", func, ret_hdr_len, hdr_len);
    }

    /* callee updated block data */
    if(ret_data_len > 0 && ret_data_len == data_len)
    {
        print_msg(MSG_INFO, "LUA: Function '%s' updated hdr data\n", func);
        memcpy(data, ret_data, data_len);
    }
    else if(ret_data_len)
    {
        print_msg(MSG_INFO, "LUA: Error while calling '%s': Returned data size mismatch - %d instead of %d\n", func, ret_data_len, data_len);
    }
#else
    (void)lua_state;
    (void)type;
    (void)suffix;
    (void)hdr;
    (void)hdr_len;
    (void)data;
    (void)data_len;
#endif
    return 0;
}

int32_t lua_handle_hdr(lua_State *lua_state, uint8_t *type, void *hdr, int hdr_len)
{
    return lua_handle_hdr_suffix(lua_state, type, "", hdr, hdr_len, NULL, 0);
}

int32_t lua_handle_hdr_data(lua_State *lua_state, uint8_t *type, char *suffix, void *hdr, int hdr_len, void *data, int data_len)
{
    return lua_handle_hdr_suffix(lua_state, type, suffix, hdr, hdr_len, data, data_len);
}

/* platform/target specific fseek/ftell functions go here */
uint64_t file_get_pos(FILE *stream)
{
#if defined(__WIN32)
    return ftello64(stream);
#else
    return ftello(stream);
#endif
}

uint32_t file_set_pos(FILE *stream, uint64_t offset, int whence)
{
#if defined(__WIN32)
    return fseeko64(stream, offset, whence);
#else
    return fseeko(stream, offset, whence);
#endif
}


/* this structure is used to build the mlv_xref_t table */
typedef struct
{
    uint64_t    frameTime;
    uint64_t    frameOffset;
    uint16_t    fileNumber;
    uint16_t    frameType;
} frame_xref_t;

void xref_resize(frame_xref_t **table, int entries, int *allocated)
{
    /* make sure there is no crappy pointer before using */
    if(*allocated == 0)
    {
        *table = NULL;
    }

    /* only resize if the buffer is too small */
    if(entries * sizeof(frame_xref_t) > (uint32_t)(*allocated))
    {
        *allocated += (entries + 1) * sizeof(frame_xref_t);
        *table = realloc(*table, *allocated);
    }
}

void xref_dump(mlv_xref_hdr_t *xref)
{
    mlv_xref_t *xrefs = (mlv_xref_t*)&(((unsigned char *)xref)[sizeof(mlv_xref_hdr_t)]);

    for(uint32_t pos = 0; pos < xref->entryCount; pos++)
    {
        print_msg(MSG_INFO, "Entry %d/%d\n", pos + 1, xref->entryCount);
        print_msg(MSG_INFO, "    File   #%d\n", xrefs[pos].fileNumber);
        print_msg(MSG_INFO, "    Offset 0x%08X\n", xrefs[pos].frameOffset);
        switch (xrefs[pos].frameType)
        {
            case MLV_FRAME_VIDF:
                print_msg(MSG_INFO, "    Type   VIDF\n");
                break;
            case MLV_FRAME_AUDF:
                print_msg(MSG_INFO, "    Type   AUDF\n");
                break;
            default:
                break;
        }
    }
}


void xref_sort(frame_xref_t *table, int entries)
{
    int n = entries;
    do
    {
        int newn = 1;
        for (int i = 0; i < n-1; ++i)
        {
            if (table[i].frameTime > table[i+1].frameTime)
            {
                frame_xref_t tmp = table[i+1];
                table[i+1] = table[i];
                table[i] = tmp;
                newn = i + 1;
            }
        }
        n = newn;
    } while (n > 1);
}

void bitinsert(uint16_t *dst, int position, int depth, uint16_t new_value)
{
    uint16_t old_value = 0;
    int dst_pos = position * depth / 16;
    int bits_to_left = ((depth * position) - (16 * dst_pos)) % 16;
    int shift_right = 16 - depth - bits_to_left;

    old_value = dst[dst_pos];
    if(shift_right >= 0)
    {
        /* this case is a bit simpler. the word fits into this uint16_t */
        uint16_t mask = ((1<<depth)-1) << shift_right;

        /* shift and mask out */
        new_value <<= shift_right;
        new_value &= mask;
        old_value &= ~mask;

        /* now combine */
        new_value |= old_value;
        dst[dst_pos] = new_value;
    }
    else
    {
        /* here we need two operations as the bits are split over two words */
        uint16_t mask1 = ((1<<(depth + shift_right))-1);
        uint16_t mask2 = ((1<<(-shift_right))-1) << (16+shift_right);

        /* write the upper bits */
        old_value &= ~mask1;
        old_value |= (new_value >> (-shift_right)) & mask1;
        dst[dst_pos] = old_value;

        /* write the lower bits */
        old_value = dst[dst_pos + 1];
        old_value &= ~mask2;
        old_value |= (new_value << (16+shift_right)) & mask2;
        dst[dst_pos + 1] = old_value;
    }
}

uint16_t bitextract(uint16_t *src, int position, int depth)
{
    uint16_t value = 0;
    int src_pos = position * depth / 16;
    int bits_to_left = ((depth * position) - (16 * src_pos)) % 16;
    int shift_right = 16 - depth - bits_to_left;

    value = src[src_pos];

    if(shift_right >= 0)
    {
        value >>= shift_right;
    }
    else
    {
        value <<= -shift_right;
        value |= src[src_pos + 1] >> (16 + shift_right);
    }
    value &= (1<<depth) - 1;

    return value;
}

int load_frame(char *filename, uint8_t **frame_buffer, uint32_t *frame_buffer_size)
{
    FILE *in_file = NULL;
    int ret = 0;

    /* open files */
    in_file = fopen(filename, "rb");
    if(!in_file)
    {
        print_msg(MSG_ERROR, "Failed to open file '%s'\n", filename);
        return 1;
    }

    do
    {
        mlv_hdr_t buf;
        uint64_t position = 0;

        position = file_get_pos(in_file);

        if(fread(&buf, sizeof(mlv_hdr_t), 1, in_file) != 1)
        {
            print_msg(MSG_ERROR, "Failed to read from file '%s'\n", filename);
            ret = 2;
            goto load_frame_finish;
        }

        print_msg(MSG_INFO, "Block: %c%c%c%c\n", buf.blockType[0], buf.blockType[1], buf.blockType[2], buf.blockType[3]);
        print_msg(MSG_INFO, "  Offset: 0x%08" PRIx64 "\n", position);
        print_msg(MSG_INFO, "    Size: %d\n", buf.blockSize);
        
        /* jump back to the beginning of the block just read */
        file_set_pos(in_file, position, SEEK_SET);

        position = file_get_pos(in_file);

        if(!memcmp(buf.blockType, "MLVI", 4))
        {
            mlv_file_hdr_t file_hdr;
            uint32_t hdr_size = MIN(sizeof(mlv_file_hdr_t), buf.blockSize);

            /* read the whole header block, but limit size to either our local type size or the written block size */
            if(fread(&file_hdr, hdr_size, 1, in_file) != 1)
            {
                print_msg(MSG_ERROR, "File ends in the middle of a block\n");
                ret = 3;
                goto load_frame_finish;
            }
            file_set_pos(in_file, position + file_hdr.blockSize, SEEK_SET);

            if(file_hdr.videoClass & MLV_VIDEO_CLASS_FLAG_LZMA)
            {
                print_msg(MSG_ERROR, "Compressed formats not supported for frame extraction\n");
                ret = 5;
                goto load_frame_finish;
            }
        }
        else if(!memcmp(buf.blockType, "VIDF", 4))
        {
            mlv_vidf_hdr_t block_hdr;
            uint32_t hdr_size = MIN(sizeof(mlv_vidf_hdr_t), buf.blockSize);

            if(fread(&block_hdr, hdr_size, 1, in_file) != 1)
            {
                print_msg(MSG_ERROR, "File '%s' ends in the middle of a block\n", filename);
                ret = 3;
                goto load_frame_finish;
            }

            int frame_size = block_hdr.blockSize - sizeof(mlv_vidf_hdr_t) - block_hdr.frameSpace;
        
            /* loading the first frame. report frame size and allocate memory for that frame */
            *frame_buffer_size = frame_size;
            *frame_buffer = malloc(frame_size);

            file_set_pos(in_file, block_hdr.frameSpace, SEEK_CUR);
            if(fread(*frame_buffer, frame_size, 1, in_file) != 1)
            {
                print_msg(MSG_ERROR, "File '%s' ends in the middle of a block\n", filename);
                ret = 4;
                goto load_frame_finish;
            }

            ret = 0;
            goto load_frame_finish;
        }
        else
        {
            file_set_pos(in_file, position + buf.blockSize, SEEK_SET);
        }
    }
    while(!feof(in_file));

load_frame_finish:

    fclose(in_file);

    return ret;
}

mlv_xref_hdr_t *load_index(char *base_filename)
{
    mlv_xref_hdr_t *block_hdr = NULL;
    int max_name_len = strlen(base_filename) + 16;
    char *filename = malloc(max_name_len);
    FILE *in_file = NULL;

    strncpy(filename, base_filename, max_name_len);
    strcpy(&filename[strlen(filename) - 3], "IDX");

    in_file = fopen(filename, "rb");

    if(!in_file)
    {
        free(filename);
        return NULL;
    }

    print_msg(MSG_INFO, "File %s opened (XREF)\n", filename);

    do
    {
        mlv_hdr_t buf;
        uint64_t position = 0;

        position = file_get_pos(in_file);

        if(fread(&buf, sizeof(mlv_hdr_t), 1, in_file) != 1)
        {
            break;
        }

        /* jump back to the beginning of the block just read */
        file_set_pos(in_file, position, SEEK_SET);

        position = file_get_pos(in_file);

        /* we should check the MLVI header for matching UID value to make sure its the right index... */
        if(!memcmp(buf.blockType, "XREF", 4))
        {
            block_hdr = malloc(buf.blockSize);

            if(fread(block_hdr, buf.blockSize, 1, in_file) != 1)
            {
                print_msg(MSG_ERROR, "File '%s' ends in the middle of a block\n", filename);
                free(block_hdr);
                block_hdr = NULL;
            }
        }
        else
        {
            file_set_pos(in_file, position + buf.blockSize, SEEK_SET);
        }

        /* we are at the same position as before, so abort */
        if(position == file_get_pos(in_file))
        {
            print_msg(MSG_ERROR, "File '%s' has invalid blocks\n", filename);
            break;
        }
    }
    while(!feof(in_file));

    fclose(in_file);

    free(filename);
    return block_hdr;
}

void save_index(char *base_filename, mlv_file_hdr_t *ref_file_hdr, int fileCount, frame_xref_t *index, int entries)
{
    int max_name_len = strlen(base_filename) + 16;
    char *filename = malloc(max_name_len);
    FILE *out_file = NULL;

    strncpy(filename, base_filename, max_name_len);

    strcpy(&filename[strlen(filename) - 3], "IDX");

    out_file = fopen(filename, "wb+");

    if(!out_file)
    {
        free(filename);
        print_msg(MSG_ERROR, "Failed writing into .IDX file\n");
        return;
    }

    print_msg(MSG_INFO, "File %s opened for writing\n", filename);


    /* first write MLVI header */
    mlv_file_hdr_t file_hdr = *ref_file_hdr;

    /* update fields */
    file_hdr.blockSize = sizeof(mlv_file_hdr_t);
    file_hdr.videoFrameCount = 0;
    file_hdr.audioFrameCount = 0;
    file_hdr.fileNum = fileCount + 1;

    if(fwrite(&file_hdr, sizeof(mlv_file_hdr_t), 1, out_file) != 1)
    {
        free(filename);
        print_msg(MSG_ERROR, "Failed writing into .IDX file\n");
        fclose(out_file);
        return;
    }

    /* now write XREF block */
    mlv_xref_hdr_t hdr;

    memset(&hdr, 0x00, sizeof(mlv_xref_hdr_t));
    memcpy(hdr.blockType, "XREF", 4);
    hdr.blockSize = sizeof(mlv_xref_hdr_t) + entries * sizeof(mlv_xref_t);
    hdr.entryCount = entries;

    if(fwrite(&hdr, sizeof(mlv_xref_hdr_t), 1, out_file) != 1)
    {
        free(filename);
        print_msg(MSG_ERROR, "Failed writing into .IDX file\n");
        fclose(out_file);
        return;
    }

    /* and then the single entries */
    for(int entry = 0; entry < entries; entry++)
    {
        mlv_xref_t field;

        memset(&field, 0x00, sizeof(mlv_xref_t));

        field.frameOffset = index[entry].frameOffset;
        field.fileNumber = index[entry].fileNumber;
        field.frameType = index[entry].frameType;

        if(fwrite(&field, sizeof(mlv_xref_t), 1, out_file) != 1)
        {
            free(filename);
            print_msg(MSG_ERROR, "Failed writing into .IDX file\n");
            fclose(out_file);
            return;
        }
    }

    free(filename);
    fclose(out_file);
}


FILE **load_all_chunks(char *base_filename, int *entries)
{
    int seq_number = 0;
    int max_name_len = strlen(base_filename) + 16;
    char *filename = malloc(max_name_len);

    strncpy(filename, base_filename, max_name_len - 1);
    FILE **files = malloc(sizeof(FILE*));

    files[0] = fopen(filename, "rb");
    if(!files[0])
    {
        free(filename);
        free(files);
        return NULL;
    }

    print_msg(MSG_INFO, "File %s opened\n", filename);

    /* get extension and check if it is a .MLV */
    char *dot = strrchr(filename, '.');
    if(dot)
    {
        dot++;
        if(strcasecmp(dot, "mlv"))
        {
            seq_number = 100;
        }
    }
    
    (*entries)++;
    while(seq_number < 99)
    {
        FILE **realloc_files = realloc(files, (*entries + 1) * sizeof(FILE*));

        if(!realloc_files)
        {
            free(filename);
            free(files);
            return NULL;
        }

        files = realloc_files;

        /* check for the next file M00, M01 etc */
        char seq_name[8];

        sprintf(seq_name, "%02d", seq_number);
        seq_number++;

        strcpy(&filename[strlen(filename) - 2], seq_name);

        /* try to open */
        files[*entries] = fopen(filename, "rb");
        if(files[*entries])
        {
            print_msg(MSG_INFO, "File %s opened\n", filename);
            (*entries)++;
        }
        else
        {
            print_msg(MSG_INFO, "File %s not existing.\n", filename);
            break;
        }
    }

    free(filename);
    return files;
}


#define EV_RESOLUTION 32768

#define CHROMA_SMOOTH_2X2
#include "../dual_iso/chroma_smooth.c"
#undef CHROMA_SMOOTH_2X2

#define CHROMA_SMOOTH_3X3
#include "../dual_iso/chroma_smooth.c"
#undef CHROMA_SMOOTH_3X3

#define CHROMA_SMOOTH_5X5
#include "../dual_iso/chroma_smooth.c"
#undef CHROMA_SMOOTH_5X5


void chroma_smooth(int method, struct raw_info *info)
{
    int black = info->black_level;
    static int raw2ev[16384];
    static int _ev2raw[24*EV_RESOLUTION];
    int* ev2raw = _ev2raw + 10*EV_RESOLUTION;
    
    if(!method)
    {
        return;
    }

    for(int i = 0; i < 16384; i++)
    {
        raw2ev[i] = log2(MAX(1, i - black)) * EV_RESOLUTION;
    }

    for(int i = -10*EV_RESOLUTION; i < 14*EV_RESOLUTION; i++)
    {
        ev2raw[i] = black + pow(2, (float)i / EV_RESOLUTION);
    }

    int w = info->width;
    int h = info->height;

    unsigned short * aux = malloc(w * h * sizeof(short));
    unsigned short * aux2 = malloc(w * h * sizeof(short));

    int x,y;
    for (y = 0; y < h; y++)
    {
        for (x = 0; x < w; x++)
        {
            aux[x + y*w] = aux2[x + y*w] = raw_get_pixel(x, y);
        }
    }

    switch(method)
    {
        case 2:
            chroma_smooth_2x2(aux, aux2, raw2ev, ev2raw);
            break;
        case 3:
            chroma_smooth_3x3(aux, aux2, raw2ev, ev2raw);
            break;
        case 5:
            chroma_smooth_5x5(aux, aux2, raw2ev, ev2raw);
            break;
    }

    for (y = 0; y < h; y++)
    {
        for (x = 0; x < w; x++)
        {
            raw_set_pixel(x, y, aux2[x + y*w]);
        }
    }

    free(aux);
    free(aux2);
}

void show_usage(char *executable)
{
    print_msg(MSG_INFO, "Usage: %s [-o output_file] [-rscd] [-l compression_level(0-9)] <inputfile>\n", executable);
    print_msg(MSG_INFO, "Parameters:\n");
    print_msg(MSG_INFO, " -o output_file      set the filename to write into\n");
    print_msg(MSG_INFO, " -v                  verbose output\n");
    print_msg(MSG_INFO, " --batch             output message suitable for batch processing\n");
    
    print_msg(MSG_INFO, "\n");
    print_msg(MSG_INFO, "-- DNG output --\n");
    print_msg(MSG_INFO, " --dng               output frames into separate .dng files. set prefix with -o\n");
    print_msg(MSG_INFO, " --no-cs             no chroma smoothing\n");
    print_msg(MSG_INFO, " --cs2x2             2x2 chroma smoothing\n");
    print_msg(MSG_INFO, " --cs3x3             3x3 chroma smoothing\n");
    print_msg(MSG_INFO, " --cs5x5             5x5 chroma smoothing\n");
    print_msg(MSG_INFO, " --fixcp             fix cold pixels\n");

    print_msg(MSG_INFO, "\n");
    print_msg(MSG_INFO, "-- RAW output --\n");
    print_msg(MSG_INFO, " -r                  output into a legacy raw file for e.g. raw2dng\n");

    print_msg(MSG_INFO, "\n");
    print_msg(MSG_INFO, "-- MLV output --\n");
    print_msg(MSG_INFO, " -b bits             convert image data to given bit depth per channel (1-16)\n");
    print_msg(MSG_INFO, " -z bits             zero the lowest bits, so we have only specified number of bits containing data (1-16) (improves compression rate)\n");
    print_msg(MSG_INFO, " -f frames           frames to save. e.g. '12' saves the first 12 frames, '12-40' saves frames 12 to 40.\n");
    print_msg(MSG_INFO, " -A fpsx1000         Alter the video file's FPS metadata\n");
    print_msg(MSG_INFO, " -x                  build xref file (indexing)\n");
    print_msg(MSG_INFO, " -m                  write only metadata, no audio or video frames\n");
    print_msg(MSG_INFO, " -n                  write no metadata, only audio and video frames\n");

    print_msg(MSG_INFO, "\n");
    print_msg(MSG_INFO, "-- Image manipulation --\n");
    print_msg(MSG_INFO, " -a                  average all frames in <inputfile> and output a single-frame MLV from it\n");
    print_msg(MSG_INFO, " --avg-vertical      [DARKFRAME ONLY] average the resulting frame in vertical direction, so we will extract vertical banding\n");
    print_msg(MSG_INFO, " --avg-horizontal    [DARKFRAME ONLY] average the resulting frame in horizontal direction, so we will extract horizontal banding\n");
    print_msg(MSG_INFO, " -s mlv_file         subtract the reference frame in given file from every single frame during processing\n");

    print_msg(MSG_INFO, "\n");
    print_msg(MSG_INFO, "-- Processing --\n");
    print_msg(MSG_INFO, " -e                  delta-encode frames to improve compression, but lose random access capabilities\n");
    print_msg(MSG_INFO, " -X type             extract only block type\n");
    print_msg(MSG_INFO, " -I mlv_file         inject data from given MLV file right after MLVI header\n");

    /* yet unclear which format to choose, so keep that as reminder */
    //print_msg(MSG_INFO, " -u lut_file         look-up table with 4 * xRes * yRes 16-bit words that is applied before bit depth conversion\n");
#ifdef MLV_USE_LZMA
    print_msg(MSG_INFO, " -c                  (re-)compress video and audio frames using LZMA (set bpp to 16 to improve compression rate)\n");
    print_msg(MSG_INFO, " -d                  decompress compressed video and audio frames using LZMA\n");
    print_msg(MSG_INFO, " -l level            set compression level from 0=fastest to 9=best compression\n");
#else
    print_msg(MSG_INFO, " -c, -d, -l          NOT AVAILABLE: compression support was not compiled into this release\n");
#endif
    print_msg(MSG_INFO, "\n");

    print_msg(MSG_INFO, "-- bugfixes --\n");
    print_msg(MSG_INFO, " --black-fix=value   set black level to <value> (fix green/magenta cast)\n");
    print_msg(MSG_INFO, " --fix-bug=id        fix some special bugs. *only* to be used if given instruction by developers.\n");
    print_msg(MSG_INFO, "\n");
}

int main (int argc, char *argv[])
{
    char *input_filename = NULL;
    char *output_filename = NULL;
    char *subtract_filename = NULL;
    char *lut_filename = NULL;
    char *extract_block = NULL;
    char *inject_filename = NULL;
    int blocks_processed = 0;

    uint32_t frame_start = 0;
    uint32_t frame_end = 0;
    uint32_t audf_frames_processed = 0;
    uint32_t vidf_frames_processed = 0;
    uint32_t vidf_max_number = 0;

    int delta_encode_mode = 0;
    int xref_mode = 0;
    int average_mode = 0;
    int average_vert = 0;
    int average_hor = 0;
    int subtract_mode = 0;
    int no_metadata_mode = 0;
    int only_metadata_mode = 0;
    int average_samples = 0;

    int mlv_output = 0;
    int raw_output = 0;
    int bit_depth = 0;
    int bit_zap = 0;
    int compress_output = 0;
    int decompress_output = 0;
    int verbose = 0;
    int lzma_level = 5;
    int alter_fps = 0;
    char opt = ' ';

    int video_xRes = 0;
    int video_yRes = 0;

#ifdef MLV_USE_LZMA
    /* this may need some tuning */
    int lzma_dict = 1<<27;
    int lzma_lc = 0;
    int lzma_lp = 1;
    int lzma_pb = 1;
    int lzma_fb = 16;
    int lzma_threads = 8;
#endif

    lua_State *lua_state = NULL;

    /* long options */
    int chroma_smooth_method = 0;
    int black_fix = 0;
    enum bug_id fix_bug = BUG_ID_NONE;
    int fix_bug_1_offset = 0;
    int fix_bug_2_offset = 0;
    int dng_output = 0;
    int dump_xrefs = 0;
    int fix_cold_pixels = 0;

    struct option long_options[] = {
        {"lua",    required_argument, NULL,  'L' },
        {"black-fix",  optional_argument, NULL,  'B' },
        {"fix-bug",  required_argument, NULL,  'F' },
        {"batch",  no_argument, &batch_mode,  1 },
        {"dump-xrefs",   no_argument, &dump_xrefs,  1 },
        {"dng",    no_argument, &dng_output,  1 },
        {"no-cs",  no_argument, &chroma_smooth_method,  0 },
        {"cs2x2",  no_argument, &chroma_smooth_method,  2 },
        {"cs3x3",  no_argument, &chroma_smooth_method,  3 },
        {"cs5x5",  no_argument, &chroma_smooth_method,  5 },
        {"fixcp",  no_argument, &fix_cold_pixels,  1 },
        {"avg-vertical",  no_argument, &average_vert,  1 },
        {"avg-horizontal",  no_argument, &average_hor,  1 },
        {0,         0,                 0,  0 }
    };

    /* disable stdout buffering */
    setvbuf(stderr, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    if(sizeof(mlv_file_hdr_t) != 52)
    {
        print_msg(MSG_INFO, "Error: Your compiler setup is weird. sizeof(mlv_file_hdr_t) is "FMT_SIZE" on your machine. Expected: 52\n", sizeof(mlv_file_hdr_t));
        return ERR_STRUCT_ALIGN;
    }

    int index = 0;
    while ((opt = getopt_long(argc, argv, "A:F:B:L:txz:emnas:X:I:uvrcdo:l:b:f:", long_options, &index)) != -1)
    {
        switch (opt)
        {
            case 'F':
                if(!optarg)
                {
                    print_msg(MSG_ERROR, "Error: Missing bug ID\n");
                    print_msg(MSG_ERROR, "    #1 - fix invalid block sizes\n");
                    return ERR_PARAM;
                }
                else
                {
                    fix_bug = MIN(16384, MAX(1, atoi(optarg)));
                    print_msg(MSG_INFO, "FIX BUG #%d [active]\n", fix_bug);
                    
                    if(fix_bug == BUG_ID_FRAMEDATA_MISALIGN)
                    {
                        char *parm = strchr(optarg, ',');
                        if(parm && *parm)
                        {
                            parm++;
                            fix_bug_2_offset = MIN(16384, MAX(-16384, atoi(parm)));
                            print_msg(MSG_INFO, "FIX BUG #%d [active] parameter: %d\n", fix_bug, fix_bug_2_offset);
                        }
                    }
                }
                break;
              
            case 'B':
                if(!optarg)
                {
                    black_fix = 2048;
                }
                else
                {
                    black_fix = MIN(16384, MAX(1, atoi(optarg)));
                }
                break;
                
            case 'A':
                if(!optarg)
                {
                    print_msg(MSG_ERROR, "Error: Missing parameter FPSx1000\n");
                    return ERR_PARAM;
                }
                else
                {
                    alter_fps = MAX(1, atoi(optarg));
                }
                break;
                
            case 'L':
#ifdef USE_LUA
                if(!optarg)
                {
                    print_msg(MSG_ERROR, "Error: Missing LUA script filename\n");
                    return ERR_PARAM;
                }
                lua_state = luaL_newstate();
                if(!lua_state)
                {
                    print_msg(MSG_ERROR, "LUA: Failed to init LUA library\n");
                    return ERR_PARAM;
                }

                luaL_openlibs(lua_state);

                if(luaL_loadfile(lua_state, optarg) != 0 || lua_pcall(lua_state, 0, 0, 0) != 0)
                {
                    print_msg(MSG_ERROR, "LUA: Failed to load script\n");
                }

                if(lua_call_va(lua_state, "init", "", 0) != 0)
                {
                    print_msg(MSG_ERROR, "LUA: Failed to call 'init' in script\n");
                }
                break;
#else
                print_msg(MSG_ERROR, "LUA support not compiled into this binary\n");
                return ERR_PARAM;
#endif

            case 'x':
                xref_mode = 1;
                break;

            case 'm':
                only_metadata_mode = 1;
                break;

            case 'n':
                no_metadata_mode = 1;
                break;

            case 'e':
                delta_encode_mode = 1;
                break;

            case 'a':
                average_mode = 1;
                decompress_output = 1;
                //no_metadata_mode = 1;
                break;

            case 's':
                if(!optarg)
                {
                    print_msg(MSG_ERROR, "Error: Missing subtract frame filename\n");
                    return ERR_PARAM;
                }
                subtract_filename = strdup(optarg);
                subtract_mode = 1;
                decompress_output = 1;
                break;

            case 'X':
                if(!optarg || strlen(optarg) != 4)
                {
                    print_msg(MSG_ERROR, "Error: Missing block type. e.g. MLVI or RAWI\n");
                    return ERR_PARAM;
                }
                extract_block = strdup(optarg);
                break;

            case 'I':
                if(!optarg)
                {
                    print_msg(MSG_ERROR, "Error: Missing filename of data to inject\n");
                    return ERR_PARAM;
                }
                inject_filename = strdup(optarg);
                break;

            case 'u':
                if(!optarg)
                {
                    print_msg(MSG_ERROR, "Error: Missing LUT filename\n");
                    return ERR_PARAM;
                }
                lut_filename = strdup(optarg);
                break;

            case 'v':
                verbose = 1;
                break;

            case 'r':
                raw_output = 1;
                bit_depth = 14;
                break;

            case 'c':
#ifdef MLV_USE_LZMA
                compress_output = 1;
#else
                print_msg(MSG_ERROR, "Error: Compression support was not compiled into this release\n");
                return ERR_PARAM;
#endif
                break;

            case 'd':
#ifdef MLV_USE_LZMA
                decompress_output = 1;
#else
                print_msg(MSG_ERROR, "Error: Compression support was not compiled into this release\n");
                return ERR_PARAM;
#endif
                break;

            case 'o':
                if(!optarg)
                {
                    print_msg(MSG_ERROR, "Error: Missing output filename\n");
                    return ERR_PARAM;
                }
                output_filename = strdup(optarg);
                break;

            case 'l':
                lzma_level = MIN(9, MAX(0, atoi(optarg)));
                break;

            case 'f':
                {
                    char *dash = strchr(optarg, '-');

                    /* try to parse "1-10" */
                    if(dash)
                    {
                        *dash = '\000';
                        frame_start = atoi(optarg);
                        frame_end = atoi(&dash[1]);

                        /* to makse sure it is a valid range */
                        if(frame_start > frame_end)
                        {
                            frame_end = frame_start;
                        }
                    }
                    else
                    {
                        /* only the frame end is specified */
                        frame_end = MAX(0, atoi(optarg));
                    }
                }
                break;

            case 'b':
                if(!raw_output)
                {
                    bit_depth = MIN(16, MAX(1, atoi(optarg)));
                }
                break;

            case 'z':
                if(!raw_output)
                {
                    bit_zap = MIN(16, MAX(1, atoi(optarg)));
                }
                break;

            case 0:
                break;

            default:
                show_usage(argv[0]);
                return ERR_PARAM;
        }
    }

    if(optind >= argc)
    {
        print_msg(MSG_ERROR, "Error: Missing input filename\n");
        show_usage(argv[0]);
        return ERR_PARAM;
    }



    print_msg(MSG_INFO, "\n");
    print_msg(MSG_INFO, " MLV Dumper v1.0\n");
    print_msg(MSG_INFO, "-----------------\n");
    print_msg(MSG_INFO, "\n");

    /* get first file */
    input_filename = argv[optind];

    print_msg(MSG_INFO, "Mode of operation:\n");
    print_msg(MSG_INFO, "   - Input MLV file: '%s'\n", input_filename);

    if(verbose)
    {
        print_msg(MSG_INFO, "   - Verbose messages\n");
    }
    
    if(black_fix)
    {
        print_msg(MSG_INFO, "   - Setting black level to %d\n", black_fix);
    }

    if(alter_fps)
    {
        print_msg(MSG_INFO, "   - altering FPS metadata for %d/1000 fps\n", alter_fps);
    }

    /* special case - splitting into frames doesnt require a specific output file */
    if(dng_output && !output_filename)
    {
        int len = strlen(input_filename) + 16;
        output_filename = malloc(len);

        strcpy(output_filename, input_filename);

        char *ext_dot = strrchr(output_filename, '.');
        if(ext_dot)
        {
            *ext_dot = '\000';
        }

        strcat(output_filename, "_frame_");
    }

    /* display and set/unset variables according to parameters to have a consistent state */
    if(output_filename)
    {
        if(dng_output)
        {
            print_msg(MSG_INFO, "   - Convert to DNG frames\n");

            delta_encode_mode = 0;
            compress_output = 0;
            mlv_output = 0;
            raw_output = 0;
        }
        else if(raw_output)
        {
            print_msg(MSG_INFO, "   - Convert to legacy RAW\n");

            delta_encode_mode = 0;
            compress_output = 0;
            mlv_output = 0;
            dng_output = 0;

            if(average_mode)
            {
                print_msg(MSG_INFO, "   - disabled average mode, not possible\n");
                average_mode = 0;
            }
        }
        else
        {
            mlv_output = 1;
            dng_output = 0;

            print_msg(MSG_INFO, "   - Rewrite MLV\n");
            if(bit_zap)
            {
                print_msg(MSG_INFO, "   - Only store %d bits of information per pixel\n", bit_zap);
            }
            if(bit_depth)
            {
                print_msg(MSG_INFO, "   - Convert to %d bpp\n", bit_depth);
            }
            if(delta_encode_mode)
            {
                print_msg(MSG_INFO, "   - Only store changes to previous frame\n");
            }
            if(compress_output)
            {
                print_msg(MSG_INFO, "   - Compress frame data\n");
            }
            if(average_mode)
            {
                print_msg(MSG_INFO, "   - Output only one frame with averaged pixel values\n");
                if(average_vert)
                {
                    print_msg(MSG_INFO, "   - Also average the images in vertical direction to extract vertical banding\n");
                }
                if(average_hor)
                {
                    print_msg(MSG_INFO, "   - Also average the images in horizontal direction to extract horizontal banding\n");
                }
            }
            if(subtract_mode)
            {
                print_msg(MSG_INFO, "   - Subtract reference frame '%s' from single images\n", subtract_filename);
            }
            if(extract_block)
            {
                print_msg(MSG_INFO, "   - But only write '%s' blocks\n", extract_block);
            }
            if(inject_filename)
            {
                print_msg(MSG_INFO, "   - Inject data from '%s'\n", inject_filename);
            }
        }

        print_msg(MSG_INFO, "   - Output into '%s'\n", output_filename);
    }
    else
    {
        /* those dont make sense then */
        raw_output = 0;
        compress_output = 0;

        print_msg(MSG_INFO, "   - Verify file structure\n");
        if(verbose)
        {
            print_msg(MSG_INFO, "   - Dump all block information\n");
        }
    }

    if(xref_mode)
    {
        print_msg(MSG_INFO, "   - Output .idx file for faster processing\n");
    }

    /* start processing */
    lv_rec_file_footer_t lv_rec_footer;
    mlv_file_hdr_t main_header;
    mlv_lens_hdr_t lens_info;
    mlv_expo_hdr_t expo_info;
    mlv_idnt_hdr_t idnt_info;
    mlv_wbal_hdr_t wbal_info;
    mlv_wavi_hdr_t wavi_info;
    mlv_rtci_hdr_t rtci_info;
    mlv_vidf_hdr_t last_vidf;

    /* initialize stuff */
    memset(&lv_rec_footer, 0x00, sizeof(lv_rec_file_footer_t));
    memset(&lens_info, 0x00, sizeof(mlv_lens_hdr_t));
    memset(&expo_info, 0x00, sizeof(mlv_expo_hdr_t));
    memset(&idnt_info, 0x00, sizeof(mlv_idnt_hdr_t));
    memset(&wbal_info, 0x00, sizeof(mlv_wbal_hdr_t));
    memset(&wavi_info, 0x00, sizeof(mlv_wavi_hdr_t));
    memset(&rtci_info, 0x00, sizeof(mlv_rtci_hdr_t));
    memset(&main_header, 0x00, sizeof(mlv_file_hdr_t));

    char info_string[256] = "(MLV Video without INFO blocks)";

    /* this table contains the XREF chunk read from idx file, if existing */
    mlv_xref_hdr_t *block_xref = NULL;
    mlv_xref_t *xrefs = NULL;
    uint32_t block_xref_pos = 0;

    uint32_t frame_buffer_size = 1*1024*1024;
    uint32_t subtract_frame_buffer_size = 0;

    uint32_t *frame_arith_buffer = NULL;
    uint8_t *frame_sub_buffer = NULL;
    uint8_t *frame_buffer = NULL;
    uint8_t *prev_frame_buffer = NULL;

    FILE *out_file = NULL;
    FILE *out_file_wav = NULL;
    FILE **in_files = NULL;
    FILE *in_file = NULL;

    int in_file_count = 0;
    int in_file_num = 0;

    uint32_t wav_file_size = 0; /* WAV format supports only 32-bit size */
    uint32_t wav_header_size = 0;

    /* this is for our generated XREF table */
    frame_xref_t *frame_xref_table = NULL;
    int frame_xref_allocated = 0;
    int frame_xref_entries = 0;

    int total_vidf_count = 0;
    int total_audf_count = 0;

    /* open files */
    in_files = load_all_chunks(input_filename, &in_file_count);
    if(!in_files || !in_file_count)
    {
        print_msg(MSG_ERROR, "Failed to open file '%s'\n", input_filename);
        return ERR_FILE;
    }
    else
    {
        in_file_num = 0;
        in_file = in_files[in_file_num];
    }

    if(!xref_mode)
    {
        block_xref = load_index(input_filename);

        if(block_xref)
        {
            print_msg(MSG_INFO, "XREF table contains %d entries\n", block_xref->entryCount);
            xrefs = (mlv_xref_t *)((uint32_t)block_xref + sizeof(mlv_xref_hdr_t));

            if(dump_xrefs)
            {
                xref_dump(block_xref);
            }
        }
        else
        {
            if(delta_encode_mode)
            {
                print_msg(MSG_ERROR, "Delta encoding is not possible without an index file. Please create one using -x option.\n");
                return ERR_INDEX_REQ;
            }
        }
    }

    /* this block will load an image from a MLV file, so use its reported frame size for future use */
    if(subtract_mode)
    {
        int ret = load_frame(subtract_filename, &frame_sub_buffer, &subtract_frame_buffer_size);

        if(ret)
        {
            print_msg(MSG_ERROR, "Failed to load subtract frame (%d)\n", ret);
            return ERR_FILE;
        }
        
        frame_buffer_size = subtract_frame_buffer_size;
    }

    if(average_mode)
    {
        frame_arith_buffer = malloc(frame_buffer_size * sizeof(uint32_t));
        if(!frame_arith_buffer)
        {
            print_msg(MSG_ERROR, "Failed to alloc mem\n");
            return ERR_MALLOC;
        }
        memset(frame_arith_buffer, 0x00, frame_buffer_size * sizeof(uint32_t));
    }

    /* always allocate, delta decoding also needs this buffer */
    {
        prev_frame_buffer = malloc(frame_buffer_size);
        if(!prev_frame_buffer)
        {
            print_msg(MSG_ERROR, "Failed to alloc mem\n");
            return ERR_MALLOC;
        }
        memset(prev_frame_buffer, 0x00, frame_buffer_size);
    }

    if(output_filename || lua_state)
    {
        frame_buffer = malloc(frame_buffer_size);
        if(!frame_buffer)
        {
            print_msg(MSG_ERROR, "Failed to alloc mem\n");
            return ERR_MALLOC;
        }
        memset(frame_buffer, 0x00, frame_buffer_size);

        if(!dng_output && output_filename)
        {
            out_file = fopen(output_filename, "wb+");
            if(!out_file)
            {
                print_msg(MSG_ERROR, "Failed to open file '%s'\n", output_filename);
                return ERR_FILE;
            }
        }
    }

    print_msg(MSG_INFO, "Processing...\n");
    uint64_t position_previous = 0;
    do
    {
        mlv_hdr_t buf;
        uint64_t position = 0;

read_headers:

        print_msg(MSG_PROGRESS, "B:%d/%d V:%d/%d A:%d/%d\n", blocks_processed, block_xref?block_xref->entryCount:0, vidf_frames_processed, total_vidf_count, audf_frames_processed, total_audf_count);

        if(block_xref)
        {
            /* get the file and position of the next block */
            in_file_num = xrefs[block_xref_pos].fileNumber;
            position = xrefs[block_xref_pos].frameOffset;

            /* select file and seek to the right position */
            in_file = in_files[in_file_num];
            file_set_pos(in_file, position, SEEK_SET);
        }

        position = file_get_pos(in_file);

        if(fread(&buf, sizeof(mlv_hdr_t), 1, in_file) != 1)
        {
            if(block_xref)
            {
                print_msg(MSG_INFO, "Reached EOF of chunk %d/%d after %i blocks total. This should never happen or your index file is wrong.\n", in_file_num, in_file_count, blocks_processed);
                break;
            }
            print_msg(MSG_INFO, "Reached end of chunk %d/%d after %i blocks\n", in_file_num + 1, in_file_count, blocks_processed);

            if(in_file_num < (in_file_count - 1))
            {
                in_file_num++;
                in_file = in_files[in_file_num];
            }
            else
            {
                break;
            }

            blocks_processed = 0;

            goto read_headers;
        }

        /* jump back to the beginning of the block just read */
        file_set_pos(in_file, position, SEEK_SET);

        position = file_get_pos(in_file);

        /* unexpected block header size? */
        if(buf.blockSize < sizeof(mlv_hdr_t) || buf.blockSize > 50 * 1024 * 1024)
        {
            if(!fix_bug)
            {
                print_msg(MSG_ERROR, "Invalid block size at position 0x%08" PRIx64 "\n", position);
                goto abort;
            }
        }

        /* file header */
        if(!memcmp(buf.blockType, "MLVI", 4))
        {
            mlv_file_hdr_t file_hdr;
            uint32_t hdr_size = MIN(sizeof(mlv_file_hdr_t), buf.blockSize);

            /* read the whole header block, but limit size to either our local type size or the written block size */
            if(fread(&file_hdr, hdr_size, 1, in_file) != 1)
            {
                print_msg(MSG_ERROR, "File ends in the middle of a block\n");
                goto abort;
            }
            file_set_pos(in_file, position + file_hdr.blockSize, SEEK_SET);

            lua_handle_hdr(lua_state, buf.blockType, &file_hdr, sizeof(file_hdr));

            if(verbose)
            {
                print_msg(MSG_INFO, "File Header (MLVI)\n");
                print_msg(MSG_INFO, "    Size        : 0x%08X\n", file_hdr.blockSize);
                print_msg(MSG_INFO, "    Ver         : %s\n", file_hdr.versionString);
                print_msg(MSG_INFO, "    GUID        : %08" PRIu64 "\n", file_hdr.fileGuid);
                print_msg(MSG_INFO, "    FPS         : %f\n", (double)file_hdr.sourceFpsNom / (double)file_hdr.sourceFpsDenom);
                print_msg(MSG_INFO, "    File        : %d / %d\n", file_hdr.fileNum, file_hdr.fileCount);
                print_msg(MSG_INFO, "    Frames Video: %d\n", file_hdr.videoFrameCount);
                print_msg(MSG_INFO, "    Frames Audio: %d\n", file_hdr.audioFrameCount);
            }
            
            if(alter_fps)
            {
                file_hdr.sourceFpsNom = alter_fps;
                file_hdr.sourceFpsDenom = 1000;
            }

            /* in xref mode, use every block and get its timestamp etc */
            if(xref_mode)
            {
                xref_resize(&frame_xref_table, frame_xref_entries + 1, &frame_xref_allocated);

                /* add xref data */
                frame_xref_table[frame_xref_entries].frameTime = 0;
                frame_xref_table[frame_xref_entries].frameOffset = position;
                frame_xref_table[frame_xref_entries].fileNumber = in_file_num;
                frame_xref_table[frame_xref_entries].frameType = MLV_FRAME_UNSPECIFIED;

                frame_xref_entries++;
            }

            /* is this the first file? */
            if(main_header.fileGuid == 0)
            {
                /* correct header size if needed */
                file_hdr.blockSize = sizeof(mlv_file_hdr_t);

                memcpy(&main_header, &file_hdr, sizeof(mlv_file_hdr_t));

                total_vidf_count = main_header.videoFrameCount;
                total_audf_count = main_header.audioFrameCount;

                if(mlv_output)
                {
                    if(average_mode)
                    {
                        file_hdr.videoFrameCount = 1;
                    }

                    /* set the output compression flag */
                    if(compress_output)
                    {
                        file_hdr.videoClass |= MLV_VIDEO_CLASS_FLAG_LZMA;
                    }
                    else
                    {
                        file_hdr.videoClass &= ~MLV_VIDEO_CLASS_FLAG_LZMA;
                    }

                    if(delta_encode_mode)
                    {
                        file_hdr.videoClass |= MLV_VIDEO_CLASS_FLAG_DELTA;
                    }
                    else
                    {
                        file_hdr.videoClass &= ~MLV_VIDEO_CLASS_FLAG_DELTA;
                    }

                    if(!extract_block || !strncasecmp(extract_block, (char*)file_hdr.fileMagic, 4))
                    {
                        if(fwrite(&file_hdr, file_hdr.blockSize, 1, out_file) != 1)
                        {
                            print_msg(MSG_ERROR, "Failed writing into .MLV file\n");
                            goto abort;
                        }
                    }
                    
                    if(inject_filename)
                    {
                        FILE *inject_file = fopen(inject_filename, "rb");
                        
                        if(!inject_file)
                        {
                            print_msg(MSG_ERROR, "Failed opening .MLV file to inject\n");
                            goto abort;
                        }
                        
                        file_set_pos(inject_file, 0, SEEK_END);
                        uint32_t size = file_get_pos(inject_file);
                        file_set_pos(inject_file, 0, SEEK_SET);
                        
                        uint8_t *buf = malloc(size);
                        if(!buf)
                        {
                            print_msg(MSG_ERROR, "Failed to allocate buffer for data to inject\n");
                            goto abort;
                        }
                        
                        if(fread(buf, size, 1, inject_file) != 1)
                        {
                            print_msg(MSG_ERROR, "Failed to read data form inject file\n");
                            goto abort;
                        }

                        if(fwrite(buf, size, 1, out_file) != 1)
                        {
                            print_msg(MSG_ERROR, "Failed writing into .MLV file\n");
                            goto abort;
                        }
                        
                        free(buf);
                        
                        fclose(inject_file);
                    }
                }
            }
            else
            {
                /* no, its another chunk */
                if(main_header.fileGuid != file_hdr.fileGuid)
                {
                    print_msg(MSG_ERROR, "Error: GUID within the file chunks mismatch!\n");
                    //break;
                }

                total_vidf_count += file_hdr.videoFrameCount;
                total_audf_count += file_hdr.audioFrameCount;
            }

            if(raw_output)
            {
                lv_rec_footer.frameCount += file_hdr.videoFrameCount;
                lv_rec_footer.sourceFpsx1000 = (double)file_hdr.sourceFpsNom / (double)file_hdr.sourceFpsDenom * 1000;
                lv_rec_footer.frameSkip = 0;
            }
        }
        else
        {
            /* in xref mode, use every block and get its timestamp etc */
            if(xref_mode && memcmp(buf.blockType, "NULL", 4) && memcmp(buf.blockType, "BKUP", 4))
            {
                xref_resize(&frame_xref_table, frame_xref_entries + 1, &frame_xref_allocated);

                /* add xref data */
                frame_xref_table[frame_xref_entries].frameTime = buf.timestamp;
                frame_xref_table[frame_xref_entries].frameOffset = position;
                frame_xref_table[frame_xref_entries].fileNumber = in_file_num;
                frame_xref_table[frame_xref_entries].frameType =
                    !memcmp(buf.blockType, "VIDF", 4) ? MLV_FRAME_VIDF :
                    !memcmp(buf.blockType, "AUDF", 4) ? MLV_FRAME_AUDF :
                    MLV_FRAME_UNSPECIFIED;

                frame_xref_entries++;
            }

            if(main_header.blockSize == 0)
            {
                print_msg(MSG_ERROR, "Missing file header\n");
                goto abort;
            }

            if(verbose)
            {
                print_msg(MSG_INFO, "Block: %c%c%c%c\n", buf.blockType[0], buf.blockType[1], buf.blockType[2], buf.blockType[3]);
                print_msg(MSG_INFO, "  Offset: 0x%08" PRIx64 "\n", position);
                print_msg(MSG_INFO, "    Size: %d\n", buf.blockSize);

                /* NULL blocks don't have timestamps */
                if(memcmp(buf.blockType, "NULL", 4)|| memcmp(buf.blockType, "BKUP", 4))
                {
                    print_msg(MSG_INFO, "    Time: %f ms\n", (double)buf.timestamp / 1000.0f);
                }
            }

            if(!memcmp(buf.blockType, "AUDF", 4))
            {
                mlv_audf_hdr_t block_hdr;
                uint32_t hdr_size = MIN(sizeof(mlv_audf_hdr_t), buf.blockSize);

                if(fread(&block_hdr, hdr_size, 1, in_file) != 1)
                {
                    print_msg(MSG_ERROR, "AUDF: File ends in the middle of a block\n");
                    goto abort;
                }

                lua_handle_hdr(lua_state, buf.blockType, &block_hdr, sizeof(block_hdr));
                if(verbose)
                {
                    print_msg(MSG_INFO, "   Frame: #%04d\n", block_hdr.frameNumber);
                    print_msg(MSG_INFO, "   Space: %d\n", block_hdr.frameSpace);
                }

                uint32_t skip_block = 0;
                
                if(block_hdr.frameSpace > block_hdr.blockSize - sizeof(mlv_vidf_hdr_t))
                {
                    print_msg(MSG_ERROR, "AUDF: Frame space is larger than block size. Skipping\n");
                    skip_block = 1;
                }

                if(!skip_block)
                {
                    /* skip frame space */
                    file_set_pos(in_file, block_hdr.frameSpace, SEEK_CUR);

                    int frame_size = block_hdr.blockSize - sizeof(mlv_audf_hdr_t) - block_hdr.frameSpace;
                    void *buf = malloc(frame_size);

                    if(!buf)
                    {
                        print_msg(MSG_ERROR, "AUDF: Failed to allocate buffer\n");
                        goto abort;
                    }
                    
                    if(fread(buf, frame_size, 1, in_file) != 1)
                    {
                        free(buf);
                        print_msg(MSG_ERROR, "AUDF: File ends in the middle of a block\n");
                        goto abort;
                    }


                    if(mlv_output && !only_metadata_mode && (!extract_block || !strncasecmp(extract_block, (char*)block_hdr.blockType, 4)))
                    {
                        /* correct header size */
                        block_hdr.blockSize = sizeof(mlv_audf_hdr_t) + frame_size;
                        
                        if(fwrite(&block_hdr, sizeof(mlv_audf_hdr_t), 1, out_file) != 1)
                        {
                            print_msg(MSG_ERROR, "AUDF: Failed writing into .MLV file\n");
                            print_msg(MSG_ERROR, "ptr: 0x%08X type: %s\n", &block_hdr, block_hdr.blockType);
                            goto abort;
                        }
                        if(fwrite(buf, frame_size, 1, out_file) != 1)
                        {
                            print_msg(MSG_ERROR, "AUDF: Failed writing into .MLV file\n");
                            goto abort;
                        }
                    }
                
                    /* only write WAV if the WAVI header created a file */
                    if(out_file_wav)
                    {
                        if(!wavi_info.timestamp)
                        {
                            print_msg(MSG_ERROR, "AUDF: Received AUDF without WAVI, the .wav file might be corrupt\n");
                        }
                        
                        /* assume block size is uniform, this allows random access */
                        file_set_pos(out_file_wav, wav_header_size + frame_size * block_hdr.frameNumber, SEEK_SET);
                        
                        if(fwrite(buf, frame_size, 1, out_file_wav) != 1)
                        {
                            print_msg(MSG_ERROR, "AUDF: Failed writing into .WAV file\n");
                            goto abort;
                        }
                        
                        wav_file_size += frame_size;
                    }
                    free(buf);
                }
                audf_frames_processed++;
            }
            else if(!memcmp(buf.blockType, "VIDF", 4))
            {
                mlv_vidf_hdr_t block_hdr;
                uint32_t hdr_size = MIN(sizeof(mlv_vidf_hdr_t), buf.blockSize);

                if(fread(&block_hdr, hdr_size, 1, in_file) != 1)
                {
                    print_msg(MSG_ERROR, "VIDF: File ends in the middle of a block\n");
                    goto abort;
                }

                /* store last VIDF for reference */
                last_vidf = block_hdr;

                lua_handle_hdr(lua_state, buf.blockType, &block_hdr, sizeof(block_hdr));

                if(verbose)
                {
                    print_msg(MSG_INFO, "   Frame: #%04d\n", block_hdr.frameNumber);
                    print_msg(MSG_INFO, "    Crop: %dx%d\n", block_hdr.cropPosX, block_hdr.cropPosY);
                    print_msg(MSG_INFO, "     Pan: %dx%d\n", block_hdr.panPosX, block_hdr.panPosY);
                    print_msg(MSG_INFO, "   Space: %d\n", block_hdr.frameSpace);
                }
                
                if(alter_fps)
                {
                    block_hdr.timestamp = (((uint64_t)block_hdr.frameNumber * 10000000ULL) / alter_fps) * 1000;
                }
                
                uint32_t skip_block = 0;
                
                if(block_hdr.frameSpace > block_hdr.blockSize - sizeof(mlv_vidf_hdr_t))
                {
                    print_msg(MSG_ERROR, "VIDF: Frame space is larger than block size. Skipping\n");
                    skip_block = 1;
                }

                if((raw_output || mlv_output || dng_output || lua_state) && !skip_block)
                {
                    /* if already compressed, we have to decompress it first */
                    int compressed = main_header.videoClass & MLV_VIDEO_CLASS_FLAG_LZMA;
                    int recompress = compressed && compress_output;
                    int decompress = compressed && decompress_output;

                    int frame_size = block_hdr.blockSize - sizeof(mlv_vidf_hdr_t) - block_hdr.frameSpace;
                    int prev_frame_size = frame_size;

                    uint64_t skipSize = block_hdr.frameSpace;
                    if(fix_bug == BUG_ID_FRAMEDATA_MISALIGN && (int)block_hdr.frameSpace >= fix_bug_2_offset)
                    {
                        print_msg(MSG_INFO, "BUG_ID_FRAMEDATA_MISALIGN: Offset frame data by %d byte\n", fix_bug_2_offset);
                        skipSize -= fix_bug_2_offset;
                    }
                    file_set_pos(in_file, skipSize, SEEK_CUR);
                    
                    /* we can correct that frame by fixing frame space */
                    if(fix_bug == BUG_ID_BLOCKSIZE_WRONG && fix_bug_1_offset != 0)
                    {
                        print_msg(MSG_INFO, "BUG_ID_BLOCKSIZE_WRONG: Seeking %d byte\n", fix_bug_1_offset);
                        file_set_pos(in_file, fix_bug_1_offset, SEEK_CUR);
                        block_hdr.frameSpace += fix_bug_1_offset;
                        fix_bug_1_offset = 0;
                    }
                    
                    /* check if there is enough memory for that frame */
                    if(frame_size > (int)frame_buffer_size)
                    {
                        /* no, set new size */
                        frame_buffer_size = frame_size;
                        
                        /* realloc buffers */
                        frame_buffer = realloc(frame_buffer, frame_buffer_size);
                        
                        if(!frame_buffer)
                        {
                            print_msg(MSG_ERROR, "VIDF: Failed to allocate %d byte\n", frame_buffer_size);
                            goto abort;
                        }
                        
                        if(frame_arith_buffer)
                        {
                            frame_arith_buffer = realloc(frame_arith_buffer, frame_buffer_size * sizeof(uint32_t));
                            if(!frame_arith_buffer)
                            {
                                print_msg(MSG_ERROR, "VIDF: Failed to allocate %d byte\n", frame_buffer_size);
                                goto abort;
                            }
                        }
                        
                        if(frame_sub_buffer)
                        {
                            frame_sub_buffer = realloc(frame_sub_buffer, frame_buffer_size);
                            if(!frame_sub_buffer)
                            {
                                print_msg(MSG_ERROR, "VIDF: Failed to allocate %d byte\n", frame_buffer_size);
                                goto abort;
                            }
                        }
                        
                        if(prev_frame_buffer)
                        {
                            prev_frame_buffer = realloc(prev_frame_buffer, frame_buffer_size);
                            if(!prev_frame_buffer)
                            {
                                print_msg(MSG_ERROR, "VIDF: Failed to allocate %d byte\n", frame_buffer_size);
                                goto abort;
                            }
                        }
                    }
                    
                    if(fread(frame_buffer, frame_size, 1, in_file) != 1)
                    {
                        print_msg(MSG_ERROR, "VIDF: File ends in the middle of a block\n");
                        goto abort;
                    }

                    if(fix_bug == BUG_ID_FRAMEDATA_MISALIGN && (int)block_hdr.frameSpace >= fix_bug_2_offset)
                    {
                        file_set_pos(in_file, fix_bug_2_offset, SEEK_CUR);
                    }
                    
                    lua_handle_hdr_data(lua_state, buf.blockType, "_data_read", &block_hdr, sizeof(block_hdr), frame_buffer, frame_size);

                    if(recompress || decompress || ((raw_output || dng_output) && compressed))
                    {
#ifdef MLV_USE_LZMA
                        size_t lzma_out_size = *(uint32_t *)frame_buffer;
                        size_t lzma_in_size = frame_size - LZMA_PROPS_SIZE - 4;
                        size_t lzma_props_size = LZMA_PROPS_SIZE;
                        unsigned char *lzma_out = malloc(lzma_out_size);

                        int ret = LzmaUncompress(
                            lzma_out, &lzma_out_size,
                            (unsigned char *)&frame_buffer[4 + LZMA_PROPS_SIZE], &lzma_in_size,
                            (unsigned char *)&frame_buffer[4], lzma_props_size
                            );

                        if(ret == SZ_OK)
                        {
                            frame_size = lzma_out_size;
                            memcpy(frame_buffer, lzma_out, frame_size);
                            if(verbose)
                            {
                                print_msg(MSG_INFO, "    LZMA: "FMT_SIZE" -> "FMT_SIZE"  (%2.2f%%)\n", lzma_in_size, lzma_out_size, ((float)lzma_out_size * 100.0f) / (float)lzma_in_size);
                            }
                        }
                        else
                        {
                            print_msg(MSG_INFO, "    LZMA: Failed (%d)\n", ret);
                            goto abort;
                        }
#else
                        print_msg(MSG_INFO, "    LZMA: not compiled into this release, aborting.\n");
                        goto abort;
#endif
                    }

                    int old_depth = lv_rec_footer.raw_info.bits_per_pixel;
                    int new_depth = bit_depth;

                    /* this value changes in this context */
                    int current_depth = old_depth;

                    /* in subtract mode, subtract reference frame. do that before averaging */
                    if(subtract_mode)
                    {
                        if((int)subtract_frame_buffer_size != frame_size)
                        {
                            print_msg(MSG_ERROR, "Error: Frame sizes of footage and subtract frame differ (%d, %d)", frame_size, subtract_frame_buffer_size);
                            break;
                        }
                        
                        int pitch = video_xRes * current_depth / 8;

                        for(int y = 0; y < video_yRes; y++)
                        {
                            uint16_t *src_line = (uint16_t *)&frame_buffer[y * pitch];
                            uint16_t *sub_line = (uint16_t *)&frame_sub_buffer[y * pitch];

                            for(int x = 0; x < video_xRes; x++)
                            {
                                int32_t value = bitextract(src_line, x, current_depth);
                                int32_t sub_value = bitextract(sub_line, x, current_depth);

                                value -= sub_value;
                                value += lv_rec_footer.raw_info.black_level; /* should we really add it here? or better subtract it from averaged frame? */
                                value = COERCE(value, lv_rec_footer.raw_info.black_level, lv_rec_footer.raw_info.white_level);

                                bitinsert(src_line, x, current_depth, value);
                            }
                        }
                    }

                    /* in average mode, sum up all pixel values of a pixel position */
                    if(average_mode)
                    {
                        int pitch = video_xRes * current_depth / 8;

                        for(int y = 0; y < video_yRes; y++)
                        {
                            uint16_t *src_line = (uint16_t *)&frame_buffer[y * pitch];

                            for(int x = 0; x < video_xRes; x++)
                            {
                                uint16_t value = bitextract(src_line, x, current_depth);

                                frame_arith_buffer[y * video_xRes + x] += value;
                            }
                        }

                        average_samples++;
                    }

                    /* now resample bit depth if requested */
                    if(new_depth && (old_depth != new_depth))
                    {
                        int new_size = (video_xRes * video_yRes * new_depth + 7) / 8;
                        unsigned char *new_buffer = malloc(new_size);

                        if(verbose)
                        {
                            print_msg(MSG_INFO, "   depth: %d -> %d, size: %d -> %d (%2.2f%%)\n", old_depth, new_depth, frame_size, new_size, ((float)new_depth * 100.0f) / (float)old_depth);
                        }

                        int calced_size = ((video_xRes * video_yRes * old_depth + 7) / 8);
                        if(calced_size > frame_size)
                        {
                            print_msg(MSG_INFO, "Error: old frame size is too small for %dx%d at %d bpp. Input data corrupt. (%d < %d)\n", video_xRes, video_yRes, old_depth, frame_size, calced_size);
                            break;
                        }

                        int old_pitch = video_xRes * old_depth / 8;
                        int new_pitch = video_xRes * new_depth / 8;

                        for(int y = 0; y < video_yRes; y++)
                        {
                            uint16_t *src_line = (uint16_t *)&frame_buffer[y * old_pitch];
                            uint16_t *dst_line = (uint16_t *)&new_buffer[y * new_pitch];

                            for(int x = 0; x < video_xRes; x++)
                            {
                                uint16_t value = bitextract(src_line, x, old_depth);

                                /* normalize the old value to 16 bits */
                                value <<= (16-old_depth);

                                /* convert the old value to destination depth */
                                value >>= (16-new_depth);

                                bitinsert(dst_line, x, new_depth, value);
                            }
                        }

                        frame_size = new_size;
                        current_depth = new_depth;

                        memcpy(frame_buffer, new_buffer, frame_size);
                        free(new_buffer);
                    }

                    if(bit_zap)
                    {
                        int pitch = video_xRes * current_depth / 8;
                        uint32_t mask = ~((1 << (16 - bit_zap)) - 1);

                        for(int y = 0; y < video_yRes; y++)
                        {
                            uint16_t *src_line = (uint16_t *)&frame_buffer[y * pitch];

                            for(int x = 0; x < video_xRes; x++)
                            {
                                int32_t value = bitextract(src_line, x, current_depth);

                                /* normalize the old value to 16 bits */
                                value <<= (16-current_depth);

                                value &= mask;

                                /* convert the old value to destination depth */
                                value >>= (16-current_depth);


                                bitinsert(src_line, x, current_depth, value);
                            }
                        }
                    }

                    if(delta_encode_mode)
                    {
                        /* only delta encode, if not already encoded */
                        if(!(main_header.videoClass & MLV_VIDEO_CLASS_FLAG_DELTA))
                        {
                            uint8_t *current_frame_buffer = malloc(frame_size);
                            int pitch = video_xRes * current_depth / 8;

                            /* backup current frame for later */
                            memcpy(current_frame_buffer, frame_buffer, frame_size);

                            for(int y = 0; y < video_yRes; y++)
                            {
                                uint16_t *src_line = (uint16_t *)&frame_buffer[y * pitch];
                                uint16_t *ref_line = (uint16_t *)&prev_frame_buffer[y * pitch];
                                int32_t offset = 1 << (current_depth - 1);
                                int32_t max_val = (1 << current_depth) - 1;

                                for(int x = 0; x < video_xRes; x++)
                                {
                                    int32_t value = bitextract(src_line, x, current_depth);
                                    int32_t ref_value = bitextract(ref_line, x, current_depth);

                                    /* when e.g. using 16 bit values:
                                           delta =  1      -> encode to 0x8001
                                           delta =  0      -> encode to 0x8000
                                           delta = -1      -> encode to 0x7FFF
                                           delta = -0xFFFF -> encode to 0x0001
                                           delta =  0xFFFF -> encode to 0x7FFF
                                       so this is basically a signed int with overflow and a max/2 offset.
                                       this offset makes the frames uniform grey when viewing non-decoded frames and improves compression rate a bit.
                                    */
                                    int32_t delta = offset + value - ref_value;

                                    uint16_t new_value = (uint16_t)(delta & max_val);

                                    bitinsert(src_line, x, current_depth, new_value);
                                }
                            }

                            /* save current original frame to prev buffer */
                            memcpy(prev_frame_buffer, current_frame_buffer, frame_size);
                            free(current_frame_buffer);
                        }
                    }
                    else
                    {
                        /* delta decode, if input data is encoded */
                        if(main_header.videoClass & MLV_VIDEO_CLASS_FLAG_DELTA)
                        {
                            int pitch = video_xRes * current_depth / 8;

                            for(int y = 0; y < video_yRes; y++)
                            {
                                uint16_t *src_line = (uint16_t *)&frame_buffer[y * pitch];
                                uint16_t *ref_line = (uint16_t *)&prev_frame_buffer[y * pitch];
                                int32_t offset = 1 << (current_depth - 1);
                                int32_t max_val = (1 << current_depth) - 1;

                                for(int x = 0; x < video_xRes; x++)
                                {
                                    int32_t value = bitextract(src_line, x, current_depth);
                                    int32_t ref_value = bitextract(ref_line, x, current_depth);

                                    /* when e.g. using 16 bit values:
                                           delta =  1      -> encode to 0x8001
                                           delta =  0      -> encode to 0x8000
                                           delta = -1      -> encode to 0x7FFF
                                           delta = -0xFFFF -> encode to 0x0001
                                           delta =  0xFFFF -> encode to 0x7FFF
                                       so this is basically a signed int with overflow and a max/2 offset.
                                       this offset makes the frames uniform grey when viewing non-decoded frames and improves compression rate a bit.
                                    */
                                    int32_t delta = offset + value + ref_value;

                                    uint16_t new_value = (uint16_t)(delta & max_val);

                                    bitinsert(src_line, x, current_depth, new_value);
                                }
                            }

                            /* save current original frame to prev buffer */
                            memcpy(prev_frame_buffer, frame_buffer, frame_size);
                        }
                    }

                    /* when no end was specified, save all frames */
                    uint32_t frame_selected = (!frame_end) || ((block_hdr.frameNumber >= frame_start) && (block_hdr.frameNumber <= frame_end));

                    if(frame_selected)
                    {
                        lua_handle_hdr_data(lua_state, buf.blockType, "_data_write", &block_hdr, sizeof(block_hdr), frame_buffer, frame_size);

                        if(raw_output)
                        {
                            if(!lv_rec_footer.frameSize)
                            {
                                lv_rec_footer.frameSize = frame_size;
                            }

                            lua_handle_hdr_data(lua_state, buf.blockType, "_data_write_raw", &block_hdr, sizeof(block_hdr), frame_buffer, frame_size);

                            file_set_pos(out_file, (uint64_t)block_hdr.frameNumber * (uint64_t)frame_size, SEEK_SET);
                            if(fwrite(frame_buffer, frame_size, 1, out_file) != 1)
                            {
                                print_msg(MSG_ERROR, "VIDF: Failed writing into .RAW file\n");
                                goto abort;
                            }
                        }

                        if(dng_output)
                        {
                            void fix_vertical_stripes();
                            void find_and_fix_cold_pixels(int fix, int framenumber);
                            extern struct raw_info raw_info;

                            int frame_filename_len = strlen(output_filename) + 32;
                            char *frame_filename = malloc(frame_filename_len);
                            snprintf(frame_filename, frame_filename_len, "%s%06d.dng", output_filename, block_hdr.frameNumber);

                            lua_handle_hdr_data(lua_state, buf.blockType, "_data_write_dng", &block_hdr, sizeof(block_hdr), frame_buffer, frame_size);

                            raw_info = lv_rec_footer.raw_info;
                            raw_info.frame_size = frame_size;
                            raw_info.buffer = frame_buffer;

                            /* override the resolution from raw_info with the one from lv_rec_footer, if they don't match */
                            if (lv_rec_footer.xRes != raw_info.width)
                            {
                                raw_info.width = lv_rec_footer.xRes;
                                raw_info.pitch = raw_info.width * 14/8;
                                raw_info.active_area.x1 = 0;
                                raw_info.active_area.x2 = raw_info.width;
                                raw_info.jpeg.x = 0;
                                raw_info.jpeg.width = raw_info.width;
                            }

                            if (lv_rec_footer.yRes != raw_info.height)
                            {
                                raw_info.height = lv_rec_footer.yRes;
                                raw_info.active_area.y1 = 0;
                                raw_info.active_area.y2 = raw_info.height;
                                raw_info.jpeg.y = 0;
                                raw_info.jpeg.height = raw_info.height;
                            }

                            /* call raw2dng code */
                            fix_vertical_stripes();
                            find_and_fix_cold_pixels(fix_cold_pixels, block_hdr.frameNumber);

                            /* this is internal again */
                            chroma_smooth(chroma_smooth_method, &raw_info);

                            /* set MLV metadata into DNG tags */
                            dng_set_framerate_rational(main_header.sourceFpsNom, main_header.sourceFpsDenom);
                            dng_set_shutter(1, (int)(1000000.0f/(float)expo_info.shutterValue));
                            dng_set_aperture(lens_info.aperture, 100);
                            dng_set_camname((char*)idnt_info.cameraName);
                            dng_set_description((char*)info_string);
                            dng_set_lensmodel((char*)lens_info.lensName);
                            dng_set_focal(lens_info.focalLength, 1);
                            dng_set_iso(expo_info.isoValue);

                            //dng_set_wbgain(1024, wbal_info.wbgain_r, 1024, wbal_info.wbgain_g, 1024, wbal_info.wbgain_b);

                            /* calculate the time this frame was taken at, i.e., the start time + the current timestamp. this can be off by a second but it's better than nothing */
                            int ms = 0.5 + buf.timestamp / 1000.0;
                            int sec = ms / 1000;
                            ms %= 1000;
                            // FIXME: the struct tm doesn't have tm_gmtoff on Linux so the result might be wrong?
                            struct tm tm;
                            tm.tm_sec = rtci_info.tm_sec + sec;
                            tm.tm_min = rtci_info.tm_min;
                            tm.tm_hour = rtci_info.tm_hour;
                            tm.tm_mday = rtci_info.tm_mday;
                            tm.tm_mon = rtci_info.tm_mon;
                            tm.tm_year = rtci_info.tm_year;
                            tm.tm_wday = rtci_info.tm_wday;
                            tm.tm_yday = rtci_info.tm_yday;
                            tm.tm_isdst = rtci_info.tm_isdst;

                            if(mktime(&tm) != -1)
                            {
                                char datetime_str[32];
                                char subsec_str[8];
                                strftime(datetime_str, 20, "%Y:%m:%d %H:%M:%S", &tm);
                                snprintf(subsec_str, sizeof(subsec_str), "%03d", ms);
                                dng_set_datetime(datetime_str, subsec_str);
                            }
                            else
                            {
                                // soemthing went wrong. let's proceed anyway
                                print_msg(MSG_ERROR, "VIDF: [W] Failed calculating the DateTime from the timestamp\n");
                                dng_set_datetime("", "");
                            }


                            uint64_t serial = 0;
                            char *end;
                            serial = strtoull((char *)idnt_info.cameraSerial, &end, 16);
                            if (serial && !*end)
                            {
                                char serial_str[64];

                                sprintf(serial_str, "%"PRIu64, serial);
                                dng_set_camserial((char*)serial_str);
                            }

                            /* finally save the DNG */
                            if(!save_dng(frame_filename, &raw_info))
                            {
                                print_msg(MSG_ERROR, "VIDF: Failed writing into .DNG file\n");
                                goto abort;
                            }

                            /* callout for a saved dng file */
                            lua_call_va(lua_state, "dng_saved", "si", frame_filename, block_hdr.frameNumber);

                            free(frame_filename);
                        }

                        if(mlv_output && !only_metadata_mode && !average_mode && (!extract_block || !strncasecmp(extract_block, (char*)block_hdr.blockType, 4)))
                        {
                            if(compress_output)
                            {
#ifdef MLV_USE_LZMA
                                size_t lzma_out_size = 2 * frame_size;
                                size_t lzma_in_size = frame_size;
                                size_t lzma_props_size = LZMA_PROPS_SIZE;
                                unsigned char *lzma_out = malloc(lzma_out_size + LZMA_PROPS_SIZE);

                                int ret = LzmaCompress(
                                    &lzma_out[LZMA_PROPS_SIZE], &lzma_out_size,
                                    (unsigned char *)frame_buffer, lzma_in_size,
                                    &lzma_out[0], &lzma_props_size,
                                    lzma_level, lzma_dict, lzma_lc, lzma_lp, lzma_pb, lzma_fb, lzma_threads
                                    );

                                if(ret == SZ_OK)
                                {
                                    /* store original frame size */
                                    *(uint32_t *)frame_buffer = frame_size;

                                    /* set new compressed size and copy buffers */
                                    frame_size = lzma_out_size + LZMA_PROPS_SIZE + 4;
                                    memcpy(&frame_buffer[4], lzma_out, frame_size - 4);

                                    if(verbose)
                                    {
                                        print_msg(MSG_INFO, "    LZMA: "FMT_SIZE" -> "FMT_SIZE"  (%2.2f%%)\n", lzma_in_size, frame_size, ((float)lzma_out_size * 100.0f) / (float)lzma_in_size);
                                    }
                                }
                                else
                                {
                                    print_msg(MSG_INFO, "    LZMA: Failed (%d)\n", ret);
                                    goto abort;
                                }
                                free(lzma_out);
#else
                                print_msg(MSG_INFO, "    LZMA: not compiled into this release, aborting.\n");
                                goto abort;
#endif
                            }

                            if(frame_size != prev_frame_size)
                            {
                                print_msg(MSG_INFO, "  saving: "FMT_SIZE" -> "FMT_SIZE"  (%2.2f%%)\n", prev_frame_size, frame_size, ((float)frame_size * 100.0f) / (float)prev_frame_size);
                            }

                            lua_handle_hdr_data(lua_state, buf.blockType, "_data_write_mlv", &block_hdr, sizeof(block_hdr), frame_buffer, frame_size);

                            /* delete free space and correct header size if needed */
                            block_hdr.blockSize = sizeof(mlv_vidf_hdr_t) + frame_size;
                            block_hdr.frameSpace = 0;
                            block_hdr.frameNumber -= frame_start;

                            if(fwrite(&block_hdr, sizeof(mlv_vidf_hdr_t), 1, out_file) != 1)
                            {
                                print_msg(MSG_ERROR, "VIDF: Failed writing into .MLV file\n");
                                goto abort;
                            }
                            if(fwrite(frame_buffer, frame_size, 1, out_file) != 1)
                            {
                                print_msg(MSG_ERROR, "VIDF: Failed writing into .MLV file\n");
                                goto abort;
                            }
                        }
                    }
                }
                else
                {
                    file_set_pos(in_file, position + block_hdr.blockSize, SEEK_SET);
                    
                    /* we can correct that frame by fixing frame space */
                    if(fix_bug == BUG_ID_BLOCKSIZE_WRONG && fix_bug_1_offset != 0)
                    {
                        print_msg(MSG_INFO, "BUG_ID_BLOCKSIZE_WRONG: Seeking %d byte\n", fix_bug_1_offset);
                        file_set_pos(in_file, fix_bug_1_offset, SEEK_CUR);
                        fix_bug_1_offset = 0;
                    }
                }

                vidf_max_number = MAX(vidf_max_number, block_hdr.frameNumber);

                vidf_frames_processed++;
            }
            else if(!memcmp(buf.blockType, "LENS", 4))
            {
                uint32_t hdr_size = MIN(sizeof(mlv_lens_hdr_t), buf.blockSize);

                if(fread(&lens_info, hdr_size, 1, in_file) != 1)
                {
                    print_msg(MSG_ERROR, "File ends in the middle of a block\n");
                    goto abort;
                }

                /* skip remaining data, if there is any */
                file_set_pos(in_file, position + lens_info.blockSize, SEEK_SET);

                lua_handle_hdr(lua_state, buf.blockType, &lens_info, sizeof(lens_info));

                if(verbose)
                {
                    print_msg(MSG_INFO, "     Name:        '%s'\n", lens_info.lensName);
                    print_msg(MSG_INFO, "     Serial:      '%s'\n", lens_info.lensSerial);
                    print_msg(MSG_INFO, "     Focal Len:   %d mm\n", lens_info.focalLength);
                    print_msg(MSG_INFO, "     Focus Dist:  %d mm\n", lens_info.focalDist);
                    print_msg(MSG_INFO, "     Aperture:    f/%.2f\n", (double)lens_info.aperture / 100.0f);
                    print_msg(MSG_INFO, "     IS Mode:     %d\n", lens_info.stabilizerMode);
                    print_msg(MSG_INFO, "     AF Mode:     %d\n", lens_info.autofocusMode);
                    print_msg(MSG_INFO, "     Lens ID:     0x%08X\n", lens_info.lensID);
                    print_msg(MSG_INFO, "     Flags:       0x%08X\n", lens_info.flags);
                }

                if(mlv_output && !no_metadata_mode && (!extract_block || !strncasecmp(extract_block, (char*)lens_info.blockType, 4)))
                {
                    /* correct header size if needed */
                    lens_info.blockSize = sizeof(mlv_lens_hdr_t);
                    if(fwrite(&lens_info, lens_info.blockSize, 1, out_file) != 1)
                    {
                        print_msg(MSG_ERROR, "Failed writing into .MLV file\n");
                        goto abort;
                    }
                }
            }
            else if(!memcmp(buf.blockType, "INFO", 4))
            {
                mlv_info_hdr_t block_hdr;
                int32_t hdr_size = MIN(sizeof(mlv_info_hdr_t), buf.blockSize);

                if(fread(&block_hdr, hdr_size, 1, in_file) != 1)
                {
                    print_msg(MSG_ERROR, "File ends in the middle of a block\n");
                    goto abort;
                }

                lua_handle_hdr(lua_state, buf.blockType, &block_hdr, sizeof(block_hdr));

                /* get the string length and malloc a buffer for that string */
                int str_length = block_hdr.blockSize - hdr_size;

                if(str_length)
                {
                    char *buf = malloc(str_length + 1);

                    if(fread(buf, str_length, 1, in_file) != 1)
                    {
                        free(buf);
                        print_msg(MSG_ERROR, "File ends in the middle of a block\n");
                        goto abort;
                    }

                    strncpy(info_string, buf, sizeof(info_string));

                    if(verbose)
                    {
                        buf[str_length] = '\000';
                        print_msg(MSG_INFO, "     String:   '%s'\n", buf);
                    }

                    /* only output this block if there is any data */
                    if(mlv_output && !no_metadata_mode && (!extract_block || !strncasecmp(extract_block, (char*)block_hdr.blockType, 4)))
                    {
                        /* correct header size if needed */
                        block_hdr.blockSize = sizeof(mlv_info_hdr_t) + str_length;
                        if(fwrite(&block_hdr, sizeof(mlv_info_hdr_t), 1, out_file) != 1)
                        {
                            free(buf);
                            print_msg(MSG_ERROR, "Failed writing into .MLV file\n");
                            goto abort;
                        }
                        if(fwrite(buf, str_length, 1, out_file) != 1)
                        {
                            free(buf);
                            print_msg(MSG_ERROR, "Failed writing into .MLV file\n");
                            goto abort;
                        }
                    }

                    free(buf);
                }
            }
            else if(!memcmp(buf.blockType, "DEBG", 4))
            {
                mlv_debg_hdr_t block_hdr;
                int32_t hdr_size = MIN(sizeof(mlv_debg_hdr_t), buf.blockSize);

                if(fread(&block_hdr, hdr_size, 1, in_file) != 1)
                {
                    print_msg(MSG_ERROR, "File ends in the middle of a block\n");
                    goto abort;
                }

                lua_handle_hdr(lua_state, buf.blockType, &block_hdr, sizeof(block_hdr));

                /* get the string length and malloc a buffer for that string */
                int str_length = block_hdr.blockSize - hdr_size;

                if(str_length)
                {
                    char *buf = malloc(str_length + 1);

                    if(fread(buf, str_length, 1, in_file) != 1)
                    {
                        free(buf);
                        print_msg(MSG_ERROR, "File ends in the middle of a block\n");
                        goto abort;
                    }

                    if(verbose)
                    {
                        buf[block_hdr.length] = '\000';
                        print_msg(MSG_INFO, "     String:   '%s'\n", buf);
                    }
                    
                    char *log_filename = malloc(strlen(input_filename) + 6);
                    snprintf(log_filename, strlen(input_filename) + 6, "%s.log", input_filename);
                    
                    FILE *log_file = fopen(log_filename, "ab+");
                    fwrite(buf, block_hdr.length, 1, log_file);
                    fclose(log_file);
                    free(log_filename);

                    /* only output this block if there is any data */
                    if(mlv_output && !no_metadata_mode)
                    {
                        /* correct header size if needed */
                        block_hdr.blockSize = sizeof(mlv_debg_hdr_t) + str_length;
                        if(fwrite(&block_hdr, sizeof(mlv_debg_hdr_t), 1, out_file) != 1)
                        {
                            free(buf);
                            print_msg(MSG_ERROR, "Failed writing into .MLV file\n");
                            goto abort;
                        }
                        if(fwrite(buf, str_length, 1, out_file) != 1)
                        {
                            free(buf);
                            print_msg(MSG_ERROR, "Failed writing into .MLV file\n");
                            goto abort;
                        }
                    }

                    free(buf);
                }
            }
            else if(!memcmp(buf.blockType, "ELVL", 4))
            {
                mlv_elvl_hdr_t block_hdr;
                uint32_t hdr_size = MIN(sizeof(mlv_elvl_hdr_t), buf.blockSize);

                if(fread(&block_hdr, hdr_size, 1, in_file) != 1)
                {
                    print_msg(MSG_ERROR, "File ends in the middle of a block\n");
                    goto abort;
                }

                /* skip remaining data, if there is any */
                file_set_pos(in_file, position + block_hdr.blockSize, SEEK_SET);

                lua_handle_hdr(lua_state, buf.blockType, &block_hdr, sizeof(block_hdr));

                if(verbose)
                {
                    print_msg(MSG_INFO, "     Roll:    %2.2f\n", (double)block_hdr.roll / 100.0f);
                    print_msg(MSG_INFO, "     Pitch:   %2.2f\n", (double)block_hdr.pitch / 100.0f);
                }

                if(mlv_output && !no_metadata_mode && (!extract_block || !strncasecmp(extract_block, (char*)block_hdr.blockType, 4)))
                {
                    /* correct header size if needed */
                    block_hdr.blockSize = sizeof(mlv_elvl_hdr_t);
                    if(fwrite(&block_hdr, block_hdr.blockSize, 1, out_file) != 1)
                    {
                        print_msg(MSG_ERROR, "Failed writing into .MLV file\n");
                        goto abort;
                    }
                }
            }
            else if(!memcmp(buf.blockType, "STYL", 4))
            {
                mlv_styl_hdr_t block_hdr;
                uint32_t hdr_size = MIN(sizeof(mlv_styl_hdr_t), buf.blockSize);

                if(fread(&block_hdr, hdr_size, 1, in_file) != 1)
                {
                    print_msg(MSG_ERROR, "File ends in the middle of a block\n");
                    goto abort;
                }

                /* skip remaining data, if there is any */
                file_set_pos(in_file, position + block_hdr.blockSize, SEEK_SET);

                lua_handle_hdr(lua_state, buf.blockType, &block_hdr, sizeof(block_hdr));

                if(verbose)
                {
                    print_msg(MSG_INFO, "     picStyle:   %d\n", block_hdr.picStyleId);
                    print_msg(MSG_INFO, "     contrast:   %d\n", block_hdr.contrast);
                    print_msg(MSG_INFO, "     sharpness:  %d\n", block_hdr.sharpness);
                    print_msg(MSG_INFO, "     saturation: %d\n", block_hdr.saturation);
                    print_msg(MSG_INFO, "     colortone:  %d\n", block_hdr.colortone);
                }

                if(mlv_output && !no_metadata_mode && (!extract_block || !strncasecmp(extract_block, (char*)block_hdr.blockType, 4)))
                {
                    /* correct header size if needed */
                    block_hdr.blockSize = sizeof(mlv_styl_hdr_t);
                    if(fwrite(&block_hdr, block_hdr.blockSize, 1, out_file) != 1)
                    {
                        print_msg(MSG_ERROR, "Failed writing into .MLV file\n");
                        goto abort;
                    }
                }
            }
            else if(!memcmp(buf.blockType, "WBAL", 4))
            {
                uint32_t hdr_size = MIN(sizeof(mlv_wbal_hdr_t), buf.blockSize);

                if(fread(&wbal_info, hdr_size, 1, in_file) != 1)
                {
                    print_msg(MSG_ERROR, "File ends in the middle of a block\n");
                    goto abort;
                }

                /* skip remaining data, if there is any */
                file_set_pos(in_file, position + wbal_info.blockSize, SEEK_SET);

                lua_handle_hdr(lua_state, buf.blockType, &wbal_info, sizeof(wbal_info));

                if(verbose)
                {
                    print_msg(MSG_INFO, "     Mode:   %d\n", wbal_info.wb_mode);
                    print_msg(MSG_INFO, "     Kelvin:   %d\n", wbal_info.kelvin);
                    print_msg(MSG_INFO, "     Gain R:   %d\n", wbal_info.wbgain_r);
                    print_msg(MSG_INFO, "     Gain G:   %d\n", wbal_info.wbgain_g);
                    print_msg(MSG_INFO, "     Gain B:   %d\n", wbal_info.wbgain_b);
                    print_msg(MSG_INFO, "     Shift GM:   %d\n", wbal_info.wbs_gm);
                    print_msg(MSG_INFO, "     Shift BA:   %d\n", wbal_info.wbs_ba);
                }

                if(mlv_output && !no_metadata_mode && (!extract_block || !strncasecmp(extract_block, (char*)wbal_info.blockType, 4)))
                {
                    /* correct header size if needed */
                    wbal_info.blockSize = sizeof(mlv_wbal_hdr_t);
                    if(fwrite(&wbal_info, wbal_info.blockSize, 1, out_file) != 1)
                    {
                        print_msg(MSG_ERROR, "Failed writing into .MLV file\n");
                        goto abort;
                    }
                }
            }
            else if(!memcmp(buf.blockType, "IDNT", 4))
            {
                uint32_t hdr_size = MIN(sizeof(mlv_idnt_hdr_t), buf.blockSize);

                if(fread(&idnt_info, hdr_size, 1, in_file) != 1)
                {
                    print_msg(MSG_ERROR, "File ends in the middle of a block\n");
                    goto abort;
                }

                /* skip remaining data, if there is any */
                file_set_pos(in_file, position + idnt_info.blockSize, SEEK_SET);

                lua_handle_hdr(lua_state, buf.blockType, &idnt_info, sizeof(idnt_info));

                if(verbose)
                {
                    print_msg(MSG_INFO, "     Camera Name:   '%s'\n", idnt_info.cameraName);
                    print_msg(MSG_INFO, "     Camera Serial: '%s'\n", idnt_info.cameraSerial);
                    print_msg(MSG_INFO, "     Camera Model:  0x%08X\n", idnt_info.cameraModel);
                }

                if(mlv_output && !no_metadata_mode && (!extract_block || !strncasecmp(extract_block, (char*)idnt_info.blockType, 4)))
                {
                    /* correct header size if needed */
                    idnt_info.blockSize = sizeof(mlv_idnt_hdr_t);
                    if(fwrite(&idnt_info, idnt_info.blockSize, 1, out_file) != 1)
                    {
                        print_msg(MSG_ERROR, "Failed writing into .MLV file\n");
                        goto abort;
                    }
                }
            }
            else if(!memcmp(buf.blockType, "RTCI", 4))
            {
                uint32_t hdr_size = MIN(sizeof(mlv_rtci_hdr_t), buf.blockSize);

                if(fread(&rtci_info, hdr_size, 1, in_file) != 1)
                {
                    print_msg(MSG_ERROR, "File ends in the middle of a block\n");
                    goto abort;
                }

                /* skip remaining data, if there is any */
                file_set_pos(in_file, position + rtci_info.blockSize, SEEK_SET);

                lua_handle_hdr(lua_state, buf.blockType, &rtci_info, sizeof(rtci_info));

                if(verbose)
                {
                    print_msg(MSG_INFO, "     Date:        %02d.%02d.%04d\n", rtci_info.tm_mday, rtci_info.tm_mon + 1, 1900 + rtci_info.tm_year);
                    print_msg(MSG_INFO, "     Time:        %02d:%02d:%02d (GMT+%d)\n", rtci_info.tm_hour, rtci_info.tm_min, rtci_info.tm_sec, rtci_info.tm_gmtoff);
                    print_msg(MSG_INFO, "     Zone:        '%s'\n", rtci_info.tm_zone);
                    print_msg(MSG_INFO, "     Day of week: %d\n", rtci_info.tm_wday);
                    print_msg(MSG_INFO, "     Day of year: %d\n", rtci_info.tm_yday);
                    print_msg(MSG_INFO, "     Daylight s.: %d\n", rtci_info.tm_isdst);
                }

                if(mlv_output && !no_metadata_mode && (!extract_block || !strncasecmp(extract_block, (char*)rtci_info.blockType, 4)))
                {
                    /* correct header size if needed */
                    rtci_info.blockSize = sizeof(mlv_rtci_hdr_t);
                    if(fwrite(&rtci_info, rtci_info.blockSize, 1, out_file) != 1)
                    {
                        print_msg(MSG_ERROR, "Failed writing into .MLV file\n");
                        goto abort;
                    }
                }
            }
            else if(!memcmp(buf.blockType, "MARK", 4))
            {
                mlv_mark_hdr_t block_hdr;
                uint32_t hdr_size = MIN(sizeof(mlv_mark_hdr_t), buf.blockSize);

                if(fread(&block_hdr, hdr_size, 1, in_file) != 1)
                {
                    print_msg(MSG_ERROR, "File ends in the middle of a block\n");
                    goto abort;
                }

                /* skip remaining data, if there is any */
                file_set_pos(in_file, position + block_hdr.blockSize, SEEK_SET);

                lua_handle_hdr(lua_state, buf.blockType, &block_hdr, sizeof(block_hdr));

                if(verbose)
                {
                    print_msg(MSG_INFO, "  Button: 0x%02X\n", block_hdr.type);
                }

                if(mlv_output && !no_metadata_mode && (!extract_block || !strncasecmp(extract_block, (char*)block_hdr.blockType, 4)))
                {
                    /* correct header size if needed */
                    block_hdr.blockSize = sizeof(mlv_mark_hdr_t);
                    if(fwrite(&block_hdr, block_hdr.blockSize, 1, out_file) != 1)
                    {
                        print_msg(MSG_ERROR, "Failed writing into .MLV file\n");
                        goto abort;
                    }
                }
            }
            else if(!memcmp(buf.blockType, "EXPO", 4))
            {
                uint32_t hdr_size = MIN(sizeof(mlv_expo_hdr_t), buf.blockSize);

                if(fread(&expo_info, hdr_size, 1, in_file) != 1)
                {
                    print_msg(MSG_ERROR, "File ends in the middle of a block\n");
                    goto abort;
                }

                /* skip remaining data, if there is any */
                file_set_pos(in_file, position + expo_info.blockSize, SEEK_SET);

                lua_handle_hdr(lua_state, buf.blockType, &expo_info, sizeof(expo_info));

                if(verbose)
                {
                    print_msg(MSG_INFO, "     ISO Mode:   %d\n", expo_info.isoMode);
                    print_msg(MSG_INFO, "     ISO:        %d\n", expo_info.isoValue);
                    print_msg(MSG_INFO, "     ISO Analog: %d\n", expo_info.isoAnalog);
                    print_msg(MSG_INFO, "     ISO DGain:  %d/1024 EV\n", expo_info.digitalGain);
                    print_msg(MSG_INFO, "     Shutter:    %" PRIu64 " microseconds (1/%.2f)\n", expo_info.shutterValue, 1000000.0f/(float)expo_info.shutterValue);
                }

                if(mlv_output && !no_metadata_mode && (!extract_block || !strncasecmp(extract_block, (char*)expo_info.blockType, 4)))
                {
                    /* correct header size if needed */
                    expo_info.blockSize = sizeof(mlv_expo_hdr_t);
                    if(fwrite(&expo_info, expo_info.blockSize, 1, out_file) != 1)
                    {
                        print_msg(MSG_ERROR, "Failed writing into .MLV file\n");
                        goto abort;
                    }
                }
            }
            else if(!memcmp(buf.blockType, "RAWI", 4))
            {
                mlv_rawi_hdr_t block_hdr;
                uint32_t hdr_size = MIN(sizeof(mlv_rawi_hdr_t), buf.blockSize);

                if(fread(&block_hdr, hdr_size, 1, in_file) != 1)
                {
                    print_msg(MSG_ERROR, "File ends in the middle of a block\n");
                    goto abort;
                }

                /* skip remaining data, if there is any */
                file_set_pos(in_file, position + block_hdr.blockSize, SEEK_SET);
                
                if(black_fix)
                {
                    block_hdr.raw_info.black_level = black_fix;
                }
                
                lua_handle_hdr(lua_state, buf.blockType, &block_hdr, sizeof(block_hdr));

                video_xRes = block_hdr.xRes;
                video_yRes = block_hdr.yRes;
                if(verbose)
                {
                    print_msg(MSG_INFO, "    Res:  %dx%d\n", block_hdr.xRes, block_hdr.yRes);
                    print_msg(MSG_INFO, "    raw_info:\n");
                    print_msg(MSG_INFO, "      api_version      0x%08X\n", block_hdr.raw_info.api_version);
                    print_msg(MSG_INFO, "      height           %d\n", block_hdr.raw_info.height);
                    print_msg(MSG_INFO, "      width            %d\n", block_hdr.raw_info.width);
                    print_msg(MSG_INFO, "      pitch            %d\n", block_hdr.raw_info.pitch);
                    print_msg(MSG_INFO, "      frame_size       0x%08X\n", block_hdr.raw_info.frame_size);
                    print_msg(MSG_INFO, "      bits_per_pixel   %d\n", block_hdr.raw_info.bits_per_pixel);
                    print_msg(MSG_INFO, "      black_level      %d\n", block_hdr.raw_info.black_level);
                    print_msg(MSG_INFO, "      white_level      %d\n", block_hdr.raw_info.white_level);
                    print_msg(MSG_INFO, "      active_area.y1   %d\n", block_hdr.raw_info.active_area.y1);
                    print_msg(MSG_INFO, "      active_area.x1   %d\n", block_hdr.raw_info.active_area.x1);
                    print_msg(MSG_INFO, "      active_area.y2   %d\n", block_hdr.raw_info.active_area.y2);
                    print_msg(MSG_INFO, "      active_area.x2   %d\n", block_hdr.raw_info.active_area.x2);
                    print_msg(MSG_INFO, "      exposure_bias    %d, %d\n", block_hdr.raw_info.exposure_bias[0], block_hdr.raw_info.exposure_bias[1]);
                    print_msg(MSG_INFO, "      cfa_pattern      0x%08X\n", block_hdr.raw_info.cfa_pattern);
                    print_msg(MSG_INFO, "      calibration_ill  %d\n", block_hdr.raw_info.calibration_illuminant1);
                }

                /* cache these bits when we convert to legacy or resample bit depth */
                //if(raw_output || bit_depth)
                {
                    strncpy((char*)lv_rec_footer.magic, "RAWM", 4);
                    lv_rec_footer.xRes = block_hdr.xRes;
                    lv_rec_footer.yRes = block_hdr.yRes;
                    lv_rec_footer.raw_info = block_hdr.raw_info;
                }

                /* always output RAWI blocks, its not just metadata, but important frame format data */
                if(mlv_output && (!extract_block || !strncasecmp(extract_block, (char*)&block_hdr.blockType, 4)))
                {
                    /* correct header size if needed */
                    block_hdr.blockSize = sizeof(mlv_rawi_hdr_t);

                    if(bit_depth)
                    {
                        block_hdr.raw_info.bits_per_pixel = bit_depth;
                    }

                    if(fwrite(&block_hdr, block_hdr.blockSize, 1, out_file) != 1)
                    {
                        print_msg(MSG_ERROR, "Failed writing into .MLV file\n");
                        goto abort;
                    }
                }
            }
            else if(!memcmp(buf.blockType, "WAVI", 4))
            {
                mlv_wavi_hdr_t block_hdr;
                uint32_t hdr_size = MIN(sizeof(mlv_wavi_hdr_t), buf.blockSize);

                if(fread(&block_hdr, hdr_size, 1, in_file) != 1)
                {
                    print_msg(MSG_ERROR, "File ends in the middle of a block\n");
                    goto abort;
                }

                /* skip remaining data, if there is any */
                file_set_pos(in_file, position + block_hdr.blockSize, SEEK_SET);

                lua_handle_hdr(lua_state, buf.blockType, &block_hdr, sizeof(block_hdr));

                if(verbose)
                {
                    print_msg(MSG_INFO, "    wav_info:\n");
                    print_msg(MSG_INFO, "      format           %d\n", block_hdr.format);
                    print_msg(MSG_INFO, "      channels         %d\n", block_hdr.channels);
                    print_msg(MSG_INFO, "      samplingRate     %d\n", block_hdr.samplingRate);
                    print_msg(MSG_INFO, "      bytesPerSecond   %d\n", block_hdr.bytesPerSecond);
                    print_msg(MSG_INFO, "      blockAlign       %d\n", block_hdr.blockAlign);
                    print_msg(MSG_INFO, "      bitsPerSample    %d\n", block_hdr.bitsPerSample);
                }

                memcpy(&wavi_info, &block_hdr, sizeof(mlv_wavi_hdr_t));

                if(mlv_output && !no_metadata_mode && (!extract_block || !strncasecmp(extract_block, (char*)block_hdr.blockType, 4)))
                {
                    /* correct header size if needed */
                    block_hdr.blockSize = sizeof(mlv_wavi_hdr_t);
                    if(fwrite(&block_hdr, block_hdr.blockSize, 1, out_file) != 1)
                    {
                        print_msg(MSG_ERROR, "Failed writing into .MLV file\n");
                        goto abort;
                    }
                }
                
                if(output_filename && out_file_wav == NULL && !extract_block)
                {
                    size_t name_len = strlen(output_filename) + 5;  // + .wav\0
                    char* wav_file_name = malloc(name_len);
                    /* NOTE, assumes little endian system, fix for big endian */
                    uint32_t tmp_uint32;
                    uint16_t tmp_uint16;

                    strncpy(wav_file_name, output_filename, name_len);
                    strncat(wav_file_name, ".wav", name_len);
                    out_file_wav = fopen(wav_file_name, "wb");
                    free(wav_file_name);
                    if(!out_file_wav)
                    {
                        print_msg(MSG_ERROR, "Failed writing into audio output file\n");
                        goto abort;
                    }

                    int error = 1;
                    
                    /* Write header */
                    error &= fwrite("RIFF", 4, 1, out_file_wav);
                    tmp_uint32 = 36; // Two headers combined size, will be patched later
                    error &= fwrite(&tmp_uint32, 4, 1, out_file_wav);
                    error &= fwrite("WAVE", 4, 1, out_file_wav);

                    error &= fwrite("fmt ", 4, 1, out_file_wav);
                    tmp_uint32 = 16; // Header size
                    error &= fwrite(&tmp_uint32, 4, 1, out_file_wav);
                    tmp_uint16 = wavi_info.format; // PCM
                    error &= fwrite(&tmp_uint16, 2, 1, out_file_wav);
                    tmp_uint16 = wavi_info.channels; // Stereo
                    error &= fwrite(&tmp_uint16, 2, 1, out_file_wav);
                    tmp_uint32 = wavi_info.samplingRate; // Sample rate
                    error &= fwrite(&tmp_uint32, 4, 1, out_file_wav);
                    tmp_uint32 = wavi_info.bytesPerSecond; // Byte rate (16-bit data, stereo)
                    error &= fwrite(&tmp_uint32, 4, 1, out_file_wav);
                    tmp_uint16 = wavi_info.blockAlign; // Block align
                    error &= fwrite(&tmp_uint16, 2, 1, out_file_wav);
                    tmp_uint16 = wavi_info.bitsPerSample; // Bits per sample
                    error &= fwrite(&tmp_uint16, 2, 1, out_file_wav);

                    error &= fwrite("data", 4, 1, out_file_wav);
                    tmp_uint32 = 0; // Audio data length, will be patched later
                    error &= fwrite(&tmp_uint32, 4, 1, out_file_wav);

                    wav_file_size = 0;
                    wav_header_size = file_get_pos(out_file_wav);
                    
                    if(error != 1)
                    {
                        print_msg(MSG_ERROR, "Failed writing into .WAV file\n");
                        goto abort;
                    }
                }
            }
            else if(!memcmp(buf.blockType, "NULL", 4))
            {
                file_set_pos(in_file, position + buf.blockSize, SEEK_SET);
            }
            else if(!memcmp(buf.blockType, "BKUP", 4))
            {
                file_set_pos(in_file, position + buf.blockSize, SEEK_SET);
            }
            else
            {
                print_msg(MSG_INFO, "Unknown Block: %c%c%c%c, skipping\n", buf.blockType[0], buf.blockType[1], buf.blockType[2], buf.blockType[3]);
                
                if(fix_bug == BUG_ID_BLOCKSIZE_WRONG)
                {
                    char type[5];
                    uint32_t range = 0x80;
                    
                    type[4] = '\000';
                    print_msg(MSG_INFO, "BUG_ID_BLOCKSIZE_WRONG: Invalid block at 0x%08" PRIx64 ", trying to fix it\n", position);
                    position += range / 2;
                    
                    for(uint32_t offset = 0; offset < range; offset++)
                    {
                        file_set_pos(in_file, position, SEEK_SET);
                        
                        if(fread(&type, 4, 1, in_file) != 1)
                        {
                            print_msg(MSG_ERROR, "BUG_ID_BLOCKSIZE_WRONG: Failed to read from source file\n");
                            goto abort;
                        }
                        
                        if(!memcmp(type, "NULL", 4))
                        {
                            fix_bug_1_offset = -(offset - range / 2);
                            print_msg(MSG_INFO, "BUG_ID_BLOCKSIZE_WRONG: Success, offset: %d bytes.\n", fix_bug_1_offset);
                            file_set_pos(in_file, position_previous, SEEK_SET);
                            position = position_previous;
                            break;
                        }
                        position--;
                    }
                    if(memcmp(type, "NULL", 4))
                    {
                        print_msg(MSG_ERROR, "BUG_ID_BLOCKSIZE_WRONG: Failed to fix\n");
                        goto abort;
                    }
                }
                else
                {
                    file_set_pos(in_file, position + buf.blockSize, SEEK_SET);

                    lua_handle_hdr(lua_state, buf.blockType, "", 0);
                }
            }
        }

        /* count any read block, no matter if header or video frame */
        blocks_processed++;


        if(block_xref)
        {
            block_xref_pos++;
            if(block_xref_pos >= block_xref->entryCount)
            {
                print_msg(MSG_INFO, "Reached end of all files after %i blocks\n", blocks_processed);
                break;
            }
        }
        
        position_previous = position;
    }
    while(!feof(in_file));

abort:

    print_msg(MSG_INFO, "Processed %d video frames\n", vidf_frames_processed);

    /* in average mode, finalize average calculation and output the resulting average */
    if(average_mode)
    {
        if(!average_samples)
        {
            print_msg(MSG_ERROR, "Number of averaged frames is zero. Cannot continue.\n");
        }
        else
        {
            int new_pitch = video_xRes * lv_rec_footer.raw_info.bits_per_pixel / 8;
            
            /* average the pixels in vertical direction, so we will extract vertical banding noise */
            if(average_vert)
            {
                for(int x = 0; x < video_xRes; x++)
                {
                    uint64_t column = 0;
                    
                    for(int y = 0; y < video_yRes; y++)
                    {
                        column += frame_arith_buffer[y * video_xRes + x];
                    }
                    column /= video_yRes;
                    for(int y = 0; y < video_yRes; y++)
                    {
                        frame_arith_buffer[y * video_xRes + x] = column;
                    }
                }
            }
            if(average_hor)
            {
                for(int y = 0; y < video_yRes; y++)
                {
                    uint64_t line = 0;
                    
                    for(int x = 0; x < video_xRes; x++)
                    {
                        line += frame_arith_buffer[y * video_xRes + x];
                    }
                    line /= video_yRes;
                    for(int x = 0; x < video_xRes; x++)
                    {

                        frame_arith_buffer[y * video_xRes + x] = line;
                    }
                }
            }
            
            for(int y = 0; y < video_yRes; y++)
            {
                uint16_t *dst_line = (uint16_t *)&frame_buffer[y * new_pitch];
                for(int x = 0; x < video_xRes; x++)
                {
                    uint32_t value = frame_arith_buffer[y * video_xRes + x];

                    value /= average_samples;
                    bitinsert(dst_line, x, lv_rec_footer.raw_info.bits_per_pixel, value);
                }
            }
            

            int frame_size = ((video_xRes * video_yRes * lv_rec_footer.raw_info.bits_per_pixel + 7) / 8);

            mlv_vidf_hdr_t hdr;

            memset(&hdr, 0x00, sizeof(mlv_vidf_hdr_t));
            memcpy(hdr.blockType, "VIDF", 4);
            hdr.blockSize = sizeof(mlv_vidf_hdr_t) + frame_size;
            hdr.frameNumber = 0;
            hdr.timestamp = last_vidf.timestamp;

            if(fwrite(&hdr, sizeof(mlv_vidf_hdr_t), 1, out_file) != 1)
            {
                print_msg(MSG_ERROR, "Failed writing average frame header into .MLV file\n");
            }
            if(fwrite(frame_buffer, frame_size, 1, out_file) != 1)
            {
                print_msg(MSG_ERROR, "Failed writing average frame data into .MLV file\n");
            }
        }
    }

    if(raw_output)
    {
        lv_rec_footer.frameCount = vidf_max_number + 1;
        lv_rec_footer.raw_info.bits_per_pixel = 14;

        file_set_pos(out_file, 0, SEEK_END);
        if(fwrite(&lv_rec_footer, sizeof(lv_rec_file_footer_t), 1, out_file) != 1)
        {
            print_msg(MSG_ERROR, "Failed writing into .RAW file\n");
        }
    }

    if(xref_mode)
    {
        print_msg(MSG_INFO, "XREF table contains %d entries\n", frame_xref_entries);
        xref_sort(frame_xref_table, frame_xref_entries);
        save_index(input_filename, &main_header, in_file_count, frame_xref_table, frame_xref_entries);
    }

    /* fix frame count */
    if(mlv_output && !extract_block)
    {
        /* get extension and set fileNum in header to zero if its a .MLV */
        char *dot = strrchr(output_filename, '.');
        if(dot)
        {
            dot++;
            if(!strcasecmp(dot, "mlv"))
            {
                main_header.fileNum = 0;
                main_header.fileCount = 1;
            }
        }
        
        main_header.videoFrameCount = vidf_frames_processed;
        main_header.audioFrameCount = audf_frames_processed;

        fseek(out_file, 0L, SEEK_SET);
        
        if(fwrite(&main_header, main_header.blockSize, 1, out_file) != 1)
        {
            print_msg(MSG_ERROR, "Failed to rewrite header in .MLV file\n");
        }
    }
    
    
    /* free list of input files */
    for(in_file_num = 0; in_file_num < in_file_count; in_file_num++)
    {
        fclose(in_files[in_file_num]);
    }
    free(in_files);

    if(out_file)
    {
        fclose(out_file);
    }

    if(out_file_wav)
    {
        /* Patch the WAV size fields */
        uint32_t tmp_uint32 = wav_file_size + 36; /* + header size */
        file_set_pos(out_file_wav, 4, SEEK_SET);
        if(fwrite(&tmp_uint32, 4, 1, out_file_wav) != 1)
        {
            print_msg(MSG_ERROR, "Failed writing into .WAV file\n");
        }

        tmp_uint32 = wav_file_size; /* data size */
        file_set_pos(out_file_wav, 40, SEEK_SET);
        if(fwrite(&tmp_uint32, 4, 1, out_file_wav) != 1)
        {
            print_msg(MSG_ERROR, "Failed writing into .WAV file\n");
        }
        fclose(out_file_wav);
    }

    /* passing NULL to free is absolutely legal, so no check required */
    free(lut_filename);
    free(subtract_filename);
    free(output_filename);
    free(prev_frame_buffer);
    free(frame_arith_buffer);
    free(block_xref);

    print_msg(MSG_INFO, "Done\n");
    print_msg(MSG_INFO, "\n");

    return ERR_OK;
}
back to top