Write StampData metadata to video files
authorSybren A. Stüvel <sybren@stuvel.eu>
Thu, 5 Apr 2018 14:31:59 +0000 (16:31 +0200)
committerSybren A. Stüvel <sybren@stuvel.eu>
Thu, 5 Apr 2018 14:50:23 +0000 (16:50 +0200)
This is currently only supported by FFmpeg (so not frameserver, AVI RAW,
or AVI JPEG), and only seems to work when using Matroska or Ogg Theora
containers.

Only metadata that doesn't change from frame to frame is written to
video files. This distinction is visible in the UI by looking at the
stamp checkbox tooltips (they either mention "image" or "image/video").

Part of: https://developer.blender.org/D2273

Reviewed by: @campbellbarton

source/blender/blenkernel/BKE_image.h
source/blender/blenkernel/intern/image.c
source/blender/blenkernel/intern/writeffmpeg.c
source/blender/makesrna/intern/rna_scene.c

index 5745941..1af1237 100644 (file)
@@ -67,6 +67,11 @@ void    BKE_image_init(struct Image *image);
 typedef void (StampCallback)(void *data, const char *propname, char *propvalue, int len);
 
 void    BKE_render_result_stamp_info(struct Scene *scene, struct Object *camera, struct RenderResult *rr, bool allocate_only);
+/**
+ * Fills in the static stamp data (i.e. everything except things that can change per frame).
+ * The caller is responsible for freeing the allocated memory.
+ */
+struct StampData *BKE_stamp_info_from_scene_static(struct Scene *scene);
 void    BKE_imbuf_stamp_info(struct RenderResult *rr, struct ImBuf *ibuf);
 void    BKE_stamp_info_from_imbuf(struct RenderResult *rr, struct ImBuf *ibuf);
 void    BKE_stamp_info_callback(void *data, struct StampData *stamp_data, StampCallback callback, bool noskip);
index e65692f..34891d9 100644 (file)
@@ -1623,6 +1623,7 @@ typedef struct StampData {
        char marker[512];
        char time[512];
        char frame[512];
+       char frame_range[512];
        char camera[STAMP_NAME_SIZE];
        char cameralens[STAMP_NAME_SIZE];
        char scene[STAMP_NAME_SIZE];
@@ -1639,7 +1640,12 @@ typedef struct StampData {
 } StampData;
 #undef STAMP_NAME_SIZE
 
-static void stampdata(Scene *scene, Object *camera, StampData *stamp_data, int do_prefix)
+/**
+ * \param do_prefix: Include a label like "File ", "Date ", etc. in the stamp data strings.
+ * \param use_dynamic: Also include data that can change on a per-frame basis.
+ */
+static void stampdata(Scene *scene, Object *camera, StampData *stamp_data, int do_prefix,
+                      bool use_dynamic)
 {
        char text[256];
        struct tm *tl;
@@ -1670,7 +1676,7 @@ static void stampdata(Scene *scene, Object *camera, StampData *stamp_data, int d
                stamp_data->date[0] = '\0';
        }
 
-       if (scene->r.stamp & R_STAMP_MARKER) {
+       if (use_dynamic && scene->r.stamp & R_STAMP_MARKER) {
                const char *name = BKE_scene_find_last_marker_name(scene, CFRA);
 
                if (name) BLI_strncpy(text, name, sizeof(text));
@@ -1682,7 +1688,7 @@ static void stampdata(Scene *scene, Object *camera, StampData *stamp_data, int d
                stamp_data->marker[0] = '\0';
        }
 
-       if (scene->r.stamp & R_STAMP_TIME) {
+       if (use_dynamic && scene->r.stamp & R_STAMP_TIME) {
                const short timecode_style = USER_TIMECODE_SMPTE_FULL;
                BLI_timecode_string_from_time(text, sizeof(text), 0, FRA2TIME(scene->r.cfra), FPS, timecode_style);
                BLI_snprintf(stamp_data->time, sizeof(stamp_data->time), do_prefix ? "Timecode %s" : "%s", text);
@@ -1691,7 +1697,7 @@ static void stampdata(Scene *scene, Object *camera, StampData *stamp_data, int d
                stamp_data->time[0] = '\0';
        }
 
-       if (scene->r.stamp & R_STAMP_FRAME) {
+       if (use_dynamic && scene->r.stamp & R_STAMP_FRAME) {
                char fmtstr[32];
                int digits = 1;
 
@@ -1705,14 +1711,14 @@ static void stampdata(Scene *scene, Object *camera, StampData *stamp_data, int d
                stamp_data->frame[0] = '\0';
        }
 
-       if (scene->r.stamp & R_STAMP_CAMERA) {
+       if (use_dynamic && scene->r.stamp & R_STAMP_CAMERA) {
                BLI_snprintf(stamp_data->camera, sizeof(stamp_data->camera), do_prefix ? "Camera %s" : "%s", camera ? camera->id.name + 2 : "<none>");
        }
        else {
                stamp_data->camera[0] = '\0';
        }
 
-       if (scene->r.stamp & R_STAMP_CAMERALENS) {
+       if (use_dynamic && scene->r.stamp & R_STAMP_CAMERALENS) {
                if (camera && camera->type == OB_CAMERA) {
                        BLI_snprintf(text, sizeof(text), "%.2f", ((Camera *)camera->data)->lens);
                }
@@ -1733,7 +1739,7 @@ static void stampdata(Scene *scene, Object *camera, StampData *stamp_data, int d
                stamp_data->scene[0] = '\0';
        }
 
-       if (scene->r.stamp & R_STAMP_SEQSTRIP) {
+       if (use_dynamic && scene->r.stamp & R_STAMP_SEQSTRIP) {
                Sequence *seq = BKE_sequencer_foreground_frame_get(scene, scene->r.cfra);
 
                if (seq) BLI_strncpy(text, seq->name + 2, sizeof(text));
@@ -1749,7 +1755,7 @@ static void stampdata(Scene *scene, Object *camera, StampData *stamp_data, int d
                Render *re = RE_GetSceneRender(scene);
                RenderStats *stats = re ? RE_GetStats(re) : NULL;
 
-               if (stats && (scene->r.stamp & R_STAMP_RENDERTIME)) {
+               if (use_dynamic && stats && (scene->r.stamp & R_STAMP_RENDERTIME)) {
                        BLI_timecode_string_from_time_simple(text, sizeof(text), stats->lastframetime);
 
                        BLI_snprintf(stamp_data->rendertime, sizeof(stamp_data->rendertime), do_prefix ? "RenderTime %s" : "%s", text);
@@ -1758,7 +1764,7 @@ static void stampdata(Scene *scene, Object *camera, StampData *stamp_data, int d
                        stamp_data->rendertime[0] = '\0';
                }
 
-               if (stats && (scene->r.stamp & R_STAMP_MEMORY)) {
+               if (use_dynamic && stats && (scene->r.stamp & R_STAMP_MEMORY)) {
                        BLI_snprintf(stamp_data->memory, sizeof(stamp_data->memory), do_prefix ? "Peak Memory %.2fM" : "%.2fM", stats->mem_peak);
                }
                else {
@@ -1885,7 +1891,7 @@ void BKE_image_stamp_buf(
        display = IMB_colormanagement_display_get_named(display_device);
 
        if (stamp_data_template == NULL) {
-               stampdata(scene, camera, &stamp_data, (scene->r.stamp & R_STAMP_HIDE_LABELS) == 0);
+               stampdata(scene, camera, &stamp_data, (scene->r.stamp & R_STAMP_HIDE_LABELS) == 0, true);
        }
        else {
                stampdata_from_template(&stamp_data, scene, stamp_data_template);
@@ -2106,13 +2112,28 @@ void BKE_render_result_stamp_info(Scene *scene, Object *camera, struct RenderRes
        }
 
        if (!allocate_only)
-               stampdata(scene, camera, stamp_data, 0);
+               stampdata(scene, camera, stamp_data, 0, true);
 
        if (!rr->stamp_data) {
                rr->stamp_data = stamp_data;
        }
 }
 
+struct StampData *BKE_stamp_info_from_scene_static(Scene *scene)
+{
+       struct StampData *stamp_data;
+
+       if (!(scene && (scene->r.stamp & R_STAMP_ALL)))
+               return NULL;
+
+       /* Memory is allocated here (instead of by the caller) so that the caller
+        * doesn't have to know the size of the StampData struct. */
+       stamp_data = MEM_callocN(sizeof(StampData), __func__);
+       stampdata(scene, NULL, stamp_data, 0, false);
+
+       return stamp_data;
+}
+
 void BKE_stamp_info_callback(void *data, struct StampData *stamp_data, StampCallback callback, bool noskip)
 {
        if ((callback == NULL) || (stamp_data == NULL)) {
index 5415fbb..aa81b39 100644 (file)
@@ -53,6 +53,7 @@
 
 #include "BKE_global.h"
 #include "BKE_idprop.h"
+#include "BKE_image.h"
 #include "BKE_main.h"
 #include "BKE_report.h"
 #include "BKE_sound.h"
@@ -62,6 +63,8 @@
 
 #include "ffmpeg_compat.h"
 
+struct StampData;
+
 typedef struct FFMpegContext {
        int ffmpeg_type;
        int ffmpeg_codec;
@@ -94,6 +97,8 @@ typedef struct FFMpegContext {
        bool audio_deinterleave;
        int audio_sample_size;
 
+       struct StampData *stamp_data;
+
 #ifdef WITH_AUDASPACE
        AUD_Device *audio_mixdown_device;
 #endif
@@ -836,6 +841,12 @@ static void ffmpeg_dict_set_float(AVDictionary **dict, const char *key, float va
        av_dict_set(dict, key, buffer, 0);
 }
 
+static void ffmpeg_add_metadata_callback(void *data, const char *propname, char *propvalue, int len)
+{
+       AVDictionary **metadata = (AVDictionary **)data;
+       av_dict_set(metadata, propname, propvalue, 0);
+}
+
 static int start_ffmpeg_impl(FFMpegContext *context, struct RenderData *rd, int rectx, int recty, const char *suffix, ReportList *reports)
 {
        /* Handle to the output file */
@@ -994,6 +1005,11 @@ static int start_ffmpeg_impl(FFMpegContext *context, struct RenderData *rd, int
                        goto fail;
                }
        }
+
+       if (context->stamp_data != NULL) {
+               BKE_stamp_info_callback(&of->metadata, context->stamp_data, ffmpeg_add_metadata_callback, false);
+       }
+
        if (avformat_write_header(of, NULL) < 0) {
                BKE_report(reports, RPT_ERROR, "Could not initialize streams, probably unsupported codec combination");
                goto fail;
@@ -1168,6 +1184,7 @@ int BKE_ffmpeg_start(void *context_v, struct Scene *scene, RenderData *rd, int r
 
        context->ffmpeg_autosplit_count = 0;
        context->ffmpeg_preview = preview;
+       context->stamp_data = BKE_stamp_info_from_scene_static(scene);
 
        success = start_ffmpeg_impl(context, rd, rectx, recty, suffix, reports);
 #ifdef WITH_AUDASPACE
@@ -1734,6 +1751,7 @@ void *BKE_ffmpeg_context_create(void)
        context->ffmpeg_autosplit = 0;
        context->ffmpeg_autosplit_count = 0;
        context->ffmpeg_preview = false;
+       context->stamp_data = NULL;
 
        return context;
 }
@@ -1741,9 +1759,13 @@ void *BKE_ffmpeg_context_create(void)
 void BKE_ffmpeg_context_free(void *context_v)
 {
        FFMpegContext *context = context_v;
-       if (context) {
-               MEM_freeN(context);
+       if (context == NULL) {
+               return;
+       }
+       if (context->stamp_data) {
+               MEM_freeN(context->stamp_data);
        }
+       MEM_freeN(context);
 }
 
 #endif /* WITH_FFMPEG */
index e6ba459..9420671 100644 (file)
@@ -6355,7 +6355,7 @@ static void rna_def_scene_render_data(BlenderRNA *brna)
        
        prop = RNA_def_property(srna, "use_stamp_date", PROP_BOOLEAN, PROP_NONE);
        RNA_def_property_boolean_sdna(prop, NULL, "stamp", R_STAMP_DATE);
-       RNA_def_property_ui_text(prop, "Stamp Date", "Include the current date in image metadata");
+       RNA_def_property_ui_text(prop, "Stamp Date", "Include the current date in image/video metadata");
        RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, NULL);
        
        prop = RNA_def_property(srna, "use_stamp_frame", PROP_BOOLEAN, PROP_NONE);
@@ -6375,12 +6375,12 @@ static void rna_def_scene_render_data(BlenderRNA *brna)
        
        prop = RNA_def_property(srna, "use_stamp_scene", PROP_BOOLEAN, PROP_NONE);
        RNA_def_property_boolean_sdna(prop, NULL, "stamp", R_STAMP_SCENE);
-       RNA_def_property_ui_text(prop, "Stamp Scene", "Include the name of the active scene in image metadata");
+       RNA_def_property_ui_text(prop, "Stamp Scene", "Include the name of the active scene in image/video metadata");
        RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, NULL);
        
        prop = RNA_def_property(srna, "use_stamp_note", PROP_BOOLEAN, PROP_NONE);
        RNA_def_property_boolean_sdna(prop, NULL, "stamp", R_STAMP_NOTE);
-       RNA_def_property_ui_text(prop, "Stamp Note", "Include a custom note in image metadata");
+       RNA_def_property_ui_text(prop, "Stamp Note", "Include a custom note in image/video metadata");
        RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, NULL);
        
        prop = RNA_def_property(srna, "use_stamp_marker", PROP_BOOLEAN, PROP_NONE);
@@ -6390,7 +6390,7 @@ static void rna_def_scene_render_data(BlenderRNA *brna)
        
        prop = RNA_def_property(srna, "use_stamp_filename", PROP_BOOLEAN, PROP_NONE);
        RNA_def_property_boolean_sdna(prop, NULL, "stamp", R_STAMP_FILENAME);
-       RNA_def_property_ui_text(prop, "Stamp Filename", "Include the .blend filename in image metadata");
+       RNA_def_property_ui_text(prop, "Stamp Filename", "Include the .blend filename in image/video metadata");
        RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, NULL);
        
        prop = RNA_def_property(srna, "use_stamp_sequencer_strip", PROP_BOOLEAN, PROP_NONE);