[SCM] nordlicht/master: New upstream version 0.4.5

cinemast-guest at users.alioth.debian.org cinemast-guest at users.alioth.debian.org
Fri Jan 13 22:45:51 UTC 2017


The following commit has been merged in the master branch:
commit 1e7979dd4fb3354059fe0b839a007e88bc541d92
Author: Peter Spiess-Knafl <dev at spiessknafl.at>
Date:   Fri Jan 13 23:28:27 2017 +0100

    New upstream version 0.4.5

diff --git a/CHANGELOG.md b/CHANGELOG.md
index baed063..3973ae6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,16 @@
 All notable changes to this project will be documented in this file.
 *nordlicht* uses [Semantic Versioning](http://semver.org/).
 
+## 0.4.5 - 2017-01-09
+
+- introduce `nordlicht_generate_step` and `nordlicht_done` to the API, for simple, non-blocking applications
+- improve FFmpeg backwards compability down to v3.0 (thanks, Manuel!)
+- fix build on ARM platforms
+- correctly open files which contain a colon (thanks, Roland!)
+- various bug fixes and stability improvements
+
+- tool: when the user specifies a non-PNG file as output, warn them but don't fail
+
 ## 0.4.4 - 2016-01-24
 
 - introduce the convention that the nordlicht is always written to `VIDEOFILE.nordlicht.png`
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ad3eb00..c181d90 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 2.8)
 project(nordlicht C)
 set(MAJOR_VERSION 0)
 set(MINOR_VERSION 4)
-set(PATCH_VERSION 4)
+set(PATCH_VERSION 5)
 set(NORDLICHT_VERSION "${MAJOR_VERSION}.${MINOR_VERSION}.${PATCH_VERSION}")
 
 option (BUILD_SHARED_LIBS "Build shared libraries." ON)
@@ -12,6 +12,8 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/")
 
 include(GNUInstallDirs)
 
+set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -DDEBUG")
+
 if (BUILD_SHARED_LIBS)
     add_definitions(-DNORDLICHT_BUILD_SHARED)
     set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed")
@@ -20,7 +22,6 @@ else()
     set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static")
 endif()
 
-find_package(Threads REQUIRED)
 find_package(FFmpeg COMPONENTS AVUTIL AVFORMAT AVCODEC SWSCALE REQUIRED)
 find_package(Popt REQUIRED)
 
@@ -33,22 +34,22 @@ endif()
 
 include_directories(${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR} ${FFMPEG_INCLUDE_DIRS} ${POPT_INCLUDES})
 
-add_library(libnordlicht error.c image.c nordlicht.c source.c)
+add_library(libnordlicht src/error.c src/image.c src/nordlicht.c src/source.c)
 set_target_properties(libnordlicht PROPERTIES OUTPUT_NAME nordlicht)
 set_target_properties(libnordlicht PROPERTIES SOVERSION ${MAJOR_VERSION} VERSION ${NORDLICHT_VERSION})
 set_target_properties(libnordlicht PROPERTIES C_VISIBILITY_PRESET hidden)
 target_link_libraries(libnordlicht m ${FFMPEG_LIBRARIES})
 
-add_executable(nordlicht main.c)
-target_link_libraries(nordlicht libnordlicht ${POPT_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
+add_executable(nordlicht src/main.c)
+target_link_libraries(nordlicht libnordlicht ${POPT_LIBRARIES})
 if (WIN32)
     target_link_libraries(nordlicht mman)
 endif()
 
-add_executable(testsuite testsuite.c)
+add_executable(testsuite src/testsuite.c)
 target_link_libraries(testsuite libnordlicht)
 
-configure_file(version.h.in version.h @ONLY)
+configure_file(src/version.h.in version.h @ONLY)
 
 if (NOT WIN32)
     configure_file(cmake/nordlicht.pc.in nordlicht.pc @ONLY)
@@ -60,10 +61,10 @@ endif()
 
 install(TARGETS libnordlicht DESTINATION ${CMAKE_INSTALL_LIBDIR})
 install(TARGETS nordlicht DESTINATION bin)
-install(FILES nordlicht.h DESTINATION include)
+install(FILES ${CMAKE_SOURCE_DIR}/src/nordlicht.h DESTINATION include)
 
 if (NOT WIN32)
-		install(FILES ${CMAKE_BINARY_DIR}/nordlicht.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
+    install(FILES ${CMAKE_BINARY_DIR}/nordlicht.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
     install(FILES ${CMAKE_BINARY_DIR}/nordlicht.1 DESTINATION share/man/man1)
 endif()
 
diff --git a/README.md b/README.md
index 221b14b..9d13d0a 100644
--- a/README.md
+++ b/README.md
@@ -21,4 +21,4 @@ You can also help by packaging the software for your favorite operating system,
 
 ## License: GPLv2+
 
-See [LICENSE.md] for details.
+See [LICENSE.md](LICENSE.md) for details.
diff --git a/cmake/download_testfile.cmake b/cmake/download_testfile.cmake
index 49d69b6..aea9108 100644
--- a/cmake/download_testfile.cmake
+++ b/cmake/download_testfile.cmake
@@ -1,4 +1,4 @@
 if(NOT EXISTS ${CMAKE_BINARY_DIR}/video.mp4)
-    message("Downloading 20 MB test file, just a sec...")
-    file(DOWNLOAD https://upload.wikimedia.org/wikipedia/commons/7/79/Big_Buck_Bunny_small.ogv ${CMAKE_BINARY_DIR}/video.mp4 EXPECTED_MD5 9f701e645fd55e1ae8d35b7671002881)
+    message("Downloading 3 MB test file, just a sec...")
+    file(DOWNLOAD https://upload.wikimedia.org/wikipedia/commons/0/07/Sintel_excerpt.OGG ${CMAKE_BINARY_DIR}/video.mp4 EXPECTED_MD5 be22cb1f83f6f3620d8048df12f9f52d)
 endif()
diff --git a/nordlicht.c b/nordlicht.c
deleted file mode 100644
index e696847..0000000
--- a/nordlicht.c
+++ /dev/null
@@ -1,331 +0,0 @@
-#include "nordlicht.h"
-#include <string.h>
-#include "error.h"
-#include "source.h"
-
-#ifdef _WIN32
-#define realpath(N,R) _fullpath((R),(N),_MAX_PATH)
-#endif
-
-typedef struct {
-    nordlicht_style style;
-    int height;
-} track;
-
-struct nordlicht {
-    int width, height;
-    const char *filename;
-    track *tracks;
-    int num_tracks;
-    unsigned char *data;
-
-    int owns_data;
-    int modifiable;
-    nordlicht_strategy strategy;
-    float progress;
-    source *source;
-};
-
-NORDLICHT_API size_t nordlicht_buffer_size(const nordlicht *n) {
-    return n->width * n->height * 4;
-}
-
-NORDLICHT_API nordlicht* nordlicht_init(const char *filename, const int width, const int height) {
-    if (width < 1 || height < 1) {
-        error("Dimensions must be positive (got %dx%d)", width, height);
-        return NULL;
-    }
-    nordlicht *n;
-    n = (nordlicht *) malloc(sizeof(nordlicht));
-
-    n->width = width;
-    n->height = height;
-    n->filename = filename;
-
-    n->data = (unsigned char *) calloc(nordlicht_buffer_size(n), 1);
-    if (n->data == 0) {
-        error("Not enough memory to allocate %d bytes", nordlicht_buffer_size(n));
-        return NULL;
-    }
-
-    n->owns_data = 1;
-
-    n->num_tracks = 1;
-    n->tracks = (track *) malloc(sizeof(track));
-    n->tracks[0].style = NORDLICHT_STYLE_HORIZONTAL;
-    n->tracks[0].height = n->height;
-
-    n->strategy = NORDLICHT_STRATEGY_FAST;
-    n->modifiable = 1;
-    n->progress = 0;
-    n->source = source_init(filename);
-
-    if (n->source == NULL) {
-        error("Could not open video file '%s'", filename);
-        free(n);
-        return NULL;
-    }
-
-    return n;
-}
-
-NORDLICHT_API void nordlicht_free(nordlicht *n) {
-    if (n->owns_data) {
-        free(n->data);
-    }
-    free(n->tracks);
-    source_free(n->source);
-    free(n);
-}
-
-NORDLICHT_API const char *nordlicht_error() {
-    return get_error();
-}
-
-NORDLICHT_API int nordlicht_set_start(nordlicht *n, const float start) {
-    if (! n->modifiable) {
-        return -1;
-    }
-
-    if (start < 0) {
-        error("'start' has to be >= 0.");
-        return -1;
-    }
-
-    if (start >= source_end(n->source)) {
-        error("'start' has to be smaller than 'end'.");
-        return -1;
-    }
-
-    source_set_start(n->source, start);
-    return 0;
-}
-
-NORDLICHT_API int nordlicht_set_end(nordlicht *n, const float end) {
-    if (! n->modifiable) {
-        return -1;
-    }
-
-    if (end > 1) {
-        error("'end' has to be <= 1.");
-        return -1;
-    }
-
-    if (source_start(n->source) >= end) {
-        error("'start' has to be smaller than 'end'.");
-        return -1;
-    }
-
-    source_set_end(n->source, end);
-    return 0;
-}
-
-NORDLICHT_API int nordlicht_set_style(nordlicht *n, const nordlicht_style style) {
-    if (! n->modifiable) {
-        return -1;
-    }
-
-    nordlicht_style styles[1] = {style};
-    return nordlicht_set_styles(n, styles, 1);
-}
-
-NORDLICHT_API int nordlicht_set_styles(nordlicht *n, const nordlicht_style *styles, const int num_tracks) {
-    if (! n->modifiable) {
-        return -1;
-    }
-
-    n->num_tracks = num_tracks;
-
-    if (n->num_tracks > n->height) {
-        error("Height of %d px is too low for %d styles", n->height, n->num_tracks);
-        return -1;
-    }
-
-    free(n->tracks);
-    n->tracks = (track *) malloc(n->num_tracks*sizeof(track));
-
-    int height_of_each_track = n->height/n->num_tracks;
-    int i;
-    for (i=0; i<num_tracks; i++) {
-        nordlicht_style s = styles[i];
-        if (s > NORDLICHT_STYLE_LAST-1) {
-            return -1;
-        }
-
-        n->tracks[i].style = s;
-        n->tracks[i].height = height_of_each_track;
-    }
-    n->tracks[0].height = n->height - (n->num_tracks-1)*height_of_each_track;
-
-    return 0;
-}
-
-NORDLICHT_API int nordlicht_set_strategy(nordlicht *n, const nordlicht_strategy s) {
-    if (! n->modifiable) {
-        return -1;
-    }
-    if (s > NORDLICHT_STRATEGY_LIVE) {
-        return -1;
-    }
-    n->strategy = s;
-    return 0;
-}
-
-NORDLICHT_API int nordlicht_generate(nordlicht *n) {
-    n->modifiable = 0;
-
-    source_build_keyframe_index(n->source, n->width);
-
-    int x, exact;
-
-    const int do_a_fast_pass = (n->strategy == NORDLICHT_STRATEGY_LIVE) || !source_exact(n->source);
-    const int do_an_exact_pass = source_exact(n->source);
-
-    for (exact = (!do_a_fast_pass); exact <= do_an_exact_pass; exact++) {
-        int i;
-        int y_offset = 0;
-        for(i = 0; i < n->num_tracks; i++) {
-            // call this for each track, to seek to the beginning
-            source_set_exact(n->source, exact);
-
-            for (x = 0; x < n->width; x++) {
-                image *frame;
-
-                if (n->tracks[i].style == NORDLICHT_STYLE_SPECTROGRAM) {
-                    if (!source_has_audio(n->source)) {
-                        error("File contains no audio, please select an appropriate style");
-                        n->progress = 1;
-                        return -1;
-                    }
-                    frame = source_get_audio_frame(n->source, 1.0*(x+0.5-COLUMN_PRECISION/2.0)/n->width,
-                            1.0*(x+0.5+COLUMN_PRECISION/2.0)/n->width);
-                } else {
-                    if (!source_has_video(n->source)) {
-                        error("File contains no video, please select an appropriate style");
-                        n->progress = 1;
-                        return -1;
-                    }
-                    frame = source_get_video_frame(n->source, 1.0*(x+0.5-COLUMN_PRECISION/2.0)/n->width,
-                            1.0*(x+0.5+COLUMN_PRECISION/2.0)/n->width);
-                }
-                if (frame == NULL) {
-                    continue;
-                }
-
-                int thumbnail_width = 1.0*(image_width(frame)*n->tracks[i].height/image_height(frame));
-                image *column = NULL;
-                image *tmp = NULL;
-                switch (n->tracks[i].style) {
-                    case NORDLICHT_STYLE_THUMBNAILS:
-                        column = image_scale(frame, thumbnail_width, n->tracks[i].height);
-                        break;
-                    case NORDLICHT_STYLE_HORIZONTAL:
-                        column = image_scale(frame, 1, n->tracks[i].height);
-                        break;
-                    case NORDLICHT_STYLE_VERTICAL:
-                        tmp = image_scale(frame, n->tracks[i].height, 1);
-                        column = image_flip(tmp);
-                        image_free(tmp);
-                        break;
-                    case NORDLICHT_STYLE_SLITSCAN:
-                        tmp = image_column(frame, 1.0*(x%thumbnail_width)/thumbnail_width);
-
-                        column = image_scale(tmp, 1, n->tracks[i].height);
-                        image_free(tmp);
-                        break;
-                    case NORDLICHT_STYLE_MIDDLECOLUMN:
-                        tmp = image_column(frame, 0.5);
-                        column = image_scale(tmp, 1, n->tracks[i].height);
-                        image_free(tmp);
-                        break;
-                    case NORDLICHT_STYLE_SPECTROGRAM:
-                        column = image_scale(frame, 1, n->tracks[i].height);
-                        break;
-                    default:
-                        // cannot happen (TM)
-                        return -1;
-                        break;
-                }
-
-                image_to_bgra(n->data, n->width, n->height, column, x, y_offset);
-
-                n->progress = (i+1.0*x/n->width)/n->num_tracks;
-                x = x + image_width(column) - 1;
-
-                image_free(column);
-            }
-
-            y_offset += n->tracks[i].height;
-        }
-    }
-
-    n->progress = 1.0;
-    return 0;
-}
-
-NORDLICHT_API int nordlicht_write(const nordlicht *n, const char *filename) {
-    int code = 0;
-
-    if (filename == NULL) {
-        error("Output filename must not be NULL");
-        return -1;
-    }
-
-    if (strcmp(filename, "") == 0) {
-        error("Output filename must not be empty");
-        return -1;
-    }
-
-    char *realpath_output = realpath(filename, NULL);
-    if (realpath_output != NULL) {
-        // output file exists
-        char *realpath_input = realpath(n->filename, NULL);
-        if (realpath_input != NULL) {
-            // otherwise, input filename is probably a URL
-
-            if (strcmp(realpath_input, realpath_output) == 0) {
-                error("Will not overwrite input file");
-                code = -1;
-            }
-            free(realpath_input);
-        }
-        free(realpath_output);
-
-        if (code != 0) {
-            return code;
-        }
-    }
-
-    image *i = image_from_bgra(n->data, n->width, n->height);
-    if (image_write_png(i, filename) != 0) {
-        return -1;
-    }
-    image_free(i);
-
-    return code;
-}
-
-NORDLICHT_API float nordlicht_progress(const nordlicht *n) {
-    return n->progress;
-}
-
-NORDLICHT_API const unsigned char* nordlicht_buffer(const nordlicht *n) {
-    return n->data;
-}
-
-NORDLICHT_API int nordlicht_set_buffer(nordlicht *n, unsigned char *data) {
-    if (! n->modifiable) {
-        return -1;
-    }
-
-    if (data == NULL) {
-        return -1;
-    }
-
-    if (n->owns_data) {
-        free(n->data);
-    }
-    n->owns_data = 0;
-    n->data = data;
-    return 0;
-}
diff --git a/cheat.h b/src/cheat.h
similarity index 100%
rename from cheat.h
rename to src/cheat.h
diff --git a/error.c b/src/error.c
similarity index 100%
rename from error.c
rename to src/error.c
diff --git a/error.h b/src/error.h
similarity index 100%
rename from error.h
rename to src/error.h
diff --git a/image.c b/src/image.c
similarity index 87%
rename from image.c
rename to src/image.c
index 9cb404a..89d9386 100644
--- a/image.c
+++ b/src/image.c
@@ -5,11 +5,36 @@
 #include <string.h>
 #include <libswscale/swscale.h>
 
+// Changes for ffmpeg 3.0
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57,24,0)
+#  include <libavutil/imgutils.h>
+#  define av_free_packet av_packet_unref
+#  define avpicture_get_size(fmt,w,h) av_image_get_buffer_size(fmt,w,h,1)
+#endif
+
+// PIX_FMT was renamed to AV_PIX_FMT on this version
+#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(51,74,100)
+#  define AVPixelFormat PixelFormat
+#  define AV_PIX_FMT_RGB24 PIX_FMT_RGB24
+#  define AV_PIX_FMT_YUVJ420P PIX_FMT_YUVJ420P
+#  define AV_PIX_FMT_YUVJ422P PIX_FMT_YUVJ422P
+#  define AV_PIX_FMT_YUVJ440P PIX_FMT_YUVJ440P
+#  define AV_PIX_FMT_YUVJ444P PIX_FMT_YUVJ444P
+#  define AV_PIX_FMT_YUV420P  PIX_FMT_YUV420P
+#  define AV_PIX_FMT_YUV422P  PIX_FMT_YUV422P
+#  define AV_PIX_FMT_YUV440P  PIX_FMT_YUV440P
+#  define AV_PIX_FMT_YUV444P  PIX_FMT_YUV444P
+#endif
+
 #if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(52, 8, 0)
-#define av_frame_alloc  avcodec_alloc_frame
-#define av_frame_free av_freep
+#  define av_frame_alloc  avcodec_alloc_frame
+#  if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(54,59,100)
+#    define av_frame_free   av_freep
+#  else
+#    define av_frame_free   avcodec_free_frame
+#  endif
 void av_frame_get_buffer(AVFrame *frame, int magic) { avpicture_alloc((AVPicture *)frame, frame->format, frame->width, frame->height); }
-void av_frame_copy(AVFrame *dst, AVFrame *src) { memcpy(dst->data[0], src->data[0], sizeof(uint8_t)*avpicture_get_size(PIX_FMT_RGB24, dst->width, dst->height)); }
+void av_frame_copy(AVFrame *dst, AVFrame *src) { memcpy(dst->data[0], src->data[0], sizeof(uint8_t)*avpicture_get_size(AV_PIX_FMT_RGB24, dst->width, dst->height)); }
 #endif
 
 #define MAX_FILTER_SIZE 256
@@ -25,7 +50,7 @@ image *image_init(const int width, const int height) {
     i->frame = (AVFrame *) av_frame_alloc();
     i->frame->width = width;
     i->frame->height = height;
-    i->frame->format = PIX_FMT_RGB24; // best choice?
+    i->frame->format = AV_PIX_FMT_RGB24; // best choice?
     av_frame_get_buffer(i->frame, 16); // magic number?
     return i;
 }
@@ -240,7 +265,9 @@ int image_write_png(const image *i, const char *file_path) {
     encoder_context = avcodec_alloc_context3(encoder);
     encoder_context->width = i->frame->width;
     encoder_context->height = i->frame->height;
-    encoder_context->pix_fmt = PIX_FMT_RGB24;
+    encoder_context->pix_fmt = AV_PIX_FMT_RGB24;
+    encoder_context->time_base.num = 1;
+    encoder_context->time_base.den = 1;
     if (avcodec_open2(encoder_context, encoder, NULL) < 0) {
         error("Could not open output codec.");
         return -1;
diff --git a/image.h b/src/image.h
similarity index 100%
rename from image.h
rename to src/image.h
diff --git a/main.c b/src/main.c
similarity index 81%
rename from main.c
rename to src/main.c
index b24f824..8a84419 100644
--- a/main.c
+++ b/src/main.c
@@ -1,4 +1,3 @@
-#include <pthread.h>
 #include <sys/file.h>
 #include <sys/mman.h>
 #include <stdarg.h>
@@ -69,9 +68,9 @@ void print_help(const poptContext popt, const int ret) {
 
     printf("\n\
 Examples:\n\
-  nordlicht video.mp4                                   generate video.mp4.png of 1000 x 100 pixels size\n\
-  nordlicht video.mp4 --style=vertical                  compress individual frames to columns\n\
-  nordlicht video.mp4 -w 1920 -h 200 -o barcode.png     override size and name of the output file\n");
+  nordlicht video.mp4                                   generate video.mp4.nordlicht.png of default size\n\
+  nordlicht video.mp4 -s vertical                       compress individual frames to columns\n\
+  nordlicht video.mp4 -w 1000 -h 1000 -o barcode.png    override size and name of the output file\n");
 
     exit(ret);
 }
@@ -95,8 +94,8 @@ int main(const int argc, const char **argv) {
         {"height", 'h', POPT_ARG_INT, &height, 0, "set the barcode's height; by default it's \"width/10\"", NULL},
         {"output", 'o', POPT_ARG_STRING, &output_file, 0, "set output filename, the default is VIDEOFILE.png; when you specify an *.bgra file, you'll get a raw 32-bit BGRA file that is updated as the barcode is generated", "FILENAME"},
         {"style", 's', POPT_ARG_STRING, &styles_string, 0, "default is 'horizontal', see \"Styles\" section below. You can specify more than one style, separated by '+', to get multiple tracks", "STYLE"},
-        {"start", '\0', POPT_ARG_FLOAT, &start, 0, "specify where to start the barcode (in percent between 0 and 1)", NULL},
-        {"end", '\0', POPT_ARG_FLOAT, &end, 0, "specify where to end the barcode (in percent between 0 and 1)", NULL},
+        {"start", '\0', POPT_ARG_FLOAT, &start, 0, "specify where to start the barcode (ratio between 0 and 1)", NULL},
+        {"end", '\0', POPT_ARG_FLOAT, &end, 0, "specify where to end the barcode (ratio between 0 and 1)", NULL},
         {"quiet", 'q', 0, &quiet, 0, "don't show progress indicator", NULL},
         {"help", '\0', 0, &help, 0, "display this help and exit", NULL},
         {"version", '\0', 0, &version, 0, "output version information and exit", NULL},
@@ -106,13 +105,17 @@ int main(const int argc, const char **argv) {
     const poptContext popt = poptGetContext(NULL, argc, argv, optionsTable, 0);
     poptSetOtherOptionHelp(popt, "[OPTION]... VIDEOFILE\n\nOptions:");
 
-    char c;
+    if (argc == 1) {
+        print_help(popt, 1);
+    }
+
+    int option;
 
-    // The next line leaks 3 bytes, blame popt!
-    while ((c = poptGetNextOpt(popt)) >= 0) { }
+    // The next line leaks 2 bytes, blame popt!
+    while ((option = poptGetNextOpt(popt)) >= 0) { }
 
-    if (c < -1) {
-        fprintf(stderr, "nordlicht: %s: %s\n", poptBadOption(popt, POPT_BADOPTION_NOALIAS), poptStrerror(c));
+    if (option < -1) {
+        fprintf(stderr, "nordlicht: %s: %s\n", poptBadOption(popt, POPT_BADOPTION_NOALIAS), poptStrerror(option));
         return 1;
     }
 
@@ -190,13 +193,13 @@ int main(const int argc, const char **argv) {
     }
 
     const char *ext = filename_ext(output_file);
-    if (strcmp(ext, "png") == 0) {
-        strategy = NORDLICHT_STRATEGY_FAST;
-    } else if (strcmp(ext, "bgra") == 0) {
+    if (strcmp(ext, "bgra") == 0) {
         strategy = NORDLICHT_STRATEGY_LIVE;
+    } else if (strcmp(ext, "png") == 0) {
+        strategy = NORDLICHT_STRATEGY_FAST;
     } else {
-        print_error("Unsupported file extension '%s'.", ext);
-        exit(1);
+        strategy = NORDLICHT_STRATEGY_FAST;
+        fprintf(stderr, "nordlicht: Unsupported file extension '%s', will write a PNG.\n", ext);
     }
 
     // Interesting stuff begins here!
@@ -225,7 +228,10 @@ int main(const int argc, const char **argv) {
             print_error("Could not open '%s'.", output_file);
             exit(1);
         }
-        ftruncate(fd, nordlicht_buffer_size(n));
+        if (ftruncate(fd, nordlicht_buffer_size(n)) == -1) {
+            print_error("Could not truncate '%s'.", output_file);
+            exit(1);
+        }
         data = (unsigned char *) mmap(NULL, nordlicht_buffer_size(n), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
         if (data == (void *) -1) {
             print_error("Could not mmap %d bytes.", nordlicht_buffer_size(n));
@@ -241,35 +247,35 @@ int main(const int argc, const char **argv) {
         }
     }
 
-    pthread_t thread;
-    pthread_create(&thread, NULL, (void*(*)(void*))nordlicht_generate, n);
-
-    if (! quiet) {
-        float progress = 0;
-
-        printf("nordlicht: Building keyframe index... ");
-        fflush(stdout);
-        while (progress == 0) {
-            progress = nordlicht_progress(n);
-            nanosleep((const struct timespec[]){{0, 100000000L}}, NULL);
-        }
-        printf("done.\n");
-
-        while (progress < 1) {
-            progress = nordlicht_progress(n);
-            printf("\rnordlicht: %02.0f%%", progress*100);
-            fflush(stdout);
-            nanosleep((const struct timespec[]){{0, 100000000L}}, NULL);
+    int phase = -1;
+    while(!nordlicht_done(n)) {
+        if (nordlicht_generate_step(n) == 0) {
+            if (! quiet) {
+                float progress = nordlicht_progress(n);
+                if (progress == 0) {
+                    if (phase == -1) {
+                        phase = 0;
+                        printf("nordlicht: Building keyframe index... ");
+                        fflush(stdout);
+                    }
+                } else {
+                    if (phase == 0) {
+                        phase = 1;
+                        printf("done.\n");
+                    }
+                    printf("\rnordlicht: %02.0f%%", progress*100);
+#ifdef DEBUG
+                    printf("\n");
+#endif
+                    fflush(stdout);
+                }
+            }
+        } else {
+            print_error(nordlicht_error());
+            exit(1);
         }
     }
 
-    pthread_join(thread, NULL);
-
-    if (nordlicht_error() != NULL) {
-        print_error(nordlicht_error());
-        exit(1);
-    }
-
     if (strategy != NORDLICHT_STRATEGY_LIVE) {
         if (nordlicht_write(n, output_file) != 0) {
             print_error(nordlicht_error());
diff --git a/src/nordlicht.c b/src/nordlicht.c
new file mode 100644
index 0000000..1199a45
--- /dev/null
+++ b/src/nordlicht.c
@@ -0,0 +1,385 @@
+#include "nordlicht.h"
+#include <string.h>
+#include "error.h"
+#include "source.h"
+
+#ifdef _WIN32
+#define realpath(N,R) _fullpath((R),(N),_MAX_PATH)
+#endif
+
+typedef struct {
+    nordlicht_style style;
+    int height;
+} track;
+
+struct nordlicht {
+    int width, height;
+    char *filename;
+    track *tracks;
+    int num_tracks;
+    unsigned char *data;
+
+    int owns_data;
+    int modifiable;
+    nordlicht_strategy strategy;
+    source *source;
+
+    int current_pass;
+    int current_track;
+    int current_column;
+    int current_y_offset;
+    float progress;
+};
+
+NORDLICHT_API size_t nordlicht_buffer_size(const nordlicht *n) {
+    return n->width * n->height * 4;
+}
+
+NORDLICHT_API nordlicht* nordlicht_init(const char *filename, const int width, const int height) {
+    if (width < 1 || height < 1) {
+        error("Dimensions must be positive (got %dx%d)", width, height);
+        return NULL;
+    }
+    if (width > 100000 || height > 100000) {
+        error("Both dimensions may be at most 100000 (got %dx%d)", width, height);
+        return NULL;
+    }
+    nordlicht *n;
+    n = (nordlicht *) malloc(sizeof(nordlicht));
+
+    n->width = width;
+    n->height = height;
+
+    // prepend "file:" if filename contains a colon and is not a URL,
+    // otherwise ffmpeg will fail to open the file
+    if (filename && strstr(filename, ":") != 0 && strstr(filename, "://") == 0) {
+        size_t filename_len = strlen(filename);
+        n->filename = malloc(filename_len + 5 + 1);
+        strncpy(n->filename, "file:", 5);
+        strncpy(n->filename + 5, filename, filename_len);
+        n->filename[filename_len + 5] = '\0';
+    } else {
+        n->filename = (char *) filename;
+    }
+
+    n->data = (unsigned char *) calloc(nordlicht_buffer_size(n), 1);
+    if (n->data == 0) {
+        error("Not enough memory to allocate %d bytes", nordlicht_buffer_size(n));
+        return NULL;
+    }
+
+    n->owns_data = 1;
+
+    n->num_tracks = 1;
+    n->tracks = (track *) malloc(sizeof(track));
+    n->tracks[0].style = NORDLICHT_STYLE_HORIZONTAL;
+    n->tracks[0].height = n->height;
+
+    n->strategy = NORDLICHT_STRATEGY_FAST;
+    n->modifiable = 1;
+    n->source = source_init(n->filename);
+
+    n->current_pass = -1;
+    n->current_track = 0;
+    n->current_column = 0;
+    n->current_y_offset = 0;
+    n->progress = 0;
+
+    if (n->source == NULL) {
+        error("Could not open video file '%s'", filename);
+        free(n);
+        return NULL;
+    }
+
+    return n;
+}
+
+NORDLICHT_API void nordlicht_free(nordlicht *n) {
+    if (n->owns_data) {
+        free(n->data);
+    }
+    free(n->tracks);
+    source_free(n->source);
+    free(n);
+}
+
+NORDLICHT_API const char *nordlicht_error() {
+    return get_error();
+}
+
+NORDLICHT_API int nordlicht_set_start(nordlicht *n, const float start) {
+    if (! n->modifiable) {
+        return -1;
+    }
+
+    if (start < 0) {
+        error("'start' has to be greater than or equal to 0.");
+        return -1;
+    }
+
+    if (start >= source_end(n->source)) {
+        error("'start' has to be less than 'end'.");
+        return -1;
+    }
+
+    source_set_start(n->source, start);
+    return 0;
+}
+
+NORDLICHT_API int nordlicht_set_end(nordlicht *n, const float end) {
+    if (! n->modifiable) {
+        return -1;
+    }
+
+    if (end > 1) {
+        error("'end' has to less than or equal to 1.");
+        return -1;
+    }
+
+    if (source_start(n->source) >= end) {
+        error("'start' has to be less than 'end'.");
+        return -1;
+    }
+
+    source_set_end(n->source, end);
+    return 0;
+}
+
+NORDLICHT_API int nordlicht_set_style(nordlicht *n, const nordlicht_style style) {
+    if (! n->modifiable) {
+        return -1;
+    }
+
+    nordlicht_style styles[1] = {style};
+    return nordlicht_set_styles(n, styles, 1);
+}
+
+NORDLICHT_API int nordlicht_set_styles(nordlicht *n, const nordlicht_style *styles, const int num_tracks) {
+    if (! n->modifiable) {
+        return -1;
+    }
+
+    n->num_tracks = num_tracks;
+
+    if (n->num_tracks > n->height) {
+        error("Height of %d px is too low for %d styles", n->height, n->num_tracks);
+        return -1;
+    }
+
+    free(n->tracks);
+    n->tracks = (track *) malloc(n->num_tracks*sizeof(track));
+
+    int height_of_each_track = n->height/n->num_tracks;
+    int i;
+    for (i=0; i<num_tracks; i++) {
+        nordlicht_style s = styles[i];
+        if (s > NORDLICHT_STYLE_LAST-1) {
+            return -1;
+        }
+
+        n->tracks[i].style = s;
+        n->tracks[i].height = height_of_each_track;
+    }
+    n->tracks[0].height = n->height - (n->num_tracks-1)*height_of_each_track;
+
+    return 0;
+}
+
+NORDLICHT_API int nordlicht_set_strategy(nordlicht *n, const nordlicht_strategy s) {
+    if (! n->modifiable) {
+        return -1;
+    }
+    if (s > NORDLICHT_STRATEGY_LIVE) {
+        return -1;
+    }
+    n->strategy = s;
+    return 0;
+}
+
+NORDLICHT_API int nordlicht_generate_step(nordlicht *n) {
+    n->modifiable = 0;
+
+    if (nordlicht_done(n)) {
+        return 0;
+    } else if (n->current_pass == -1) {
+        // we don't have a (finished) keyframe index yet
+        if (source_build_keyframe_index_step(n->source, n->width) == 0) {
+            // keyframe index building is done
+            if (n->strategy == NORDLICHT_STRATEGY_LIVE || !source_has_index(n->source)) {
+                n->current_pass = 0;
+            } else {
+                n->current_pass = 1;
+            }
+            source_set_exact(n->source, n->current_pass);
+        }
+    } else {
+        image *frame;
+
+        if (n->tracks[n->current_track].style == NORDLICHT_STYLE_SPECTROGRAM) {
+            if (!source_has_audio(n->source)) {
+                error("File contains no audio, please select an appropriate style");
+                n->progress = 1;
+                return -1;
+            }
+            frame = source_get_audio_frame(n->source, 1.0*(n->current_column+0.5-COLUMN_PRECISION/2.0)/n->width,
+                    1.0*(n->current_column+0.5+COLUMN_PRECISION/2.0)/n->width);
+        } else {
+            if (!source_has_video(n->source)) {
+                error("File contains no video, please select an appropriate style");
+                n->progress = 1;
+                return -1;
+            }
+            frame = source_get_video_frame(n->source, 1.0*(n->current_column+0.5-COLUMN_PRECISION/2.0)/n->width,
+                    1.0*(n->current_column+0.5+COLUMN_PRECISION/2.0)/n->width);
+        }
+
+        if (frame != NULL) {
+            int thumbnail_width = 1.0*(image_width(frame)*n->tracks[n->current_track].height/image_height(frame));
+            image *column = NULL;
+            image *tmp = NULL;
+            switch (n->tracks[n->current_track].style) {
+                case NORDLICHT_STYLE_THUMBNAILS:
+                    column = image_scale(frame, thumbnail_width, n->tracks[n->current_track].height);
+                    break;
+                case NORDLICHT_STYLE_HORIZONTAL:
+                    column = image_scale(frame, 1, n->tracks[n->current_track].height);
+                    break;
+                case NORDLICHT_STYLE_VERTICAL:
+                    tmp = image_scale(frame, n->tracks[n->current_track].height, 1);
+                    column = image_flip(tmp);
+                    image_free(tmp);
+                    break;
+                case NORDLICHT_STYLE_SLITSCAN:
+                    tmp = image_column(frame, 1.0*(n->current_column%thumbnail_width)/thumbnail_width);
+
+                    column = image_scale(tmp, 1, n->tracks[n->current_track].height);
+                    image_free(tmp);
+                    break;
+                case NORDLICHT_STYLE_MIDDLECOLUMN:
+                    tmp = image_column(frame, 0.5);
+                    column = image_scale(tmp, 1, n->tracks[n->current_track].height);
+                    image_free(tmp);
+                    break;
+                case NORDLICHT_STYLE_SPECTROGRAM:
+                    column = image_scale(frame, 1, n->tracks[n->current_track].height);
+                    break;
+                default:
+                    // cannot happen (TM)
+                    return -1;
+                    break;
+            }
+
+            image_to_bgra(n->data, n->width, n->height, column, n->current_column, n->current_y_offset);
+
+            n->current_column = n->current_column + image_width(column) - 1;
+            if (n->current_column >= n->width) {
+                n->current_column = n->width - 1;
+            }
+            n->progress = (n->current_track+1.0*n->current_column/n->width)/n->num_tracks;
+
+            image_free(column);
+        }
+
+        n->current_column++;
+        if (n->current_column == n->width) {
+            n->current_column = 0;
+            n->current_y_offset += n->tracks[n->current_track].height;
+            n->current_track++;
+            if (n->current_track == n->num_tracks) {
+                n->current_track = 0;
+                n->current_y_offset = 0;
+                n->current_pass++;
+                if (n->current_pass == 2 || !source_has_index(n->source)) {
+                    // we're done :)
+                    n->progress = 1.0;
+                    n->current_pass = 2;
+                    return 0;
+                } else {
+                    source_set_exact(n->source, n->current_pass);
+                }
+            }
+        }
+    }
+
+    return 0;
+}
+
+NORDLICHT_API int nordlicht_generate(nordlicht *n) {
+    while(!nordlicht_done(n)) {
+        if (nordlicht_generate_step(n) != 0) {
+            return -1;
+        }
+    }
+    return 0;
+}
+
+NORDLICHT_API int nordlicht_write(const nordlicht *n, const char *filename) {
+    int code = 0;
+
+    if (filename == NULL) {
+        error("Output filename must not be NULL");
+        return -1;
+    }
+
+    if (strcmp(filename, "") == 0) {
+        error("Output filename must not be empty");
+        return -1;
+    }
+
+    char *realpath_output = realpath(filename, NULL);
+    if (realpath_output != NULL) {
+        // output file exists
+        char *realpath_input = realpath(n->filename, NULL);
+        if (realpath_input != NULL) {
+            // otherwise, input filename is probably a URL
+
+            if (strcmp(realpath_input, realpath_output) == 0) {
+                error("Will not overwrite input file");
+                code = -1;
+            }
+            free(realpath_input);
+        }
+        free(realpath_output);
+
+        if (code != 0) {
+            return code;
+        }
+    }
+
+    image *i = image_from_bgra(n->data, n->width, n->height);
+    if (image_write_png(i, filename) != 0) {
+        return -1;
+    }
+    image_free(i);
+
+    return code;
+}
+
+NORDLICHT_API int nordlicht_done(const nordlicht *n) {
+    return n->current_pass == 2;
+}
+
+NORDLICHT_API float nordlicht_progress(const nordlicht *n) {
+    return n->progress;
+}
+
+NORDLICHT_API const unsigned char* nordlicht_buffer(const nordlicht *n) {
+    return n->data;
+}
+
+NORDLICHT_API int nordlicht_set_buffer(nordlicht *n, unsigned char *data) {
+    if (! n->modifiable) {
+        return -1;
+    }
+
+    if (data == NULL) {
+        return -1;
+    }
+
+    if (n->owns_data) {
+        free(n->data);
+    }
+    n->owns_data = 0;
+    n->data = data;
+    return 0;
+}
diff --git a/nordlicht.h b/src/nordlicht.h
similarity index 58%
rename from nordlicht.h
rename to src/nordlicht.h
index e334f99..b5c6cde 100644
--- a/nordlicht.h
+++ b/src/nordlicht.h
@@ -2,7 +2,6 @@
 #define INCLUDE_nordlicht_h__
 #include <stdlib.h> // for size_t
 
-
 #ifndef NORDLICHT_API
 #  ifdef _WIN32
 #     if defined(NORDLICHT_BUILD_SHARED) /* build dll */
@@ -28,13 +27,13 @@ extern "C" {
 typedef struct nordlicht nordlicht;
 
 typedef enum nordlicht_style {
-    NORDLICHT_STYLE_THUMBNAILS,
-    NORDLICHT_STYLE_HORIZONTAL, // compress frames to columns, "move to the right"
-    NORDLICHT_STYLE_VERTICAL, // compress frames to rows, "move downwards"
+    NORDLICHT_STYLE_THUMBNAILS, // a row of thumbnails
+    NORDLICHT_STYLE_HORIZONTAL, // compress frames to columns
+    NORDLICHT_STYLE_VERTICAL, // compress frames to rows and rotate them counterclockwise
     NORDLICHT_STYLE_SLITSCAN, // take single columns, while moving to the right (and wrapping to the left)
     NORDLICHT_STYLE_MIDDLECOLUMN, // take the frames' middlemost column
     NORDLICHT_STYLE_SPECTROGRAM, // spectrogram of the first audio track (not all sample formats are supported yet)
-    NORDLICHT_STYLE_LAST
+    NORDLICHT_STYLE_LAST // just a marker so that we can count the number of available styles
 } nordlicht_style;
 
 typedef enum nordlicht_strategy {
@@ -42,26 +41,26 @@ typedef enum nordlicht_strategy {
     NORDLICHT_STRATEGY_LIVE, // generate a fast approximation first, good for live display
 } nordlicht_strategy;
 
-// Returns a description of the last error, or NULL if the was no error.
+// Returns a description of the last error, or NULL if there was no error.
 NORDLICHT_API const char *nordlicht_error();
 
-// Allocate a new nordlicht of specific width and height, for a given video
-// file. When `live` is true, give a fast approximation before starting the
-// slow, exact generation. Use `nordlicht_free` to free the nordlicht again.
+// Allocate a new nordlicht of specified width and height, for a given video
+// file. Use `nordlicht_free` to free the nordlicht again.
 // Returns NULL on errors.
 NORDLICHT_API nordlicht* nordlicht_init(const char *filename, const int width, const int height);
 
 // Free a nordlicht.
 NORDLICHT_API void nordlicht_free(nordlicht *n);
 
-// Specify where to start the nordlicht, in percent between 0 and 1.
+// Specify where to start the nordlicht in the file, as a ratio between 0 and 1.
 NORDLICHT_API int nordlicht_set_start(nordlicht *n, const float start);
 
-// Specify where to end the nordlicht, in percent between 0 and 1.
+// Specify where to end the nordlicht in the file, as a ratio between 0 and 1.
 NORDLICHT_API int nordlicht_set_end(nordlicht *n, const float end);
 
-// Set the output style of the nordlicht. Default is NORDLICHT_STYLE_HORIZONTAL.
-// Returns 0 on success. To set multiple styles at once, use `nordlicht_set_styles`.
+// Set the output style of the nordlicht, see above. Default is NORDLICHT_STYLE_HORIZONTAL.
+// To set multiple styles at once, use `nordlicht_set_styles`.
+// Returns 0 on success. 
 NORDLICHT_API int nordlicht_set_style(nordlicht *n, const nordlicht_style style);
 
 // Set multiple output styles, which will be displayed on top of each other.
@@ -69,26 +68,42 @@ NORDLICHT_API int nordlicht_set_style(nordlicht *n, const nordlicht_style style)
 // Returns 0 on success.
 NORDLICHT_API int nordlicht_set_styles(nordlicht *n, const nordlicht_style *styles, const int num_styles);
 
-// Set the generation strategy of the nordlicht. Default is NORDLICHT_STRATEGY_FAST.
-// Returns 0 on success. This function will be removed in the future.
+// Set the generation strategy of the nordlicht, see above. Default is NORDLICHT_STRATEGY_FAST.
+// Returns 0 on success.
 NORDLICHT_API int nordlicht_set_strategy(nordlicht *n, const nordlicht_strategy strategy);
 
-// Returns a pointer to the nordlicht's internal buffer. You can use it to draw
+// Return a pointer to the nordlicht's internal buffer. You can use it to draw
 // the barcode while it is generated. The pixel format is 32-bit BGRA.
 NORDLICHT_API const unsigned char* nordlicht_buffer(const nordlicht *n);
 
 // Replace the internal nordlicht's internal buffer. The data pointer is owned
-// by the caller and must be freed after `nordlicht_free`. Returns 0 on success.
+// by the caller and must be freed after `nordlicht_free`. You can use this to
+// render the nordlicht into caller-owned data structures, like mmap-ed files.
+// The pixel format is 32-bit BGRA.
+// Returns 0 on success.
 NORDLICHT_API int nordlicht_set_buffer(nordlicht *n, unsigned char *data);
 
 // Returns the size of this nordlicht's buffer in bytes.
 NORDLICHT_API size_t nordlicht_buffer_size(const nordlicht *n);
 
-// Generate the nordlicht. Calling this will freeze the nordlicht:
-// "set" functions will fail. Returns 0 on success.
+// Generate the nordlicht in one pass. Use this function from a thread if you don't
+// want to block execution. Calling this will freeze the nordlicht: "set"
+// functions will fail.
+// Returns 0 on success.
 NORDLICHT_API int nordlicht_generate(nordlicht *n);
 
-// Returns a value between 0 and 1 indicating how much of the nordlicht is done.
+// Do one step of generation, which will be as small as possible. Use this
+// if you don't want to start a seperate thread, but be aware that this
+// function might still take too long for real-time applications. Calling this
+// will freeze the nordlicht: "set" functions will fail.
+// Returns 0 on success.
+NORDLICHT_API int nordlicht_generate_step(nordlicht *n);
+
+// Returns 1 if the nordlicht has been completely generated, and 0 otherwise.
+NORDLICHT_API int nordlicht_done(const nordlicht *n);
+
+// Returns a value between 0 and 1 indicating how much of the nordlicht has been
+// generated.
 NORDLICHT_API float nordlicht_progress(const nordlicht *n);
 
 // Write the nordlicht to a PNG file. Returns 0 on success.
diff --git a/source.c b/src/source.c
similarity index 75%
rename from source.c
rename to src/source.c
index 182a536..344fff8 100644
--- a/source.c
+++ b/src/source.c
@@ -6,9 +6,34 @@
 #include <libavutil/avutil.h>
 #include <libswscale/swscale.h>
 
+// Changes for ffmpeg 3.0
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57,24,0)
+#  include <libavutil/imgutils.h>
+#  define av_free_packet av_packet_unref
+#  define avpicture_get_size(fmt,w,h) av_image_get_buffer_size(fmt,w,h,1)
+#endif
+
+// PIX_FMT was renamed to AV_PIX_FMT on this version
+#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(51,74,100)
+#  define AVPixelFormat PixelFormat
+#  define AV_PIX_FMT_RGB24 PIX_FMT_RGB24
+#  define AV_PIX_FMT_YUVJ420P PIX_FMT_YUVJ420P
+#  define AV_PIX_FMT_YUVJ422P PIX_FMT_YUVJ422P
+#  define AV_PIX_FMT_YUVJ440P PIX_FMT_YUVJ440P
+#  define AV_PIX_FMT_YUVJ444P PIX_FMT_YUVJ444P
+#  define AV_PIX_FMT_YUV420P  PIX_FMT_YUV420P
+#  define AV_PIX_FMT_YUV422P  PIX_FMT_YUV422P
+#  define AV_PIX_FMT_YUV440P  PIX_FMT_YUV440P
+#  define AV_PIX_FMT_YUV444P  PIX_FMT_YUV444P
+#endif
+
 #if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(52, 8, 0)
-#define av_frame_alloc  avcodec_alloc_frame
-#define av_frame_free av_freep
+#  define av_frame_alloc  avcodec_alloc_frame
+#  if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(54,59,100)
+#    define av_frame_free   av_freep
+#  else
+#    define av_frame_free   avcodec_free_frame
+#  endif
 #endif
 
 #define HEURISTIC_NUMBER_OF_FRAMES 1800 // how many frames will the heuristic look at?
@@ -46,6 +71,7 @@ struct source {
     int *keyframes;
     int number_of_keyframes;
     int has_index;
+    int current_frame;
 };
 
 long packet_pts(stream *st, const AVPacket *packet) {
@@ -72,12 +98,18 @@ int grab_next_frame(source *s, stream *st) {
                         break;
                     default:
                         error("Stream has unknown media type.");
+#ifdef DEBUG
+    printf("Unknown media type?\n");
+#endif
                         return 1;
                 }
 
                 if (got_frame) {
                     if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
                         if (st->frame->data[0] == 0) {
+#ifdef DEBUG
+    printf("grab_next_frame: st->frame->data[0] == 0\n");
+#endif
                             return 1;
                         }
                     }
@@ -89,16 +121,25 @@ int grab_next_frame(source *s, stream *st) {
         } else {
             av_free_packet(&s->packet);
             st->current_frame = -1;
+#ifdef DEBUG
+    printf("grab_next_frame: av_read_frame() == 0\n");
+#endif
             return 1;
         }
     }
 
     av_free_packet(&s->packet);
     st->current_frame = pts;
+#ifdef DEBUG
+    printf("grab_next_frame() grabbed frame %d\n", pts);
+#endif
     return 0;
 }
 
 int seek_keyframe(source *s, stream *st, const long frame) {
+#ifdef DEBUG
+    printf("seek_keyframe(%ld)\n", frame);
+#endif
     av_seek_frame(s->format, st->stream, frame/st->fps/st->time_base, AVSEEK_FLAG_BACKWARD);
     avcodec_flush_buffers(st->codec);
     return grab_next_frame(s, st) != 0;
@@ -109,50 +150,65 @@ int total_number_of_frames(const source *s, stream *st) {
     return st->fps*duration_sec;
 }
 
-void source_build_keyframe_index(source *s, const int width) {
-    if (! s->video) {
-        return;
-    }
-
-    s->keyframes = (int *) malloc(sizeof(long)*60*60*3); // TODO: dynamic datastructure!
-    s->number_of_keyframes = 0;
+// Returns 0 if done
+int source_build_keyframe_index_step(source *s, const int width) {
+    if (s->keyframes == NULL) {
+        if (! s->video) {
+            return 0;
+        }
+        s->keyframes = (int *) malloc(sizeof(long)*60*60*3); // TODO: dynamic datastructure!
+        s->keyframes[0] = 0;
+        s->number_of_keyframes = 1;
 
-    int frame = 0;
+        s->current_frame = 0;
 
-    s->has_index = 0;
-    s->exact = 1;
-    seek_keyframe(s, s->video, 0);
+        s->has_index = 0;
+        s->exact = 1;
+    }
 
-    while (av_read_frame(s->format, &s->packet) >= 0) {
+    if (av_read_frame(s->format, &s->packet) >= 0) {
         if (s->packet.stream_index == s->video->stream) {
             if (!!(s->packet.flags & AV_PKT_FLAG_KEY)) {
                 s->number_of_keyframes++;
 
                 long pts = packet_pts(s->video, &s->packet);
                 if (pts < 1 && s->number_of_keyframes > 0) {
-                    pts = frame;
+                    pts = s->current_frame;
                 }
 
+#ifdef DEBUG
+        printf("Found a keyframe: %ld\n", pts);
+#endif
                 s->keyframes[s->number_of_keyframes] = pts;
             }
-            if (frame == HEURISTIC_NUMBER_OF_FRAMES) {
+            if (s->current_frame == HEURISTIC_NUMBER_OF_FRAMES) {
                 const float density = 1.0*s->number_of_keyframes/HEURISTIC_NUMBER_OF_FRAMES;
                 const float required_density = 1.0*HEURISTIC_KEYFRAME_FACTOR/COLUMN_PRECISION*width/total_number_of_frames(s, s->video)/(s->end-s->start);
                 if (density > required_density) {
                     // The keyframe density in the first `HEURISTIC_NUMBER_OF_FRAMES`
                     // frames is HEURISTIC_KEYFRAME_FACTOR times higher than
-                    // the density we need overall.
-                    s->exact = 0;
+                    // the density we need overall. This means that we can abort build
+                    // the keyframe index and just seek inexactly to get good results.
                     av_free_packet(&s->packet);
-                    return;
+#ifdef DEBUG
+        printf("Keyframe indexing not necessary. Aborting.\n");
+#endif
+                    return 0;
                 }
             }
-            frame++;
+            s->current_frame++;
         }
         av_free_packet(&s->packet);
+        return 1;
+    } else {
+        // we read through the whole file
+        av_free_packet(&s->packet);
+        s->has_index = 1;
+#ifdef DEBUG
+    printf("Keyframe indexing complete.\n");
+#endif
+        return 0;
     }
-    av_free_packet(&s->packet);
-    s->has_index = 1;
 }
 
 stream* stream_init(source *s, enum AVMediaType type) {
@@ -212,6 +268,9 @@ source* source_init(const char *filename) {
 
     av_log_set_level(AV_LOG_FATAL);
     av_register_all();
+    if (strstr(filename, "://") != 0) {
+      avformat_network_init();
+    }
 
     source *s;
     s = (source *) malloc(sizeof(source));
@@ -226,6 +285,7 @@ source* source_init(const char *filename) {
     }
     if (avformat_find_stream_info(s->format, NULL) < 0) {
         avformat_close_input(&s->format);
+        free(s);
         return NULL;
     }
 
@@ -235,6 +295,7 @@ source* source_init(const char *filename) {
     if (!s->video && !s->audio) {
         error("File contains neither video nor audio");
         avformat_close_input(&s->format);
+        free(s);
         return NULL;
     }
 
@@ -244,13 +305,17 @@ source* source_init(const char *filename) {
         s->scaleframe = av_frame_alloc();
         s->scaleframe->width = s->video->frame->width;
         s->scaleframe->height = s->video->frame->height;
-        s->scaleframe->format = PIX_FMT_RGB24;
+        s->scaleframe->format = AV_PIX_FMT_RGB24;
 
-        s->buffer = (uint8_t *)av_malloc(sizeof(uint8_t)*avpicture_get_size(PIX_FMT_RGB24, s->scaleframe->width, s->scaleframe->height));
-        avpicture_fill((AVPicture *)s->scaleframe, s->buffer, PIX_FMT_RGB24, s->video->frame->width, s->video->frame->height);
+        s->buffer = (uint8_t *)av_malloc(sizeof(uint8_t)*avpicture_get_size(s->scaleframe->format, s->scaleframe->width, s->scaleframe->height));
+        #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57,24,0)
+            av_image_fill_arrays(s->scaleframe->data, s->scaleframe->linesize, s->buffer, s->scaleframe->format, s->video->frame->width, s->video->frame->height, 1);
+        #else
+            avpicture_fill((AVPicture *)s->scaleframe, s->buffer, s->scaleframe->format, s->video->frame->width, s->video->frame->height);
+        #endif
 
         s->sws_context = sws_getCachedContext(NULL, s->video->frame->width, s->video->frame->height, s->video->frame->format,
-                s->scaleframe->width, s->scaleframe->height, PIX_FMT_RGB24, SWS_AREA, NULL, NULL, NULL);
+                s->scaleframe->width, s->scaleframe->height, s->scaleframe->format, SWS_AREA, NULL, NULL, NULL);
     }
 
     s->keyframes = NULL;
@@ -279,29 +344,44 @@ long preceding_keyframe(source *s, const long frame_nr) {
             best_keyframe = s->keyframes[i];
         }
     }
+#ifdef DEBUG
+    printf("preceding_keyframe(%ld) -> %ld\n", frame_nr, best_keyframe);
+#endif
     return best_keyframe;
 }
 
 int seek(source *s, stream *st, const long min_frame_nr, const long max_frame_nr) {
+#ifdef DEBUG
+    printf("seek(%ld, %ld)\n", min_frame_nr, max_frame_nr);
+#endif
     if (s->exact && st->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
         long keyframe = preceding_keyframe(s, max_frame_nr);
 
-        if (keyframe > st->current_frame) {
+        if (keyframe > st->current_frame || max_frame_nr < st->current_frame) {
             if (seek_keyframe(s, st, keyframe) != 0) {
+#ifdef DEBUG
+    printf("seek_keyframe() == 0\n");
+#endif
                 return 1;
             }
         }
 
         while (st->current_frame < min_frame_nr) {
             if (st->current_frame > max_frame_nr) {
-                error("Target frame is in the past. This shoudn't happen. Please file a bug.");
+                error("Target frame is in the past. This shouldn't happen. Please file a bug.");
             }
             if (grab_next_frame(s, st) != 0) {
+#ifdef DEBUG
+    printf("grab_next_frame() == 0\n");
+#endif
                 return 1;
             }
         }
     } else {
         if (seek_keyframe(s, st, (min_frame_nr+max_frame_nr)/2) != 0) {
+#ifdef DEBUG
+    printf("seek_keyframe() == 0\n");
+#endif
             return 1;
         }
     }
@@ -401,8 +481,8 @@ image* source_get_audio_frame(source *s, const double min_percent, const double
     return get_frame(s, s->audio, min_percent, max_percent);
 }
 
-int source_exact(const source *s) {
-    return s->exact;
+int source_has_index(const source *s) {
+    return s->has_index;
 }
 
 void source_set_exact(source *s, const int exact) {
diff --git a/source.h b/src/source.h
similarity index 88%
rename from source.h
rename to src/source.h
index bbf4506..9d7377d 100644
--- a/source.h
+++ b/src/source.h
@@ -18,9 +18,9 @@ image* source_get_video_frame(source *s, const double min_percent, const double
 
 image* source_get_audio_frame(source *s, const double min_percent, const double max_percent);
 
-void source_build_keyframe_index(source *s, const int width);
+int source_build_keyframe_index_step(source *s, const int width);
 
-int source_exact(const source *s);
+int source_has_index(const source *s);
 
 void source_set_exact(source *s, const int exact);
 
diff --git a/testsuite.c b/src/testsuite.c
similarity index 70%
rename from testsuite.c
rename to src/testsuite.c
index 8902723..a0d1f23 100644
--- a/testsuite.c
+++ b/src/testsuite.c
@@ -42,7 +42,10 @@ CHEAT_TEST(invalid_input_file,
     cheat_null(nordlicht_init("\0", 100, 100));
     cheat_null(nordlicht_init(".", 100, 100));
     cheat_null(nordlicht_init("..", 100, 100));
+    cheat_null(nordlicht_init("/", 100, 100));
+    cheat_null(nordlicht_init("\\", 100, 100));
     cheat_null(nordlicht_init("nonexistent_file.123", 100, 100));
+    cheat_null(nordlicht_init("video.mp4.", 100, 100));
 )
 
 CHEAT_TEST(invalid_size,
@@ -54,54 +57,69 @@ CHEAT_TEST(invalid_size,
     cheat_null(nordlicht_init("video.mp4", -100, 100));
     cheat_null(nordlicht_init("video.mp4", 100, -100));
     cheat_null(nordlicht_init("video.mp4", INT_MIN, INT_MIN));
+    cheat_null(nordlicht_init("video.mp4", 100001, 100001));
+    cheat_null(nordlicht_init("video.mp4", 100001, 1));
+    cheat_null(nordlicht_init("video.mp4", 1, 100001));
 )
 
 
 CHEAT_TEST(valid_size,
     cheat_not_null(nordlicht_init("video.mp4", 1, 1));
-    cheat_not_null(nordlicht_init("video.mp4", INT_MAX, INT_MAX));
+    cheat_not_null(nordlicht_init("video.mp4", 100, 100));
+    cheat_not_null(nordlicht_init("video.mp4", 100000, 100000));
+    cheat_not_null(nordlicht_init("video.mp4", 100000, 1));
+    cheat_not_null(nordlicht_init("video.mp4", 1, 100000));
 )
 
 CHEAT_TEST(invalid_output,
-    n = nordlicht_init("video.mp4", 1, 100);
+    n = nordlicht_init("video.mp4", 100, 100);
     cheat_not_null(n);
     cheat_fail(nordlicht_write(n, NULL));
     cheat_fail(nordlicht_write(n, ""));
     cheat_fail(nordlicht_write(n, "\0"));
     cheat_fail(nordlicht_write(n, "."));
     cheat_fail(nordlicht_write(n, ".."));
+    cheat_fail(nordlicht_write(n, "/"));
     cheat_fail(nordlicht_write(n, "video.mp4"));
 )
 
 CHEAT_TEST(style,
-    n = nordlicht_init("video.mp4", 1, 100);
+    n = nordlicht_init("video.mp4", 100, 100);
     cheat_not_null(n);
     cheat_ok(nordlicht_set_style(n, NORDLICHT_STYLE_HORIZONTAL));
     cheat_ok(nordlicht_set_style(n, NORDLICHT_STYLE_VERTICAL));
-    cheat_fail(nordlicht_set_style(n, 10000000));
+    cheat_ok(nordlicht_set_style(n, NORDLICHT_STYLE_LAST-1));
+    cheat_fail(nordlicht_set_style(n, -1));
+    cheat_fail(nordlicht_set_style(n, INT_MAX));
+    cheat_fail(nordlicht_set_style(n, INT_MIN));
 )
 
 CHEAT_TEST(styles,
-    n = nordlicht_init("video.mp4", 1, 100);
+    n = nordlicht_init("video.mp4", 100, 2);
     cheat_not_null(n);
-    nordlicht_style styles[2];
+    nordlicht_style styles[3];
     styles[0] = NORDLICHT_STYLE_HORIZONTAL;
     styles[1] = NORDLICHT_STYLE_VERTICAL;
     cheat_ok(nordlicht_set_styles(n, styles, 2));
-    styles[0] = NORDLICHT_STYLE_THUMBNAILS;
+    styles[0] = NORDLICHT_STYLE_LAST-1;
     cheat_ok(nordlicht_set_styles(n, styles, 2));
-    styles[0] = 10000000;
+    styles[0] = INT_MAX;
     cheat_fail(nordlicht_set_styles(n, styles, 2));
+    styles[0] = INT_MIN;
+    cheat_fail(nordlicht_set_styles(n, styles, 2));
+    styles[2] = NORDLICHT_STYLE_HORIZONTAL;
+    cheat_fail(nordlicht_set_styles(n, styles, 3));
 )
 
 CHEAT_TEST(strategy,
-    n = nordlicht_init("video.mp4", 1, 100);
+    n = nordlicht_init("video.mp4", 100, 100);
     cheat_not_null(n);
     cheat_ok(nordlicht_set_strategy(n, NORDLICHT_STRATEGY_FAST));
     cheat_ok(nordlicht_set_strategy(n, NORDLICHT_STRATEGY_LIVE));
     cheat_fail(nordlicht_set_strategy(n, 2));
-    cheat_fail(nordlicht_set_strategy(n, 1000000));
     cheat_fail(nordlicht_set_strategy(n, -1));
+    cheat_fail(nordlicht_set_strategy(n, INT_MAX));
+    cheat_fail(nordlicht_set_strategy(n, INT_MIN));
 )
 
 CHEAT_TEST(buffer,
@@ -122,6 +140,22 @@ CHEAT_TEST(buffer,
     free(buffer2);
 )
 
+CHEAT_TEST(generate_step,
+    n = nordlicht_init("video.mp4", 1, 100);
+    cheat_assert(0 == nordlicht_progress(n));
+    cheat_assert(!nordlicht_done(n));
+    cheat_ok(nordlicht_set_start(n, 0.5));
+    cheat_assert(0 == nordlicht_generate_step(n));
+    cheat_assert(!nordlicht_done(n));
+    cheat_fail(nordlicht_set_start(n, 0.5));
+    cheat_assert(0 == nordlicht_generate_step(n));
+    cheat_assert(0 == nordlicht_generate_step(n));
+    cheat_ok(nordlicht_generate(n));
+    cheat_assert(nordlicht_done(n));
+    cheat_assert(0 == nordlicht_generate_step(n));
+    cheat_assert(nordlicht_done(n));
+)
+
 CHEAT_TEST(complete_run,
     unsigned char *buffer2 = NULL;
     n = nordlicht_init("video.mp4", 1, 100);
@@ -139,13 +173,15 @@ CHEAT_TEST(tool_argument_parsing,
     cheat_ok(tool("--version"));
     cheat_fail(tool(""));
     cheat_fail(tool("--fubar"));
+    cheat_fail(tool("-1"));
     cheat_fail(tool("one.mp4 two.mp4"));
     cheat_fail(tool("does_not_exist.mp4"));
 )
 
 CHEAT_TEST(tool_size,
     cheat_ok(tool("video.mp4 -w 1 -h 1"));
-    cheat_ok(tool("video.mp4 -w 1 -h 10000"));
+    cheat_ok(tool("video.mp4 -w 1 -h 100000"));
+    cheat_fail(tool("video.mp4 -w 1 -h 100001"));
     cheat_fail(tool("video.mp4 -w huuuge"));
     cheat_fail(tool("video.mp4 -w 0"));
     cheat_fail(tool("video.mp4 -h 0"));
@@ -166,9 +202,13 @@ CHEAT_TEST(tool_output,
     cheat_assert(-1 != access("ünîç⌀də.png", F_OK));
     cheat_fail(tool("video.mp4 -o video.mp4"));
     cheat_fail(tool("video.mp4 -o ''"));
+    cheat_fail(tool("video.mp4 -o ."));
+    cheat_fail(tool("video.mp4 -o .."));
+    cheat_fail(tool("video.mp4 -o /"));
     cheat_ok(tool("video.mp4 -w 1 -o barcode.bgra"));
-    cheat_fail(tool("video.mp4 -o barcode.abc"));
-    cheat_fail(tool("video.mp4 -o barcode"));
+    // fall back to PNG in these two cases:
+    cheat_ok(tool("video.mp4 -w 1 -o barcode.abc"));
+    cheat_ok(tool("video.mp4 -w 1 -o barcode"));
 )
 
 CHEAT_TEST(tool_style,
@@ -179,9 +219,11 @@ CHEAT_TEST(tool_style,
     cheat_ok(tool("video.mp4 -h 2 -s horizontal+vertical"));
     cheat_fail(tool("video.mp4 -s horizontal+"));
     cheat_fail(tool("video.mp4 -s +"));
+    cheat_fail(tool("video.mp4 -s ++"));
     cheat_fail(tool("video.mp4 -s horizontal++horizontal"));
     cheat_fail(tool("video.mp4 -s nope"));
     cheat_fail(tool("video.mp4 -s ''"));
+    cheat_fail(tool("video.mp4 -s"));
 )
 
 CHEAT_TEST(tool_region,
@@ -196,4 +238,5 @@ CHEAT_TEST(tool_region,
     cheat_fail(tool("video.mp4 -w 1 --end=0"));
     cheat_fail(tool("video.mp4 -w 1 --end=2"));
     cheat_fail(tool("video.mp4 -w 1 --start=0.2 --end=0.1"));
+    cheat_fail(tool("video.mp4 -w 1 --start=0.5 --end=0.5"));
 )
diff --git a/version.h.in b/src/version.h.in
similarity index 100%
rename from version.h.in
rename to src/version.h.in
diff --git a/utils/mpv-nordlicht.lua b/utils/mpv-nordlicht.lua
index 698697c..4d06d2b 100644
--- a/utils/mpv-nordlicht.lua
+++ b/utils/mpv-nordlicht.lua
@@ -25,6 +25,10 @@ end
 function shutdown()
     off()
     kill()
+
+    if buffer and buffer:len() > 0 then
+        os.remove(buffer)
+    end
 end
 
 function kill()
@@ -41,7 +45,13 @@ function new_file()
 
     video = mp.get_property("path")
     nordlicht = video..".nordlicht.png"
-    buffer = "/tmp/nordlicht.bgra"
+
+    if buffer and buffer:len() > 0 then
+        os.remove(buffer)
+    end
+    local tmpbuffer = os.tmpname()
+    buffer = tmpbuffer .. ".nordlicht.mpv.bgra"
+    os.rename(tmpbuffer, buffer)
 
     local f = io.open(nordlicht, "r")
     if f ~= nil then
@@ -49,6 +59,7 @@ function new_file()
         io.close(f)
         local cmd = {"convert", nordlicht, "-depth", "8", "-resize", width.."x"..height.."!", buffer}
         utils.subprocess({args=cmd})
+        utils.subprocess({args={"chmod", "600", buffer}})
 
         if was_on then
             on()
@@ -114,7 +125,14 @@ end
 function regenerate()
     local was_on = is_on
     off()
-    local cmd = "(nice nordlicht -s "..styles.." \""..video.."\" -o "..buffer.." -w "..width.." -h "..height.." && convert -depth 8 -size "..width.."x"..height.." "..buffer.." \""..nordlicht.."\") &"
+
+    local safe_buffer    = buffer:gsub('"', '\\"')
+    local safe_video     = video:gsub('"', '\\"')
+    local safe_nordlicht = nordlicht:gsub('"', '\\"')
+    local cmd = ('(nice nordlicht -s %s "%s" -o "%s" -w %d -h %d' ..
+                 ' && convert -depth 8 -size %dx%d "%s" "%s") &')
+                :format(styles, safe_video, safe_buffer, width, height,
+                        width, height, safe_buffer, safe_nordlicht)
     os.execute(cmd)
     if was_on then
         on()
@@ -122,11 +140,14 @@ function regenerate()
 end
 
 function jump(e)
-    local mouseX, mouseY = mp.get_mouse_pos()
-    local osdX, osdY = mp.get_osd_resolution()
-    mouseX = 100.0*mouseX/osdX
+    local mouse_x, mouse_y = mp.get_mouse_pos()
+    local osd_x, osd_y = mp.get_osd_resolution()
+    local screen_y = mp.get_property("osd-height")
+    local absolute_mouse_y = 1.0*mouse_y/osd_y*screen_y
 
-    mp.commandv("seek", mouseX, "absolute-percent", "exact")
+    if absolute_mouse_y <= mh+height and is_on then
+        mp.commandv("seek", 100.0*mouse_x/osd_x, "absolute-percent", "exact")
+    end
 end
 
 -- wait until the osd-width is > 0, then init
diff --git a/utils/nordlicht.thumbnailer b/utils/nordlicht.thumbnailer
new file mode 100644
index 0000000..35a1086
--- /dev/null
+++ b/utils/nordlicht.thumbnailer
@@ -0,0 +1,4 @@
+[Thumbnailer Entry]
+TryExec=nordlicht
+Exec=nordlicht --quiet %i --output %o --width %s --height %s
+MimeType=video/jpeg;video/mp4;video/mpeg;video/quicktime;video/x-ms-asf;video/x-ms-wm;video/x-ms-wmv;video/x-msvideo;video/x-flv;video/x-matroska;video/webm;

-- 
nordlicht packaging



More information about the pkg-multimedia-commits mailing list