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