diff --git a/doc/muxers.texi b/doc/muxers.texi index d07ae9ba191558b5032bfac441e4b68b19eb37f8..cb875a463621672254a0031db4dda6dbaf27e7c3 100644 --- a/doc/muxers.texi +++ b/doc/muxers.texi @@ -656,6 +656,11 @@ Makes it possible to use segment duration (calculated in microseconds) as %%t i expression besides date/time values when use_localtime is on. To get fixed width numbers with trailing zeroes, %%0xt format is available where x is the required width. +@item temp_file +Write segment data to filename.tmp and rename to filename only once the segment is complete. A webserver +serving up segments can be configured to reject requests to *.tmp to prevent access to in-progress segments +before they have been added to the m3u8 playlist. + @example ffmpeg -i sample.mpeg \ -f hls -hls_time 3 -hls_list_size 5 \ diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c index bd1e684954e6b1a6c7dc7c495a3fbe51fec37895..17d4fe46b2360cf6f51cb2dc4d7dd9001f48b046 100644 --- a/libavformat/hlsenc.c +++ b/libavformat/hlsenc.c @@ -76,6 +76,7 @@ typedef enum HLSFlags { HLS_SECOND_LEVEL_SEGMENT_INDEX = (1 << 8), // include segment index in segment filenames when use_localtime e.g.: %%03d HLS_SECOND_LEVEL_SEGMENT_DURATION = (1 << 9), // include segment duration (microsec) in segment filenames when use_localtime e.g.: %%09t HLS_SECOND_LEVEL_SEGMENT_SIZE = (1 << 10), // include segment size (bytes) in segment filenames when use_localtime e.g.: %%014s + HLS_TEMP_FILE = (1 << 11), } HLSFlags; typedef enum { @@ -416,6 +417,7 @@ static int hls_mux_init(AVFormatContext *s) return ret; oc = hls->avf; + oc->filename[0] = '\0'; oc->oformat = hls->oformat; oc->interrupt_callback = s->interrupt_callback; oc->max_delay = s->max_delay; @@ -815,6 +817,15 @@ static int hls_start(AVFormatContext *s) char *filename, iv_string[KEYSIZE*2 + 1]; int err = 0; + if ((c->flags & HLS_TEMP_FILE) && oc->filename[0] != 0) { + size_t len = strlen(oc->filename); + char final_filename[sizeof(oc->filename)]; + av_strlcpy(final_filename, oc->filename, len); + final_filename[len-4] = '\0'; + ff_rename(oc->filename, final_filename, s); + oc->filename[len-4] = '\0'; + } + if (c->flags & HLS_SINGLE_FILE) { av_strlcpy(oc->filename, c->basename, sizeof(oc->filename)); @@ -915,6 +926,10 @@ static int hls_start(AVFormatContext *s) set_http_options(&options, c); + if (c->flags & HLS_TEMP_FILE) { + av_strlcat(oc->filename, ".tmp", sizeof(oc->filename)); + } + if (c->key_info_file) { if ((err = hls_encryption_start(s)) < 0) goto fail; @@ -1364,6 +1379,15 @@ static int hls_write_trailer(struct AVFormatContext *s) ff_rename(old_filename, hls->avf->filename, hls); } + if ((hls->flags & HLS_TEMP_FILE) && oc->filename[0] != 0) { + size_t len = strlen(oc->filename); + char final_filename[sizeof(oc->filename)]; + av_strlcpy(final_filename, oc->filename, len); + final_filename[len-4] = '\0'; + ff_rename(oc->filename, final_filename, s); + oc->filename[len-4] = '\0'; + } + if (vtt_oc) { if (vtt_oc->pb) av_write_trailer(vtt_oc); @@ -1406,6 +1430,7 @@ static const AVOption options[] = { {"hls_subtitle_path", "set path of hls subtitles", OFFSET(subtitle_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, {"hls_flags", "set flags affecting HLS playlist and media file generation", OFFSET(flags), AV_OPT_TYPE_FLAGS, {.i64 = 0 }, 0, UINT_MAX, E, "flags"}, {"single_file", "generate a single media file indexed with byte ranges", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_SINGLE_FILE }, 0, UINT_MAX, E, "flags"}, + {"temp_file", "write segment to temporary file and rename when complete", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_TEMP_FILE }, 0, UINT_MAX, E, "flags"}, {"delete_segments", "delete segment files that are no longer part of the playlist", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_DELETE_SEGMENTS }, 0, UINT_MAX, E, "flags"}, {"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"},