From d9af03ddda3f6b921740dc89a41db0602002edae Mon Sep 17 00:00:00 2001
From: Ole Andre Birkedal <o.birkedal@sportradar.com>
Date: Tue, 22 Oct 2019 12:30:58 +0200
Subject: [PATCH] Added support for forcing UTC timestamps in PROGRAM-DATE-TIME
 tags of HLS playlists using -hls_flags +program_date_time+utc_pdt. Some
 services require this, especially Akamai's MSL4 HLS ingest when doing clip
 generation, and some HLS players. This patch bumps the micro version of
 libavformat.

---
 doc/muxers.texi           |  4 ++++
 libavformat/dashenc.c     |  2 +-
 libavformat/hlsenc.c      | 10 ++++++++--
 libavformat/hlsplaylist.c | 22 ++++++++++++++++++----
 libavformat/hlsplaylist.h |  3 ++-
 libavformat/version.h     |  2 +-
 6 files changed, 34 insertions(+), 9 deletions(-)

diff --git a/doc/muxers.texi b/doc/muxers.texi
index 4c88b5daec..f23c313bca 100644
--- a/doc/muxers.texi
+++ b/doc/muxers.texi
@@ -874,6 +874,10 @@ seeking. This flag should be used with the @code{hls_time} option.
 @item program_date_time
 Generate @code{EXT-X-PROGRAM-DATE-TIME} tags.
 
+@item utc_pdt
+Always use UTC timezone in @code{EXT-X-PROGRAM-DATE-TIME} tags with format
+@code{#EXT-X-PROGRAM-DATE-TIME:<YYYY-MM-DDThh:mm:ssZ>}.
+
 @item second_level_segment_index
 Makes it possible to use segment indexes as %%d in hls_segment_filename expression
 besides date/time values when strftime is on.
diff --git a/libavformat/dashenc.c b/libavformat/dashenc.c
index a462876c13..b3f5e7c174 100644
--- a/libavformat/dashenc.c
+++ b/libavformat/dashenc.c
@@ -525,7 +525,7 @@ static void write_hls_media_playlist(OutputStream *os, AVFormatContext *s,
                                 (double) seg->duration / timescale, 0,
                                 seg->range_length, seg->start_pos, NULL,
                                 c->single_file ? os->initfile : seg->file,
-                                &prog_date_time, 0, 0, 0);
+                                &prog_date_time, 0, 0, 0, 0);
         if (ret < 0) {
             av_log(os->ctx, AV_LOG_WARNING, "ff_hls_write_file_entry get error\n");
         }
diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c
index 7b1d54e23e..5cbb85b5c6 100644
--- a/libavformat/hlsenc.c
+++ b/libavformat/hlsenc.c
@@ -102,6 +102,7 @@ typedef enum HLSFlags {
     HLS_PERIODIC_REKEY = (1 << 12),
     HLS_INDEPENDENT_SEGMENTS = (1 << 13),
     HLS_I_FRAMES_ONLY = (1 << 14),
+    HLS_UTC_PDT = (1 << 15),
 } HLSFlags;
 
 typedef enum {
@@ -1433,6 +1434,7 @@ static int hls_window(AVFormatContext *s, int last, VariantStream *vs)
     AVDictionary *options = NULL;
     double prog_date_time = vs->initial_prog_date_time;
     double *prog_date_time_p = (hls->flags & HLS_PROGRAM_DATE_TIME) ? &prog_date_time : NULL;
+    int utc_pdt = hls->flags & HLS_UTC_PDT;
     int byterange_mode = (hls->flags & HLS_SINGLE_FILE) || (hls->max_seg_size > 0);
 
     hls->version = 3;
@@ -1495,7 +1497,9 @@ static int hls_window(AVFormatContext *s, int last, VariantStream *vs)
         ret = ff_hls_write_file_entry(byterange_mode ? hls->m3u8_out : vs->out, en->discont, byterange_mode,
                                       en->duration, hls->flags & HLS_ROUND_DURATIONS,
                                       en->size, en->pos, vs->baseurl,
-                                      en->filename, prog_date_time_p, en->keyframe_size, en->keyframe_pos, hls->flags & HLS_I_FRAMES_ONLY);
+                                      en->filename, prog_date_time_p,
+                                      utc_pdt, en->keyframe_size, en->keyframe_pos,
+                                      hls->flags & HLS_I_FRAMES_ONLY);
         if (ret < 0) {
             av_log(s, AV_LOG_WARNING, "ff_hls_write_file_entry get error\n");
         }
@@ -1516,7 +1520,8 @@ static int hls_window(AVFormatContext *s, int last, VariantStream *vs)
         for (en = vs->segments; en; en = en->next) {
             ret = ff_hls_write_file_entry(hls->sub_m3u8_out, 0, byterange_mode,
                                           en->duration, 0, en->size, en->pos,
-                                          vs->baseurl, en->sub_filename, NULL, 0, 0, 0);
+                                          vs->baseurl, en->sub_filename, NULL,
+                                          utc_pdt, 0, 0, 0);
             if (ret < 0) {
                 av_log(s, AV_LOG_WARNING, "ff_hls_write_file_entry get error\n");
             }
@@ -3020,6 +3025,7 @@ static const AVOption options[] = {
     {"split_by_time", "split the hls segment by time which user set by hls_time", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_SPLIT_BY_TIME }, 0, UINT_MAX,   E, "flags"},
     {"append_list", "append the new segments into old hls segment list", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_APPEND_LIST }, 0, UINT_MAX,   E, "flags"},
     {"program_date_time", "add EXT-X-PROGRAM-DATE-TIME", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_PROGRAM_DATE_TIME }, 0, UINT_MAX,   E, "flags"},
+    {"utc_pdt", "Always use UTC timestamps in EXT-PROGRAM-DATE-TIME tags", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_UTC_PDT }, 0, UINT_MAX, E, "flags"},
     {"second_level_segment_index", "include segment index in segment filenames when use_localtime", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_SECOND_LEVEL_SEGMENT_INDEX }, 0, UINT_MAX,   E, "flags"},
     {"second_level_segment_duration", "include segment duration in segment filenames when use_localtime", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_SECOND_LEVEL_SEGMENT_DURATION }, 0, UINT_MAX,   E, "flags"},
     {"second_level_segment_size", "include segment size in segment filenames when use_localtime", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_SECOND_LEVEL_SEGMENT_SIZE }, 0, UINT_MAX,   E, "flags"},
diff --git a/libavformat/hlsplaylist.c b/libavformat/hlsplaylist.c
index 9cbd02353f..47cf1446a8 100644
--- a/libavformat/hlsplaylist.c
+++ b/libavformat/hlsplaylist.c
@@ -112,7 +112,8 @@ int ff_hls_write_file_entry(AVIOContext *out, int insert_discont,
                              int64_t size, int64_t pos, //Used only if HLS_SINGLE_FILE flag is set
                              char *baseurl, //Ignored if NULL
                              char *filename, double *prog_date_time,
-                             int64_t video_keyframe_size, int64_t video_keyframe_pos, int iframe_mode) {
+                             int utc_pdt, int64_t video_keyframe_size,
+                             int64_t video_keyframe_pos, int iframe_mode) {
     if (!out || !filename)
         return AVERROR(EINVAL);
 
@@ -134,12 +135,20 @@ int ff_hls_write_file_entry(AVIOContext *out, int insert_discont,
         char buf0[128], buf1[128];
         tt = (int64_t)*prog_date_time;
         milli = av_clip(lrint(1000*(*prog_date_time - tt)), 0, 999);
-        tm = localtime_r(&tt, &tmpbuf);
+
+        if (utc_pdt)
+            tm = gmtime_r(&tt, &tmpbuf);
+        else
+            tm = localtime_r(&tt, &tmpbuf);
+
         if (!strftime(buf0, sizeof(buf0), "%Y-%m-%dT%H:%M:%S", tm)) {
             av_log(NULL, AV_LOG_DEBUG, "strftime error in ff_hls_write_file_entry\n");
             return AVERROR_UNKNOWN;
         }
-        if (!strftime(buf1, sizeof(buf1), "%z", tm) || buf1[1]<'0' ||buf1[1]>'2') {
+
+        // Generate correct offset string if we're not using UTC
+        if (!utc_pdt && !strftime(buf1, sizeof(buf1), "%z", tm) ||
+            buf1[1]<'0' || buf1[1]>'2') {
             int tz_min, dst = tm->tm_isdst;
             tm = gmtime_r(&tt, &tmpbuf);
             tm->tm_isdst = dst;
@@ -151,7 +160,12 @@ int ff_hls_write_file_entry(AVIOContext *out, int insert_discont,
                      tz_min / 60,
                      tz_min % 60);
         }
-        avio_printf(out, "#EXT-X-PROGRAM-DATE-TIME:%s.%03d%s\n", buf0, milli, buf1);
+
+        if (utc_pdt)
+            avio_printf(out, "#EXT-X-PROGRAM-DATE-TIME:%s.%03dZ\n", buf0, milli);
+        else
+            avio_printf(out, "#EXT-X-PROGRAM-DATE-TIME:%s.%03d%s\n", buf0, milli, buf1);
+
         *prog_date_time += duration;
     }
     if (baseurl)
diff --git a/libavformat/hlsplaylist.h b/libavformat/hlsplaylist.h
index a8d29d62d3..23b86761d7 100644
--- a/libavformat/hlsplaylist.h
+++ b/libavformat/hlsplaylist.h
@@ -53,7 +53,8 @@ int ff_hls_write_file_entry(AVIOContext *out, int insert_discont,
                              int64_t size, int64_t pos, //Used only if HLS_SINGLE_FILE flag is set
                              char *baseurl, //Ignored if NULL
                              char *filename, double *prog_date_time,
-                             int64_t video_keyframe_size, int64_t video_keyframe_pos, int iframe_mode);
+                             int utc_pdt, int64_t video_keyframe_size,
+                             int64_t video_keyframe_pos, int iframe_mode);
 void ff_hls_write_end_list (AVIOContext *out);
 
 #endif /* AVFORMAT_HLSPLAYLIST_H_ */
diff --git a/libavformat/version.h b/libavformat/version.h
index bcd0408d28..426ffb16e4 100644
--- a/libavformat/version.h
+++ b/libavformat/version.h
@@ -33,7 +33,7 @@
 // Also please add any ticket numbers that you believe might be affected here
 #define LIBAVFORMAT_VERSION_MAJOR  58
 #define LIBAVFORMAT_VERSION_MINOR  33
-#define LIBAVFORMAT_VERSION_MICRO 100
+#define LIBAVFORMAT_VERSION_MICRO 101
 
 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
                                                LIBAVFORMAT_VERSION_MINOR, \
-- 
2.19.1

