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 |
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 |
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 --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 }, };