/* * storyboard generate a storyboard from a movie * Copyright (c) 2009 Sam Hocevar * 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 #include #include #include #include #include #include #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; break; } 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_video2(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, (uint8_t const **)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; }