diff mbox series

[FFmpeg-devel] avformat/img2dec: added option -strftime_mkdir to allow directory creation if the strftime pattern include non-existing directories, similarly to how hls muxer does.

Message ID 20230801201025.33541-1-alexandre.schmidt@gmail.com
State New
Headers show
Series [FFmpeg-devel] avformat/img2dec: added option -strftime_mkdir to allow directory creation if the strftime pattern include non-existing directories, similarly to how hls muxer does. | expand

Checks

Context Check Description
yinshiyou/make_loongarch64 success Make finished
yinshiyou/make_fate_loongarch64 success Make fate finished
andriy/make_x86 success Make finished
andriy/make_fate_x86 success Make fate finished

Commit Message

Alexandre Heitor Schmidt Aug. 1, 2023, 8:10 p.m. UTC
doc/demuxers.texi: Documented how to use the new parameter.

Usage example:

ffmpeg -i /dev/video0 -strftime 1 -strftime_mkdir 1 "/tmp/%Y/%m/%Y_%m_%d-%H_%M_%S.jpg"
---
 doc/muxers.texi       | 13 +++++++++++++
 libavformat/img2enc.c | 28 +++++++++++++++++++++++-----
 2 files changed, 36 insertions(+), 5 deletions(-)

Comments

Nicolas George Aug. 2, 2023, 2:35 p.m. UTC | #1
Alexandre Heitor Schmidt (12023-08-01):
> doc/demuxers.texi: Documented how to use the new parameter.
> 
> Usage example:
> 
> ffmpeg -i /dev/video0 -strftime 1 -strftime_mkdir 1 "/tmp/%Y/%m/%Y_%m_%d-%H_%M_%S.jpg"
> ---
>  doc/muxers.texi       | 13 +++++++++++++
>  libavformat/img2enc.c | 28 +++++++++++++++++++++++-----
>  2 files changed, 36 insertions(+), 5 deletions(-)

Hi.

Thanks for the patch. See my remark below.

> 
> diff --git a/doc/muxers.texi b/doc/muxers.texi
> index f6071484ff..1d03fef84b 100644
> --- a/doc/muxers.texi
> +++ b/doc/muxers.texi
> @@ -1421,6 +1421,19 @@ overwritten with new images. Default value is 0.
>  If set to 1, expand the filename with date and time information from
>  @code{strftime()}. Default value is 0.
>  
> +@item strftime_mkdir
> +Used together with -strftime, if set to 1, it will create all subdirectories
> +which are expanded in @var{filename}.
> +@example
> +ffmpeg -i /dev/video0 -strftime 1 -strftime_mkdir 1 "/tmp/%Y/%m/%Y_%m_%d-%H_%M_%S.jpg"
> +@end example
> +This example will read all frames from /dev/video0 and save each frame into
> +@file{/tmp/%Y_%m/%Y_%m_%d-%H_%M_%S.jpg}, creating the necessary directory
> +structure if it doesn't exist. Supposing the current date at the time of the
> +image creation is 1978-04-27 15:01:02, the directory @file{/tmp/1978/04}
> +would be created - if it didn't existed - prior from saving the output file
> +@file{1978_04_27-15_01_02.jpg} into it.
> +
>  @item atomic_writing
>  Write output to a temporary file, which is renamed to target filename once
>  writing is completed. Default is disabled.
> diff --git a/libavformat/img2enc.c b/libavformat/img2enc.c
> index 9b8ec06cea..238f281467 100644
> --- a/libavformat/img2enc.c
> +++ b/libavformat/img2enc.c
> @@ -44,6 +44,7 @@ typedef struct VideoMuxData {
>      char target[4][1024];
>      int update;
>      int use_strftime;
> +    int use_strftime_mkdir;
>      int frame_pts;
>      const char *muxer;
>      int use_rename;
> @@ -157,6 +158,22 @@ static int write_packet(AVFormatContext *s, AVPacket *pkt)
>              av_log(s, AV_LOG_ERROR, "Could not get frame filename with strftime\n");
>              return AVERROR(EINVAL);
>          }
> +
> +        // whether to create non-existing directory structure
> +        if (img->use_strftime_mkdir) {

> +            const char *dir;
> +            char *fn_copy = av_strdup(filename);
> +            if (!fn_copy)
> +                return AVERROR(ENOMEM);
> +            dir = av_dirname(fn_copy);
> +            if (ff_mkdir_p(dir) == -1 && errno != EEXIST) {
> +                av_log(s, AV_LOG_ERROR, "Could not create directory %s with use_strftime_mkdir\n", dir);
> +                av_freep(&fn_copy);
> +                return AVERROR(errno);
> +            }

This is copied from hlsenc.c. Please factor it instead.

As far as I can see, ff_mkdir_p() is used exactly twice now and will be
thrice with this patch. And every time, it is invoked after
av_dirname(), so I suggest to turn it into ff_mkdir_p_parent(). That
would also fix the missing error checks in format_name().

> +            av_log(s, AV_LOG_VERBOSE, "Created directory %s\n", dir);
> +            av_freep(&fn_copy);
> +        }
>      } else if (img->frame_pts) {
>          if (av_get_frame_filename2(filename, sizeof(filename), s->url, pkt->pts, AV_FRAME_FILENAME_FLAGS_MULTIPLE) < 0) {
>              av_log(s, AV_LOG_ERROR, "Cannot write filename by pts of the frames.");
> @@ -252,12 +269,13 @@ static int query_codec(enum AVCodecID id, int std_compliance)
>  #define OFFSET(x) offsetof(VideoMuxData, x)
>  #define ENC AV_OPT_FLAG_ENCODING_PARAM
>  static const AVOption muxoptions[] = {
> -    { "update",       "continuously overwrite one file", OFFSET(update),  AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0,       1, ENC },
> -    { "start_number", "set first number in the sequence", OFFSET(start_img_number), AV_OPT_TYPE_INT,  { .i64 = 1 }, 0, INT_MAX, ENC },
> -    { "strftime",     "use strftime for filename", OFFSET(use_strftime),  AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC },
> -    { "frame_pts",    "use current frame pts for filename", OFFSET(frame_pts),  AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC },
> +    { "update",         "continuously overwrite one file", OFFSET(update),  AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0,       1, ENC },
> +    { "start_number",   "set first number in the sequence", OFFSET(start_img_number), AV_OPT_TYPE_INT,  { .i64 = 1 }, 0, INT_MAX, ENC },
> +    { "strftime",       "use strftime for filename", OFFSET(use_strftime),  AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC },
> +    { "strftime_mkdir", "create directory structure for filename, if needed", OFFSET(use_strftime_mkdir),  AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC },
> +    { "frame_pts",      "use current frame pts for filename", OFFSET(frame_pts),  AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC },
>      { "atomic_writing", "write files atomically (using temporary files and renames)", OFFSET(use_rename), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC },
> -    { "protocol_opts", "specify protocol options for the opened files", OFFSET(protocol_opts), AV_OPT_TYPE_DICT, {0}, 0, 0, ENC },
> +    { "protocol_opts",  "specify protocol options for the opened files", OFFSET(protocol_opts), AV_OPT_TYPE_DICT, {0}, 0, 0, ENC },
>      { NULL },
>  };
>  

Regards,
diff mbox series

Patch

diff --git a/doc/muxers.texi b/doc/muxers.texi
index f6071484ff..1d03fef84b 100644
--- a/doc/muxers.texi
+++ b/doc/muxers.texi
@@ -1421,6 +1421,19 @@  overwritten with new images. Default value is 0.
 If set to 1, expand the filename with date and time information from
 @code{strftime()}. Default value is 0.
 
+@item strftime_mkdir
+Used together with -strftime, if set to 1, it will create all subdirectories
+which are expanded in @var{filename}.
+@example
+ffmpeg -i /dev/video0 -strftime 1 -strftime_mkdir 1 "/tmp/%Y/%m/%Y_%m_%d-%H_%M_%S.jpg"
+@end example
+This example will read all frames from /dev/video0 and save each frame into
+@file{/tmp/%Y_%m/%Y_%m_%d-%H_%M_%S.jpg}, creating the necessary directory
+structure if it doesn't exist. Supposing the current date at the time of the
+image creation is 1978-04-27 15:01:02, the directory @file{/tmp/1978/04}
+would be created - if it didn't existed - prior from saving the output file
+@file{1978_04_27-15_01_02.jpg} into it.
+
 @item atomic_writing
 Write output to a temporary file, which is renamed to target filename once
 writing is completed. Default is disabled.
diff --git a/libavformat/img2enc.c b/libavformat/img2enc.c
index 9b8ec06cea..238f281467 100644
--- a/libavformat/img2enc.c
+++ b/libavformat/img2enc.c
@@ -44,6 +44,7 @@  typedef struct VideoMuxData {
     char target[4][1024];
     int update;
     int use_strftime;
+    int use_strftime_mkdir;
     int frame_pts;
     const char *muxer;
     int use_rename;
@@ -157,6 +158,22 @@  static int write_packet(AVFormatContext *s, AVPacket *pkt)
             av_log(s, AV_LOG_ERROR, "Could not get frame filename with strftime\n");
             return AVERROR(EINVAL);
         }
+
+        // whether to create non-existing directory structure
+        if (img->use_strftime_mkdir) {
+            const char *dir;
+            char *fn_copy = av_strdup(filename);
+            if (!fn_copy)
+                return AVERROR(ENOMEM);
+            dir = av_dirname(fn_copy);
+            if (ff_mkdir_p(dir) == -1 && errno != EEXIST) {
+                av_log(s, AV_LOG_ERROR, "Could not create directory %s with use_strftime_mkdir\n", dir);
+                av_freep(&fn_copy);
+                return AVERROR(errno);
+            }
+            av_log(s, AV_LOG_VERBOSE, "Created directory %s\n", dir);
+            av_freep(&fn_copy);
+        }
     } else if (img->frame_pts) {
         if (av_get_frame_filename2(filename, sizeof(filename), s->url, pkt->pts, AV_FRAME_FILENAME_FLAGS_MULTIPLE) < 0) {
             av_log(s, AV_LOG_ERROR, "Cannot write filename by pts of the frames.");
@@ -252,12 +269,13 @@  static int query_codec(enum AVCodecID id, int std_compliance)
 #define OFFSET(x) offsetof(VideoMuxData, x)
 #define ENC AV_OPT_FLAG_ENCODING_PARAM
 static const AVOption muxoptions[] = {
-    { "update",       "continuously overwrite one file", OFFSET(update),  AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0,       1, ENC },
-    { "start_number", "set first number in the sequence", OFFSET(start_img_number), AV_OPT_TYPE_INT,  { .i64 = 1 }, 0, INT_MAX, ENC },
-    { "strftime",     "use strftime for filename", OFFSET(use_strftime),  AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC },
-    { "frame_pts",    "use current frame pts for filename", OFFSET(frame_pts),  AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC },
+    { "update",         "continuously overwrite one file", OFFSET(update),  AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0,       1, ENC },
+    { "start_number",   "set first number in the sequence", OFFSET(start_img_number), AV_OPT_TYPE_INT,  { .i64 = 1 }, 0, INT_MAX, ENC },
+    { "strftime",       "use strftime for filename", OFFSET(use_strftime),  AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC },
+    { "strftime_mkdir", "create directory structure for filename, if needed", OFFSET(use_strftime_mkdir),  AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC },
+    { "frame_pts",      "use current frame pts for filename", OFFSET(frame_pts),  AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC },
     { "atomic_writing", "write files atomically (using temporary files and renames)", OFFSET(use_rename), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC },
-    { "protocol_opts", "specify protocol options for the opened files", OFFSET(protocol_opts), AV_OPT_TYPE_DICT, {0}, 0, 0, ENC },
+    { "protocol_opts",  "specify protocol options for the opened files", OFFSET(protocol_opts), AV_OPT_TYPE_DICT, {0}, 0, 0, ENC },
     { NULL },
 };