From 6c31c99289e39ac1bf2b44828d2795086aa0d6a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Str=C3=B6m?= Date: Wed, 3 Feb 2016 22:20:07 +0100 Subject: [PATCH] hlsenc: add use_localtime_mkdir option to automatically create time-based directory Use with -use_localtime, and set -hls_segment_filename to a path which contains a subdirectory i.e. /some/path/%Y%m%d/%Y%m%dT%H%M%S-%s.ts This will mkdir the %Y%m%d-part of the path if it does not already exist. In addition, each filename in the playlist output will be prefixed with this subdirectory (if playlist and segment shares the same base path). Signed-off-by: Michael Niedermayer --- doc/muxers.texi | 20 ++++++++++++++++++ libavformat/hlsenc.c | 50 +++++++++++++++++++++++++++++++++++++------- 2 files changed, 63 insertions(+), 7 deletions(-) diff --git a/doc/muxers.texi b/doc/muxers.texi index 2e6bb4ca2a..f2ad7feab9 100644 --- a/doc/muxers.texi +++ b/doc/muxers.texi @@ -318,6 +318,26 @@ ffmpeg in.nut -hls_segment_filename 'file%03d.ts' out.m3u8 This example will produce the playlist, @file{out.m3u8}, and segment files: @file{file000.ts}, @file{file001.ts}, @file{file002.ts}, etc. +@item use_localtime +Use strftime on @var{filename} to expand the segment filename with localtime. +The segment number (%d) is not available in this mode. +@example +ffmpeg in.nut -use_localtime 1 -hls_segment_filename 'file-%Y%m%d-%s.ts' out.m3u8 +@end example +This example will produce the playlist, @file{out.m3u8}, and segment files: +@file{file-20160215-1455569023.ts}, @file{file-20160215-1455569024.ts}, etc. + +@item use_localtime_mkdir +Used together with -use_localtime, it will create up to one subdirectory which +is expanded in @var{filename}. +@example +ffmpeg in.nut -use_localtime 1 -use_localtime_mkdir 1 -hls_segment_filename '%Y%m%d/file-%Y%m%d-%s.ts' out.m3u8 +@end example +This example will create a directory 201560215 (if it does not exist), and then +produce the playlist, @file{out.m3u8}, and segment files: +@file{201560215/file-20160215-1455569023.ts}, @file{201560215/file-20160215-1455569024.ts}, etc. + + @item hls_key_info_file @var{key_info_file} Use the information in @var{key_info_file} for segment encryption. The first line of @var{key_info_file} specifies the key URI written to the playlist. The diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c index 7ab7cbb9ab..8c61b6dfc6 100644 --- a/libavformat/hlsenc.c +++ b/libavformat/hlsenc.c @@ -82,6 +82,7 @@ typedef struct HLSContext { char *segment_filename; int use_localtime; ///< flag to expand filename with localtime + int use_localtime_mkdir;///< flag to mkdir dirname in timebased filename int allowcache; int64_t recording_time; int has_video; @@ -304,16 +305,35 @@ static int hls_mux_init(AVFormatContext *s) } /* Create a new segment and append it to the segment list */ -static int hls_append_segment(HLSContext *hls, double duration, int64_t pos, - int64_t size) +static int hls_append_segment(struct AVFormatContext *s, HLSContext *hls, double duration, + int64_t pos, int64_t size) { HLSSegment *en = av_malloc(sizeof(*en)); + char *tmp, *p; + const char *pl_dir, *filename; int ret; if (!en) return AVERROR(ENOMEM); - av_strlcpy(en->filename, av_basename(hls->avf->filename), sizeof(en->filename)); + filename = av_basename(hls->avf->filename); + + if (hls->use_localtime_mkdir) { + /* Possibly prefix with mkdir'ed subdir, if playlist share same + * base path. */ + tmp = av_strdup(s->filename); + if (!tmp) { + av_free(en); + return AVERROR(ENOMEM); + } + + pl_dir = av_dirname(tmp); + p = hls->avf->filename; + if (strstr(p, pl_dir) == p) + filename = hls->avf->filename + strlen(pl_dir) + 1; + av_free(tmp); + } + av_strlcpy(en->filename, filename, sizeof(en->filename)); if(hls->has_subtitle) av_strlcpy(en->sub_filename, av_basename(hls->vtt_avf->filename), sizeof(en->sub_filename)); @@ -508,7 +528,22 @@ static int hls_start(AVFormatContext *s) av_log(oc, AV_LOG_ERROR, "Could not get segment filename with use_localtime\n"); return AVERROR(EINVAL); } - } else if (av_get_frame_filename(oc->filename, sizeof(oc->filename), + + if (c->use_localtime_mkdir) { + const char *dir; + char *fn_copy = av_strdup(oc->filename); + if (!fn_copy) { + return AVERROR(ENOMEM); + } + dir = av_dirname(fn_copy); + if (mkdir(dir, 0777) == -1 && errno != EEXIST) { + av_log(oc, AV_LOG_ERROR, "Could not create directory %s with use_localtime_mkdir\n", dir); + av_free(fn_copy); + return AVERROR(errno); + } + av_free(fn_copy); + } + } else if (av_get_frame_filename(oc->filename, sizeof(oc->filename), c->basename, c->wrap ? c->sequence % c->wrap : c->sequence) < 0) { av_log(oc, AV_LOG_ERROR, "Invalid segment filename template '%s' you can try use -use_localtime 1 with it\n", c->basename); return AVERROR(EINVAL); @@ -778,7 +813,7 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt) new_start_pos = avio_tell(hls->avf->pb); hls->size = new_start_pos - hls->start_pos; - ret = hls_append_segment(hls, hls->duration, hls->start_pos, hls->size); + ret = hls_append_segment(s, hls, hls->duration, hls->start_pos, hls->size); hls->start_pos = new_start_pos; if (ret < 0) return ret; @@ -825,7 +860,7 @@ static int hls_write_trailer(struct AVFormatContext *s) if (oc->pb) { hls->size = avio_tell(hls->avf->pb) - hls->start_pos; ff_format_io_close(s, &oc->pb); - hls_append_segment(hls, hls->duration, hls->start_pos, hls->size); + hls_append_segment(s, hls, hls->duration, hls->start_pos, hls->size); } if (vtt_oc) { @@ -871,7 +906,8 @@ static const AVOption options[] = { {"round_durations", "round durations in m3u8 to whole numbers", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_ROUND_DURATIONS }, 0, UINT_MAX, E, "flags"}, {"discont_start", "start the playlist with a discontinuity tag", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_DISCONT_START }, 0, UINT_MAX, E, "flags"}, {"omit_endlist", "Do not append an endlist when ending stream", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_OMIT_ENDLIST }, 0, UINT_MAX, E, "flags"}, - { "use_localtime", "set filename expansion with strftime at segment creation", OFFSET(use_localtime), AV_OPT_TYPE_BOOL, {.i64 = 0 }, 0, 1, E }, + {"use_localtime", "set filename expansion with strftime at segment creation", OFFSET(use_localtime), AV_OPT_TYPE_BOOL, {.i64 = 0 }, 0, 1, E }, + {"use_localtime_mkdir", "create last directory component in strftime-generated filename", OFFSET(use_localtime_mkdir), AV_OPT_TYPE_BOOL, {.i64 = 0 }, 0, 1, E }, {"method", "set the HTTP method", OFFSET(method), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, { NULL },