diff mbox series

[FFmpeg-devel,v1,1/1] Matroid patch: add support for writing additional metadata to filename

Message ID 20210223002719.36809-1-tak@matroid.com
State Superseded
Headers show
Series [FFmpeg-devel,v1,1/1] Matroid patch: add support for writing additional metadata to filename | expand

Checks

Context Check Description
andriy/x86_make_warn warning New warnings during build
andriy/x86_make success Make finished
andriy/x86_make_fate success Make fate finished
andriy/PPC64_make success Make finished
andriy/PPC64_make_fate success Make fate finished

Commit Message

Tak Li Feb. 23, 2021, 12:27 a.m. UTC
* Support micro seconds for image2 strftime filename
* Support micro seconds for segment strftime filename
* Support duration for segment output
* Allow split on keyframe with NOPTS
* Add EXT-X-PROGRAM-DATE-TIME parsing to hls.c
* Add global timestamp (gts) metatdata to AVPacket and AVFrame
* Support gts for image2 filename
* Support gts for segment filename

Signed-off-by: tak-matroid <tak@matroid.com>
---
 fftools/ffmpeg.c          |   1 +
 libavcodec/avpacket.c     |   2 +
 libavcodec/decode.c       |   1 +
 libavcodec/encode.c       |   9 +++
 libavcodec/packet.h       |   2 +
 libavformat/avformat.h    |   5 +-
 libavformat/hls.c         |  56 ++++++++++++++++++
 libavformat/hlsenc.c      |  15 +++--
 libavformat/img2enc.c     |  49 ++++++++++++----
 libavformat/segment.c     | 119 +++++++++++++++++++++++++++++---------
 libavformat/utils.c       |  49 +++++++++++++++-
 libavutil/Makefile        |   1 -
 libavutil/frame.c         |   2 +
 libavutil/frame.h         |   2 +
 libavutil/time.c          |  17 ++++++
 libavutil/time.h          |  11 ++++
 libavutil/time_internal.h |  23 ++++++++
 17 files changed, 314 insertions(+), 50 deletions(-)

Comments

Nicolas George Feb. 23, 2021, 10:02 a.m. UTC | #1
tak-matroid (12021-02-22):
> * Support micro seconds for image2 strftime filename
> * Support micro seconds for segment strftime filename
> * Support duration for segment output
> * Allow split on keyframe with NOPTS
> * Add EXT-X-PROGRAM-DATE-TIME parsing to hls.c
> * Add global timestamp (gts) metatdata to AVPacket and AVFrame
> * Support gts for image2 filename
> * Support gts for segment filename

Please make one patch per functional change.

No field can be added to AVPacket, as applications are currently allowed
to allocate it themselves.

Regards,
Tak Li March 10, 2021, 1:52 a.m. UTC | #2
Hey Nicolas,

Thanks for the comment. I'll split this patch into separate ones and work
on getting them approved sequentially then. Feel free to close this thread
in favor of the upcoming one I just emailed.

*Jiahang (Tak) Li*
Deep Learning Engineer, Matroid Inc
https://www.matroid.com


On Tue, Feb 23, 2021 at 2:02 AM Nicolas George <george@nsup.org> wrote:

> tak-matroid (12021-02-22):
> > * Support micro seconds for image2 strftime filename
> > * Support micro seconds for segment strftime filename
> > * Support duration for segment output
> > * Allow split on keyframe with NOPTS
> > * Add EXT-X-PROGRAM-DATE-TIME parsing to hls.c
> > * Add global timestamp (gts) metatdata to AVPacket and AVFrame
> > * Support gts for image2 filename
> > * Support gts for segment filename
>
> Please make one patch per functional change.
>
> No field can be added to AVPacket, as applications are currently allowed
> to allocate it themselves.
>
> Regards,
>
> --
>   Nicolas George
>
diff mbox series

Patch

diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c
index add5a3e505..9f3ea8a691 100644
--- a/fftools/ffmpeg.c
+++ b/fftools/ffmpeg.c
@@ -2117,6 +2117,7 @@  static void do_streamcopy(InputStream *ist, OutputStream *ost, const AVPacket *p
     } else
         opkt.dts = av_rescale_q(pkt->dts, ist->st->time_base, ost->mux_timebase);
     opkt.dts -= ost_tb_start_time;
+    opkt.gts = pkt->gts;
 
     opkt.duration = av_rescale_q(pkt->duration, ist->st->time_base, ost->mux_timebase);
 
diff --git a/libavcodec/avpacket.c b/libavcodec/avpacket.c
index e4ba403cf6..08eb0dc5cb 100644
--- a/libavcodec/avpacket.c
+++ b/libavcodec/avpacket.c
@@ -37,6 +37,7 @@  void av_init_packet(AVPacket *pkt)
     pkt->pts                  = AV_NOPTS_VALUE;
     pkt->dts                  = AV_NOPTS_VALUE;
     pkt->pos                  = -1;
+    pkt->gts                  = -1;
     pkt->duration             = 0;
 #if FF_API_CONVERGENCE_DURATION
 FF_DISABLE_DEPRECATION_WARNINGS
@@ -575,6 +576,7 @@  int av_packet_copy_props(AVPacket *dst, const AVPacket *src)
 
     dst->pts                  = src->pts;
     dst->dts                  = src->dts;
+    dst->gts                  = src->gts;
     dst->pos                  = src->pos;
     dst->duration             = src->duration;
 #if FF_API_CONVERGENCE_DURATION
diff --git a/libavcodec/decode.c b/libavcodec/decode.c
index 8086362eb2..499ec4bd85 100644
--- a/libavcodec/decode.c
+++ b/libavcodec/decode.c
@@ -1741,6 +1741,7 @@  FF_ENABLE_DEPRECATION_WARNINGS
         frame->pkt_pos      = pkt->pos;
         frame->pkt_duration = pkt->duration;
         frame->pkt_size     = pkt->size;
+        frame->gts          = pkt->gts;
 
         for (i = 0; i < FF_ARRAY_ELEMS(sd); i++) {
             int size;
diff --git a/libavcodec/encode.c b/libavcodec/encode.c
index 282337e453..b5a6dde879 100644
--- a/libavcodec/encode.c
+++ b/libavcodec/encode.c
@@ -126,6 +126,7 @@  static int encode_simple_internal(AVCodecContext *avctx, AVPacket *avpkt)
     AVFrame          *frame = es->in_frame;
     int got_packet;
     int ret;
+    double gts = -1;
 
     if (avci->draining_done)
         return AVERROR_EOF;
@@ -137,6 +138,10 @@  static int encode_simple_internal(AVCodecContext *avctx, AVPacket *avpkt)
             return ret;
     }
 
+    // record frame global timestamp
+    if (frame)
+        gts = frame->gts;
+
     if (!frame->buf[0]) {
         if (!(avctx->codec->capabilities & AV_CODEC_CAP_DELAY ||
               (avci->frame_thread_encoder && avctx->active_thread_type & FF_THREAD_FRAME)))
@@ -164,6 +169,10 @@  static int encode_simple_internal(AVCodecContext *avctx, AVPacket *avpkt)
             avpkt->pts = avpkt->dts = frame->pts;
     }
 
+    // assign global timestamp if available
+    if (gts > 0 && avpkt->gts <= 0)
+        avpkt->gts = gts;
+
     av_assert0(ret <= 0);
 
     emms_c();
diff --git a/libavcodec/packet.h b/libavcodec/packet.h
index b9d4c9c2c8..8b4fee759b 100644
--- a/libavcodec/packet.h
+++ b/libavcodec/packet.h
@@ -391,6 +391,8 @@  typedef struct AVPacket {
     attribute_deprecated
     int64_t convergence_duration;
 #endif
+
+    double gts;
 } AVPacket;
 
 typedef struct AVPacketList {
diff --git a/libavformat/avformat.h b/libavformat/avformat.h
index 41482328f6..38c90cba26 100644
--- a/libavformat/avformat.h
+++ b/libavformat/avformat.h
@@ -2803,8 +2803,11 @@  void av_dump_format(AVFormatContext *ic,
  * @param flags AV_FRAME_FILENAME_FLAGS_*
  * @return 0 if OK, -1 on format error
  */
+int av_get_frame_filename3(char *buf, int buf_size,
+                          const char *path, int number, int flags, int64_t ts, double duration, double global_timestamp);
+
 int av_get_frame_filename2(char *buf, int buf_size,
-                          const char *path, int number, int flags);
+                          const char *path, int number, int flags, int64_t ts);
 
 int av_get_frame_filename(char *buf, int buf_size,
                           const char *path, int number);
diff --git a/libavformat/hls.c b/libavformat/hls.c
index af2468ad9b..29e91099f7 100644
--- a/libavformat/hls.c
+++ b/libavformat/hls.c
@@ -34,6 +34,7 @@ 
 #include "libavutil/opt.h"
 #include "libavutil/dict.h"
 #include "libavutil/time.h"
+#include "libavutil/time_internal.h"
 #include "avformat.h"
 #include "internal.h"
 #include "avio_internal.h"
@@ -75,6 +76,8 @@  struct segment {
     uint8_t iv[16];
     /* associated Media Initialization Section, treated as a segment */
     struct segment *init_section;
+    double program_date_time;
+    int64_t initial_dts;
 };
 
 struct rendition;
@@ -723,6 +726,7 @@  static int parse_playlist(HLSContext *c, const char *url,
     struct segment **prev_segments = NULL;
     int prev_n_segments = 0;
     int64_t prev_start_seq_no = -1;
+    double program_date_time = -1;
 
     if (is_http && !in && c->http_persistent && c->playlist_pb) {
         in = c->playlist_pb;
@@ -872,6 +876,28 @@  static int parse_playlist(HLSContext *c, const char *url,
             ptr = strchr(ptr, '@');
             if (ptr)
                 seg_offset = strtoll(ptr+1, NULL, 10);
+        } else if (av_strstart(line, "#EXT-X-PROGRAM-DATE-TIME:", &ptr)) {
+            struct tm pdt;
+            int y,M,d,h,m;
+            double s;
+
+            // TODO: take timezone into consideration
+            if (sscanf(ptr, "%d-%d-%dT%d:%d:%lf", &y, &M, &d, &h, &m, &s) != 6) {
+                ret = AVERROR_INVALIDDATA;
+                goto fail;
+            }
+
+            pdt.tm_year = y - 1900;
+            pdt.tm_mon = M - 1;
+            pdt.tm_mday = d;
+            pdt.tm_hour = h;
+            pdt.tm_min = m;
+            pdt.tm_sec = 0;
+            pdt.tm_isdst = -1;
+
+            program_date_time = ff_timegm(&pdt);
+            program_date_time += s;
+            // TODO: avoid rounding errors by tracking ms separately
         } else if (av_strstart(line, "#", NULL)) {
             av_log(c->ctx, AV_LOG_INFO, "Skip ('%s')\n", line);
             continue;
@@ -941,6 +967,13 @@  static int parse_playlist(HLSContext *c, const char *url,
                 }
                 seg->duration = duration;
                 seg->key_type = key_type;
+
+                seg->program_date_time = program_date_time;
+                if (program_date_time > 0) {
+                    program_date_time += (double) duration / AV_TIME_BASE;
+                }
+                seg->initial_dts = -1;
+
                 dynarray_add(&pls->segments, &pls->n_segments, seg);
                 is_segment = 0;
 
@@ -992,8 +1025,20 @@  fail:
     return ret;
 }
 
+static struct segment *closest_segment(struct playlist *pls) {
+    int n = pls->cur_seq_no - pls->start_seq_no;
+    if (n < 0)
+        return pls->segments[0];
+    if (n >= pls->n_segments)
+        return pls->segments[pls->n_segments - 1];
+    return pls->segments[pls->cur_seq_no - pls->start_seq_no];
+}
+
 static struct segment *current_segment(struct playlist *pls)
 {
+    int n = pls->cur_seq_no - pls->start_seq_no;
+    if (n >= pls->n_segments)
+        return NULL;
     return pls->segments[pls->cur_seq_no - pls->start_seq_no];
 }
 
@@ -2163,6 +2208,17 @@  static int hls_read_packet(AVFormatContext *s, AVPacket *pkt)
                         return ret;
                     break;
                 } else {
+                    struct segment *seg = closest_segment(pls);
+                    if (seg->initial_dts < 0 && pls->pkt.dts != AV_NOPTS_VALUE) {
+                        seg->initial_dts = pls->pkt.dts;
+                    }
+
+                    if (pls->pkt.pts != AV_NOPTS_VALUE && seg->program_date_time > 0 && seg->initial_dts > 0) {
+                        pls->pkt.gts = seg->program_date_time + (pls->pkt.pts - seg->initial_dts) * av_q2d(get_timebase(pls));
+                    } else {
+                        pls->pkt.gts = -1;
+                    }
+
                     /* stream_index check prevents matching picture attachments etc. */
                     if (pls->is_id3_timestamped && pls->pkt.stream_index == 0) {
                         /* audio elementary streams are id3 timestamped */
diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c
index 7d97ce1789..2e583293dc 100644
--- a/libavformat/hlsenc.c
+++ b/libavformat/hlsenc.c
@@ -1265,9 +1265,11 @@  static int parse_playlist(AVFormatContext *s, const char *url, VariantStream *vs
             }
         } else if (av_strstart(line, "#EXT-X-PROGRAM-DATE-TIME:", &ptr)) {
             struct tm program_date_time;
-            int y,M,d,h,m,s;
-            double ms;
-            if (sscanf(ptr, "%d-%d-%dT%d:%d:%d.%lf", &y, &M, &d, &h, &m, &s, &ms) != 7) {
+            int y,M,d,h,m;
+            double s;
+
+            // TODO: take timezone into consideration
+            if (sscanf(ptr, "%d-%d-%dT%d:%d:%lf", &y, &M, &d, &h, &m, &s) != 6) {
                 ret = AVERROR_INVALIDDATA;
                 goto fail;
             }
@@ -1277,11 +1279,12 @@  static int parse_playlist(AVFormatContext *s, const char *url, VariantStream *vs
             program_date_time.tm_mday = d;
             program_date_time.tm_hour = h;
             program_date_time.tm_min = m;
-            program_date_time.tm_sec = s;
+            program_date_time.tm_sec = 0;
             program_date_time.tm_isdst = -1;
 
-            discont_program_date_time = mktime(&program_date_time);
-            discont_program_date_time += (double)(ms / 1000);
+            discont_program_date_time = ff_timegm(&program_date_time);
+            discont_program_date_time += s;
+            // TODO: avoid rounding errors by tracking ms separately
         } else if (av_strstart(line, "#", NULL)) {
             continue;
         } else if (line[0]) {
diff --git a/libavformat/img2enc.c b/libavformat/img2enc.c
index 0f7a21ffa0..a1f0616229 100644
--- a/libavformat/img2enc.c
+++ b/libavformat/img2enc.c
@@ -27,7 +27,9 @@ 
 #include "libavutil/log.h"
 #include "libavutil/opt.h"
 #include "libavutil/pixdesc.h"
+#include "libavutil/time.h"
 #include "libavutil/time_internal.h"
+#include <sys/time.h>
 #include "avformat.h"
 #include "avio_internal.h"
 #include "internal.h"
@@ -42,6 +44,7 @@  typedef struct VideoMuxData {
     char target[4][1024];
     int update;
     int use_strftime;
+    int use_global_timestamp;
     int frame_pts;
     const char *muxer;
     int use_rename;
@@ -130,31 +133,54 @@  static int write_packet(AVFormatContext *s, AVPacket *pkt)
     VideoMuxData *img = s->priv_data;
     AVIOContext *pb[4] = {0};
     char filename[1024];
-    AVCodecParameters *par = s->streams[pkt->stream_index]->codecpar;
+    AVStream *stream = s->streams[pkt->stream_index];
+    AVCodecParameters *par = stream->codecpar;
     const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(par->format);
     int ret, i;
     int nb_renames = 0;
+    int64_t ts = av_rescale_q(pkt->pts, stream->time_base, AV_TIME_BASE_Q);
     AVDictionary *options = NULL;
 
     if (img->update) {
         av_strlcpy(filename, img->path, sizeof(filename));
-    } else if (img->use_strftime) {
-        time_t now0;
-        struct tm *tm, tmpbuf;
-        time(&now0);
-        tm = localtime_r(&now0, &tmpbuf);
-        if (!strftime(filename, sizeof(filename), img->path, tm)) {
-            av_log(s, AV_LOG_ERROR, "Could not get frame filename with strftime\n");
-            return AVERROR(EINVAL);
+    } else if (img->use_strftime || img->frame_pts || img->use_global_timestamp) {
+        char temp_name[sizeof(filename)];
+        if (img->use_strftime) {
+            struct timeval tv;
+            gettimeofday(&tv, NULL);
+            if (!av_strftime_micro(temp_name, sizeof(temp_name), img->path, &tv)) {
+                av_log(s, AV_LOG_ERROR, "Could not get frame filename with strftime\n");
+                return AVERROR(EINVAL);
+            }
+        } else {
+            av_strlcpy(temp_name, img->path, sizeof(temp_name));
+        }
+
+        if (img->frame_pts || img->use_global_timestamp) {
+            double global_timestamp = pkt->gts;
+            if (av_get_frame_filename3(filename, sizeof(filename), temp_name, pkt->pts, AV_FRAME_FILENAME_FLAGS_MULTIPLE, ts, 0, global_timestamp) < 0) {
+                av_log(s, AV_LOG_ERROR, "Cannot write filename by pts of the frames.");
+                return AVERROR(EINVAL);
+            }
+        } else {
+            av_strlcpy(filename, temp_name, sizeof(filename));
         }
+    } else if (av_get_frame_filename2(filename, sizeof(filename), img->path,
+                                        img->img_number,
+                                        AV_FRAME_FILENAME_FLAGS_MULTIPLE, ts) < 0 &&
+                img->img_number > 1) {
+        av_log(s, AV_LOG_ERROR,
+                "Could not get frame filename number %d from pattern '%s' (either set update or use a pattern like %%03d within the filename pattern)\n",
+                img->img_number, img->path);
+        return AVERROR(EINVAL);
     } else if (img->frame_pts) {
-        if (av_get_frame_filename2(filename, sizeof(filename), img->path, pkt->pts, AV_FRAME_FILENAME_FLAGS_MULTIPLE) < 0) {
+        if (av_get_frame_filename2(filename, sizeof(filename), img->path, pkt->pts, AV_FRAME_FILENAME_FLAGS_MULTIPLE, ts) < 0) {
             av_log(s, AV_LOG_ERROR, "Cannot write filename by pts of the frames.");
             return AVERROR(EINVAL);
         }
     } else if (av_get_frame_filename2(filename, sizeof(filename), img->path,
                                       img->img_number,
-                                      AV_FRAME_FILENAME_FLAGS_MULTIPLE) < 0 &&
+                                      AV_FRAME_FILENAME_FLAGS_MULTIPLE, ts) < 0 &&
                img->img_number > 1) {
         av_log(s, AV_LOG_ERROR,
                "Could not get frame filename number %d from pattern '%s'. "
@@ -244,6 +270,7 @@  static const AVOption muxoptions[] = {
     { "start_number", "set first number in the sequence", OFFSET(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 },
+    { "global_timestamp", "use global timestamp for filename", OFFSET(use_global_timestamp),  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 },
     { NULL },
diff --git a/libavformat/segment.c b/libavformat/segment.c
index dff3d0ed48..685e6ab4d3 100644
--- a/libavformat/segment.c
+++ b/libavformat/segment.c
@@ -41,7 +41,9 @@ 
 #include "libavutil/time.h"
 #include "libavutil/timecode.h"
 #include "libavutil/time_internal.h"
+#include <sys/time.h>
 #include "libavutil/timestamp.h"
+#include "libavutil/avstring.h"
 
 typedef struct SegmentListEntry {
     int index;
@@ -118,6 +120,10 @@  typedef struct SegmentContext {
     int   write_empty;
 
     int use_rename;
+    int use_pts;
+    int use_global_timestamp;
+    int64_t segment_ts;
+    double global_timestamp;
     char temp_list_filename[1024];
 
     SegmentListEntry cur_entry;
@@ -204,19 +210,23 @@  static int set_segment_filename(AVFormatContext *s)
     if (seg->segment_idx_wrap)
         seg->segment_idx %= seg->segment_idx_wrap;
     if (seg->use_strftime) {
-        time_t now0;
-        struct tm *tm, tmpbuf;
-        time(&now0);
-        tm = localtime_r(&now0, &tmpbuf);
-        if (!strftime(buf, sizeof(buf), s->url, tm)) {
-            av_log(oc, AV_LOG_ERROR, "Could not get segment filename with strftime\n");
+        struct timeval tv;
+        gettimeofday(&tv, NULL);
+        if (!av_strftime_micro(buf, sizeof(buf), s->url, &tv)) {
+            av_log(s, AV_LOG_ERROR, "Could not get segment filename with strftime\n");
             return AVERROR(EINVAL);
         }
+    } else if (seg->use_pts || seg->use_global_timestamp)  {
+        av_strlcpy(buf, s->url, sizeof(buf));
     } else if (av_get_frame_filename(buf, sizeof(buf),
                                      s->url, seg->segment_idx) < 0) {
         av_log(oc, AV_LOG_ERROR, "Invalid segment filename template '%s'\n", s->url);
         return AVERROR(EINVAL);
     }
+
+    if (seg->use_pts || seg->use_global_timestamp)
+        av_strlcat(buf, ".tmp", sizeof(buf));
+
     new_name = av_strdup(buf);
     if (!new_name)
         return AVERROR(ENOMEM);
@@ -251,6 +261,8 @@  static int segment_start(AVFormatContext *s, int write_header)
     }
 
     seg->segment_idx++;
+    seg->segment_ts = -1;
+    seg->global_timestamp = -1;
     if ((seg->segment_idx_wrap) && (seg->segment_idx % seg->segment_idx_wrap == 0))
         seg->segment_idx_wrap_nb++;
 
@@ -374,6 +386,34 @@  static int segment_end(AVFormatContext *s, int write_trailer, int is_last)
         av_log(s, AV_LOG_ERROR, "Failure occurred when ending segment '%s'\n",
                oc->url);
 
+    if (seg->use_pts || seg->use_global_timestamp) {
+        char final_name[1024];
+        if (av_get_frame_filename3(final_name, sizeof(final_name), seg->cur_entry.filename,
+                                   0, AV_FRAME_FILENAME_FLAGS_MULTIPLE, seg->segment_ts,
+                                   seg->cur_entry.end_time - seg->cur_entry.start_time, seg->global_timestamp) < 0) {
+            av_log(s, AV_LOG_ERROR, "Cannot rename filename by pts of the frames.");
+            return AVERROR(EINVAL);
+        }
+        // remove .tmp
+        final_name[strlen(final_name) - 4] = '\0';
+
+        char *url, *src, *dst;
+        if (!(url = av_strdup(oc->url)))
+            return AVERROR(ENOMEM);
+        const char *dir = av_dirname(url);
+        if (!(src = av_append_path_component(dir, seg->cur_entry.filename)) ||
+            !(dst = av_append_path_component(dir, final_name)))
+            return AVERROR(ENOMEM);
+        if (ff_rename(src, dst, s))
+            return AVERROR(EINVAL);
+
+        av_free(url); av_free(src); av_free(dst);
+        size_t len = strlen(final_name) + 1;
+        if ((ret = av_reallocp(&seg->cur_entry.filename, len)) < 0)
+            return ret;
+        av_strlcpy(seg->cur_entry.filename, final_name, len);
+    }
+
     if (seg->list) {
         if (seg->list_size || seg->list_type == LIST_TYPE_M3U8) {
             SegmentListEntry *entry = av_mallocz(sizeof(*entry));
@@ -685,6 +725,8 @@  static int seg_init(AVFormatContext *s)
     int i;
 
     seg->segment_count = 0;
+    seg->segment_ts = -1;
+    seg->global_timestamp = -1;
     if (!seg->write_header_trailer)
         seg->individual_header_trailer = 0;
 
@@ -872,6 +914,18 @@  static int seg_write_packet(AVFormatContext *s, AVPacket *pkt)
     }
 
 calc_times:
+    if (pkt->stream_index == seg->reference_stream_index) {
+        if (seg->use_pts) {
+            if (pkt->pts != AV_NOPTS_VALUE && (seg->segment_ts < 0 ||
+                av_compare_ts(pkt->pts, st->time_base, seg->segment_ts, AV_TIME_BASE_Q) < 0)) {
+                seg->segment_ts = av_rescale_q(pkt->pts, st->time_base, AV_TIME_BASE_Q);
+            }
+        }
+        if (seg->use_global_timestamp && seg->global_timestamp < 0 && pkt->gts > 0) {
+            seg->global_timestamp = pkt->gts;
+        }
+    }
+
     if (seg->times) {
         end_pts = seg->segment_count < seg->nb_times ?
             seg->times[seg->segment_count] : INT64_MAX;
@@ -902,28 +956,35 @@  calc_times:
     if (pkt->stream_index == seg->reference_stream_index &&
         (pkt->flags & AV_PKT_FLAG_KEY || seg->break_non_keyframes) &&
         (seg->segment_frame_count > 0 || seg->write_empty) &&
-        (seg->cut_pending || seg->frame_count >= start_frame ||
-         (pkt->pts != AV_NOPTS_VALUE &&
-          av_compare_ts(pkt->pts, st->time_base,
-                        end_pts - seg->time_delta, AV_TIME_BASE_Q) >= 0))) {
-        /* sanitize end time in case last packet didn't have a defined duration */
-        if (seg->cur_entry.last_duration == 0)
-            seg->cur_entry.end_time = (double)pkt->pts * av_q2d(st->time_base);
-
-        if ((ret = segment_end(s, seg->individual_header_trailer, 0)) < 0)
-            goto fail;
-
-        if ((ret = segment_start(s, seg->individual_header_trailer)) < 0)
-            goto fail;
-
-        seg->cut_pending = 0;
-        seg->cur_entry.index = seg->segment_idx + seg->segment_idx_wrap * seg->segment_idx_wrap_nb;
-        seg->cur_entry.start_time = (double)pkt->pts * av_q2d(st->time_base);
-        seg->cur_entry.start_pts = av_rescale_q(pkt->pts, st->time_base, AV_TIME_BASE_Q);
-        seg->cur_entry.end_time = seg->cur_entry.start_time;
-
-        if (seg->times || (!seg->frames && !seg->use_clocktime) && seg->write_empty)
-            goto calc_times;
+        (pkt->pts != AV_NOPTS_VALUE || pkt->dts != AV_NOPTS_VALUE)) {
+
+        if (pkt->pts == AV_NOPTS_VALUE) {
+            av_log(s, AV_LOG_WARNING, "dangerously set keyframe NOPTS pts to dts %" PRId64 "\n", pkt->dts);
+            pkt->pts = pkt->dts;
+        }
+
+        if (seg->cut_pending || seg->frame_count >= start_frame ||
+                av_compare_ts(pkt->pts, st->time_base,
+                              end_pts - seg->time_delta, AV_TIME_BASE_Q) >= 0) {
+            /* sanitize end time in case last packet didn't have a defined duration */
+            if (!seg->cur_entry.last_duration)
+                seg->cur_entry.end_time = (double)pkt->pts * av_q2d(st->time_base);
+
+            if ((ret = segment_end(s, seg->individual_header_trailer, 0)) < 0)
+                goto fail;
+
+            if ((ret = segment_start(s, seg->individual_header_trailer)) < 0)
+                goto fail;
+
+            seg->cut_pending = 0;
+            seg->cur_entry.index = seg->segment_idx + seg->segment_idx_wrap * seg->segment_idx_wrap_nb;
+            seg->cur_entry.start_time = (double)pkt->pts * av_q2d(st->time_base);
+            seg->cur_entry.start_pts = av_rescale_q(pkt->pts, st->time_base, AV_TIME_BASE_Q);
+            seg->cur_entry.end_time = seg->cur_entry.start_time;
+
+            if (seg->times || (!seg->frames && !seg->use_clocktime) && seg->write_empty)
+                goto calc_times;
+        }
     }
 
     if (pkt->stream_index == seg->reference_stream_index) {
@@ -1045,6 +1106,8 @@  static const AVOption options[] = {
     { "strftime",          "set filename expansion with strftime at segment creation", OFFSET(use_strftime), AV_OPT_TYPE_BOOL, {.i64 = 0 }, 0, 1, E },
     { "increment_tc", "increment timecode between each segment", OFFSET(increment_tc), AV_OPT_TYPE_BOOL, {.i64 = 0 }, 0, 1, E },
     { "break_non_keyframes", "allow breaking segments on non-keyframes", OFFSET(break_non_keyframes), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, E },
+    { "frame_pts", "use current frame pts for filename", OFFSET(use_pts), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, E },
+    { "global_timestamp", "use global timestamp for filename", OFFSET(use_global_timestamp), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, E},
 
     { "individual_header_trailer", "write header/trailer to each segment", OFFSET(individual_header_trailer), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, E },
     { "write_header_trailer", "write a header to the first segment and a trailer to the last one", OFFSET(write_header_trailer), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, E },
diff --git a/libavformat/utils.c b/libavformat/utils.c
index 652758e98e..3de777baa4 100644
--- a/libavformat/utils.c
+++ b/libavformat/utils.c
@@ -20,6 +20,7 @@ 
  */
 
 #include <stdint.h>
+#include <ctype.h>
 
 #include "config.h"
 
@@ -4695,10 +4696,17 @@  uint64_t ff_get_formatted_ntp_time(uint64_t ntp_time_us)
     return ntp_ts;
 }
 
-int av_get_frame_filename2(char *buf, int buf_size, const char *path, int number, int flags)
+int av_get_frame_filename2(char *buf, int buf_size, const char *path,
+                           int number, int flags, int64_t ts)
+{
+    return av_get_frame_filename3(buf, buf_size, path, number, flags, ts, 0, -1);
+}
+
+int av_get_frame_filename3(char *buf, int buf_size, const char *path,
+                           int number, int flags, int64_t ts, double duration, double global_timestamp)
 {
     const char *p;
-    char *q, buf1[20], c;
+    char *q, buf1[32], c;
     int nd, len, percentd_found;
 
     q = buf;
@@ -4735,6 +4743,41 @@  int av_get_frame_filename2(char *buf, int buf_size, const char *path, int number
                 memcpy(q, buf1, len);
                 q += len;
                 break;
+            case 't':
+                if (!(flags & AV_FRAME_FILENAME_FLAGS_MULTIPLE) && percentd_found)
+                    goto fail;
+                percentd_found = 1;
+                int64_t seconds   = ts / AV_TIME_BASE;
+                int64_t microsecs = ts % AV_TIME_BASE;
+                snprintf(buf1, sizeof(buf1), "%010" PRId64 ".%06" PRId64, seconds, microsecs);
+                len = strlen(buf1);
+                if ((q - buf + len) > buf_size - 1)
+                    goto fail;
+                memcpy(q, buf1, len);
+                q += len;
+                break;
+            case 'l':
+                if (!(flags & AV_FRAME_FILENAME_FLAGS_MULTIPLE) && percentd_found)
+                    goto fail;
+                percentd_found = 1;
+                snprintf(buf1, sizeof(buf1), "%017.6f", duration);
+                len = strlen(buf1);
+                if ((q - buf + len) > buf_size - 1)
+                    goto fail;
+                memcpy(q, buf1, len);
+                q += len;
+                break;
+            case 'g':
+                if (!(flags & AV_FRAME_FILENAME_FLAGS_MULTIPLE) && percentd_found)
+                    goto fail;
+                percentd_found = 1;
+                snprintf(buf1, sizeof(buf1), "%017.6f", global_timestamp);
+                len = strlen(buf1);
+                if ((q - buf + len) > buf_size - 1)
+                    goto fail;
+                memcpy(q, buf1, len);
+                q += len;
+                break;
             default:
                 goto fail;
             }
@@ -4755,7 +4798,7 @@  fail:
 
 int av_get_frame_filename(char *buf, int buf_size, const char *path, int number)
 {
-    return av_get_frame_filename2(buf, buf_size, path, number, 0);
+    return av_get_frame_filename2(buf, buf_size, path, number, 0, 0);
 }
 
 void av_url_split(char *proto, int proto_size,
diff --git a/libavutil/Makefile b/libavutil/Makefile
index 27bafe9e12..3936978b26 100644
--- a/libavutil/Makefile
+++ b/libavutil/Makefile
@@ -173,7 +173,6 @@  OBJS = adler32.o                                                        \
        video_enc_params.o                                               \
        film_grain_params.o                                              \
 
-
 OBJS-$(CONFIG_CUDA)                     += hwcontext_cuda.o
 OBJS-$(CONFIG_D3D11VA)                  += hwcontext_d3d11va.o
 OBJS-$(CONFIG_DXVA2)                    += hwcontext_dxva2.o
diff --git a/libavutil/frame.c b/libavutil/frame.c
index eab51b6a32..2021239a75 100644
--- a/libavutil/frame.c
+++ b/libavutil/frame.c
@@ -164,6 +164,7 @@  FF_ENABLE_DEPRECATION_WARNINGS
     frame->color_range         = AVCOL_RANGE_UNSPECIFIED;
     frame->chroma_location     = AVCHROMA_LOC_UNSPECIFIED;
     frame->flags               = 0;
+    frame->gts                 = -1;
 }
 
 static void free_side_data(AVFrameSideData **ptr_sd)
@@ -386,6 +387,7 @@  FF_ENABLE_DEPRECATION_WARNINGS
     dst->colorspace             = src->colorspace;
     dst->color_range            = src->color_range;
     dst->chroma_location        = src->chroma_location;
+    dst->gts                    = src->gts;
 
     av_dict_copy(&dst->metadata, src->metadata, 0);
 
diff --git a/libavutil/frame.h b/libavutil/frame.h
index 1aeafef6de..fa69c6a424 100644
--- a/libavutil/frame.h
+++ b/libavutil/frame.h
@@ -691,6 +691,8 @@  typedef struct AVFrame {
      * for the target frame's private_ref field.
      */
     AVBufferRef *private_ref;
+
+    double gts;
 } AVFrame;
 
 #if FF_API_FRAME_GET_SET
diff --git a/libavutil/time.c b/libavutil/time.c
index 740afc4785..54152173a6 100644
--- a/libavutil/time.c
+++ b/libavutil/time.c
@@ -96,3 +96,20 @@  int av_usleep(unsigned usec)
     return AVERROR(ENOSYS);
 #endif
 }
+
+size_t av_strftime_micro(char *buf, size_t size, const char *format, const struct timeval *tv)
+{
+  struct tm *tm;
+  char *temp_name = av_malloc(size);
+  if (!temp_name)
+    return 0;
+
+  if (!(tm = localtime(&(tv->tv_sec))))
+    return 0;
+
+  strftime(temp_name, size, format, tm);
+  size_t retval = snprintf(buf, size, temp_name, tv->tv_usec);
+
+  av_free(temp_name);
+  return retval;
+}
diff --git a/libavutil/time.h b/libavutil/time.h
index dc169b064a..baa9aacc73 100644
--- a/libavutil/time.h
+++ b/libavutil/time.h
@@ -21,6 +21,7 @@ 
 #ifndef AVUTIL_TIME_H
 #define AVUTIL_TIME_H
 
+#include "mem.h"
 #include <stdint.h>
 
 /**
@@ -53,4 +54,14 @@  int av_gettime_relative_is_monotonic(void);
  */
 int av_usleep(unsigned usec);
 
+/**
+ * Linux strftime function with micro second accuracy
+ * @param buf    char buffer to write output
+ * @param size   max number of bytes
+ * @param format format string, identical to the one for strftime
+ * @param tv     timeval struct containing current time
+ * @return number of bytes written
+ */
+size_t av_strftime_micro(char *buf, size_t size, const char *format, const struct timeval *tv);
+
 #endif /* AVUTIL_TIME_H */
diff --git a/libavutil/time_internal.h b/libavutil/time_internal.h
index d0f007ab1c..a6fdc10ad1 100644
--- a/libavutil/time_internal.h
+++ b/libavutil/time_internal.h
@@ -20,6 +20,9 @@ 
 #define AVUTIL_TIME_INTERNAL_H
 
 #include <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
 #include "config.h"
 
 #if !HAVE_GMTIME_R && !defined(gmtime_r)
@@ -46,4 +49,24 @@  static inline struct tm *ff_localtime_r(const time_t* clock, struct tm *result)
 #define localtime_r ff_localtime_r
 #endif
 
+static inline time_t ff_timegm(struct tm *tm)
+{
+  time_t ret;
+  char *tz;
+
+  tz = getenv("TZ");
+  if (tz)
+      tz = strdup(tz);
+  setenv("TZ", "", 1);
+  tzset();
+  ret = mktime(tm);
+  if (tz) {
+      setenv("TZ", tz, 1);
+      free(tz);
+  } else
+      unsetenv("TZ");
+  tzset();
+  return ret;
+}
+
 #endif /* AVUTIL_TIME_INTERNAL_H */