/*
 *  storyboard    generate a storyboard from a movie
 *  Copyright (c) 2009 Sam Hocevar <sam@zoy.org>
 *                All Rights Reserved
 *
 *  $Id$
 *
 *  This program is free software. It comes without any warranty, to
 *  the extent permitted by applicable law. You can redistribute it
 *  and/or modify it under the terms of the Do What The Fuck You Want
 *  To Public License, Version 2, as published by Sam Hocevar. See
 *  http://sam.zoy.org/wtfpl/COPYING for more details.
 */

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>

#include <pipi.h>

#define STEP 12

#define TWIDTH 90
#define THEIGHT 60
#define TCOLS 10
#define TROWS 50
#define NTHUMBS (TCOLS*TROWS)

static int similar(uint8_t *img1, uint8_t *img2);
static void decorate(uint8_t *img);

int main(int argc, char *argv[])
{
    char fmtstr[1024];
    AVPacket packet;
    AVFormatContext *fmt;
    AVCodecContext *ctx;
    AVCodec *codec;
    AVFrame *frame;
    struct SwsContext *sws = NULL;
    pipi_image_t *image;
    pipi_pixels_t *p;
    uint8_t *buffer;
    char *parser;
    int stream, i, n = 0, k = 0, idx = 0;

    if(argc < 2)
        return EXIT_FAILURE;

    parser = strrchr(argv[1], '/');
    strcpy(fmtstr, parser ? parser + 1 : argv[1]);
    parser = strrchr(fmtstr, '.');
    if(parser)
        *parser = '\0';
    strcat(fmtstr, ".t%02i.jpeg");

    image = pipi_new(TWIDTH * TCOLS, THEIGHT * TROWS);
    p = pipi_get_pixels(image, PIPI_PIXELS_RGBA_U8);
    buffer = (uint8_t *)p->pixels;

    av_register_all();
    if(av_open_input_file(&fmt, argv[1], NULL, 0, NULL) != 0)
        return EXIT_FAILURE;
    if(av_find_stream_info(fmt) < 0 )
        return EXIT_FAILURE;

    stream = -1;
    for(i = 0; stream == -1 && i < (int)fmt->nb_streams; i++)
        if(fmt->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO)
            stream = i;
    if(stream == -1)
        return EXIT_FAILURE;
    ctx = fmt->streams[stream]->codec;

    codec = avcodec_find_decoder(ctx->codec_id);
    if(codec == NULL)
        return EXIT_FAILURE;
    if(avcodec_open(ctx, codec) < 0)
        return EXIT_FAILURE;

    frame = avcodec_alloc_frame();

    for(;;)
    {
        int finished, ret;

        ret = av_read_frame(fmt, &packet);

        if(idx == NTHUMBS || (idx > 0 && ret < 0))
        {
            char buf[1024];
            sprintf(buf, fmtstr, k++);
            printf("saving %i thumbs in %s\n", idx, buf);
            pipi_save(image, buf);
            memset(buffer, 0, TWIDTH * TCOLS * THEIGHT * TROWS * 4);
            idx = 0;
        }

        if(ret < 0)
            break;

        if(packet.stream_index != stream)
        {
            av_free_packet(&packet);
            continue;
        }

        avcodec_decode_video(ctx, frame, &finished, packet.data, packet.size);
        if(!finished)
        {
            av_free_packet(&packet);
            continue;
        }

        /* Only process every 20th image */
        if((++n % STEP) == STEP / 2)
        {
            uint8_t *start;
            int pitch = TWIDTH * TCOLS * 4;
            int good = 1;

            start = buffer + (idx % TCOLS) * TWIDTH * 4
                           + (idx / TCOLS) * TWIDTH * TCOLS * 4 * THEIGHT;

            if(!sws)
                sws = sws_getContext(ctx->width, ctx->height, ctx->pix_fmt,
                                     TWIDTH, THEIGHT, PIX_FMT_RGB32,
                                     SWS_BICUBIC, NULL, NULL, NULL);

            sws_scale(sws, frame->data, frame->linesize, 0, ctx->height,
                      &start, &pitch);

            decorate(start);

            if(idx > 0)
            {
                uint8_t *prev;

                if(idx % TCOLS)
                    prev = start - TWIDTH * 4;
                else
                    prev = start + (TCOLS - 1) * TWIDTH * 4
                                 - TWIDTH * TCOLS * 4 * THEIGHT;

                /* Now check whether the new image is really different
                 * from the previous one (> 10% pixel changes) */
                if(similar(start, prev))
                    good = 0;
            }

            if(good)
            {
                idx++;
            }
        }

        av_free_packet(&packet);
    }

    return EXIT_SUCCESS;
}

static int similar(uint8_t *img1, uint8_t *img2)
{
    int x, y, t, a, b, changed = 0;

    for(y = 2; y < THEIGHT - 2; y++)
        for(x = 2; x < TWIDTH - 2; x++)
        {
            int offset = y * TWIDTH * TCOLS + x;
            int ok = 0;

            for(t = 0; t < 3; t++)
            {
                a = 2 * img1[offset * 4 + t];
                a += img1[(offset - TWIDTH * TCOLS - 1) * 4 + t];
                a += img1[(offset - TWIDTH * TCOLS) * 4 + t];
                a += img1[(offset - TWIDTH * TCOLS + 1) * 4 + t];
                a += img1[(offset - 1) * 4 + t];
                a += img1[(offset + 1) * 4 + t];
                a += img1[(offset + TWIDTH * TCOLS - 1) * 4 + t];
                a += img1[(offset + TWIDTH * TCOLS) * 4 + t];
                a += img1[(offset + TWIDTH * TCOLS + 1) * 4 + t];
                a /= 10;

                b = 2 * img2[offset * 4 + t];
                b += img2[(offset - TWIDTH * TCOLS - 1) * 4 + t];
                b += img2[(offset - TWIDTH * TCOLS) * 4 + t];
                b += img2[(offset - TWIDTH * TCOLS + 1) * 4 + t];
                b += img2[(offset - 1) * 4 + t];
                b += img2[(offset + 1) * 4 + t];
                b += img2[(offset + TWIDTH * TCOLS - 1) * 4 + t];
                b += img2[(offset + TWIDTH * TCOLS) * 4 + t];
                b += img2[(offset + TWIDTH * TCOLS + 1) * 4 + t];
                b /= 10;

                if(a < b - 8 || a > b + 8)
                {
                    ok = 1;
                    break;
                }
            }

            changed += ok;
        }

    return changed < (TWIDTH * THEIGHT * 10 / 100);
}

static void decorate(uint8_t *img)
{
    static int const hi = 200;
    static int const lo = 50;
    static int const mid = 127;
    int x, y;

    for(y = 0; y < THEIGHT; y++)
    {
        img[(y * TWIDTH * TCOLS) * 4] = hi;
        img[(y * TWIDTH * TCOLS) * 4 + 1] = hi;
        img[(y * TWIDTH * TCOLS) * 4 + 2] = hi;
        img[(y * TWIDTH * TCOLS + TWIDTH - 1) * 4] = lo;
        img[(y * TWIDTH * TCOLS + TWIDTH - 1) * 4 + 1] = lo;
        img[(y * TWIDTH * TCOLS + TWIDTH - 1) * 4 + 2] = lo;
    }

    for(x = 0; x < TWIDTH; x++)
    {
        img[x * 4] = hi;
        img[x * 4 + 1] = hi;
        img[x * 4 + 2] = hi;
        img[((THEIGHT - 1) * TWIDTH * TCOLS + x) * 4] = lo;
        img[((THEIGHT - 1) * TWIDTH * TCOLS + x) * 4 + 1] = lo;
        img[((THEIGHT - 1) * TWIDTH * TCOLS + x) * 4 + 2] = lo;
    }

    img[0] = (mid + hi) / 2;
    img[1] = (mid + hi) / 2;
    img[2] = (mid + hi) / 2;

    img[(TWIDTH - 1) * 4 + 0] = mid;
    img[(TWIDTH - 1) * 4 + 1] = mid;
    img[(TWIDTH - 1) * 4 + 2] = mid;

    img[((THEIGHT - 1) * TWIDTH * TCOLS) * 4 + 0] = mid;
    img[((THEIGHT - 1) * TWIDTH * TCOLS) * 4 + 1] = mid;
    img[((THEIGHT - 1) * TWIDTH * TCOLS) * 4 + 2] = mid;

    img[((THEIGHT - 1) * TWIDTH * TCOLS + TWIDTH - 1) * 4 + 0] = (mid + lo) / 2;
    img[((THEIGHT - 1) * TWIDTH * TCOLS + TWIDTH - 1) * 4 + 1] = (mid + lo) / 2;
    img[((THEIGHT - 1) * TWIDTH * TCOLS + TWIDTH - 1) * 4 + 2] = (mid + lo) / 2;
}