MPEG-TS stream metadata

TL;DR: I need a way to associate a string with a h265 stream in MPEG-TS.

I’ve tried a number of ways to skin this cat, but I simply cannot find anything that works. I need some metadata on the resulting video file that can be later consumed to determine which stream is what. ‘Title’ attributes are typical, but I can’t find a good way to set one.

In the tsduck sample files, I found one that appears structured as I expect it. ffprobe on that file gives an output that looks like it has something I could store what I need in it.

Input #0, mpegts, from 'https://tsduck.io/streams/poland-dttv-warsaw/PL_DVB-T2_2023-11-10_MUX_BCAST_490MHz.ts':
  Duration: 00:02:01.08, start: 59067.570933, bitrate: 29750 kb/s
  Program 72 
    Metadata:
      service_name    : Nowa TV HD
      service_provider: BCAST
    Stream #0:0[0x32](pol): Audio: mp2 ([3][0][0][0] / 0x0003), 48000 Hz, stereo, fltp, 192 kb/s
    Stream #0:1[0x31]: Video: hevc (Main) (HEVC / 0x43564548), yuv420p(tv), 1920x1080 [SAR 1:1 DAR 16:9], 25 fps, 25 tbr, 90k tbn, 25 tbc
  Program 71 
    Metadata:
      service_name    : Polsat HD
      service_provider: BCAST
    Stream #0:7[0x35](pol): Audio: mp2 ([3][0][0][0] / 0x0003), 48000 Hz, stereo, s16p, 256 kb/s
    Stream #0:3[0x34]: Video: hevc (Main) (HEVC / 0x43564548), yuv420p(tv), 1920x1080 [SAR 1:1 DAR 16:9], 25 fps, 25 tbr, 90k tbn, 25 tbc
...

At the most basic, I’ve tried taginject. When writing it to an MKV file, it works as expected:

gst-launch-1.0 videotestsrc num-buffers=100 ! videoconvert ! x265enc ! h265parse ! taginject tags="title=FooBar" ! matroskamux ! filesink location=test.mkv

and the resulting output from ffprobe shows it’s there:

  Duration: 00:00:03.33, start: 0.000000, bitrate: 1285 kb/s
    Stream #0:0(eng): Video: hevc (Rext), yuv444p(tv, smpte170m, progressive), 320x240 [SAR 1:1 DAR 4:3], 30 fps, 30 tbr, 1k tbn, 30 tbc (default)
    Metadata:
      ENCODER         : x265
      TITLE           : FooBar
      BITSPS          : 1274113

But no such luck if I change the above pipeline to use mpegtsmux.


I’ve also tried following the example at the top of the mpegtsmux documentation by way of a quick port to python. No such luck. Some of the language used in that example and the output of ffprobe on the tsduck sample line up, so I get the feeling I’m on the right track here. But again, nothing.


            sdt = GstMpegts.SDT();
            sdt.actual_ts = True;
            sdt.transport_stream_id = 1;

            service = GstMpegts.SDTService()
            service.service_id = 1
            service.EIT_schedule_flag = False;
            service.EIT_present_following_flag = False;
            service.free_CA_mode = False;

            desc = GstMpegts.Descriptor.from_dvb_service(
              GstMpegts.DVBServiceType.DIGITAL_TELEVISION,
              "some-service");

            service.descriptors.append(desc)
            sdt.services.append(service)

            section = GstMpegts.section_from_sdt(sdt);
            section.send_event(proxy);

Note, the element I’m sending the message to is a proxysink, where the associated proxysrc is connected to mpegtsmux. Is this allowed? Will that event travel through the pipeline as I expect it to?


Normally this is where I would ask “What am I missing?” But, the answer to that is clear – everything. I’m really at a lose for how to do this, and I can’t find a comprehensive example on how to accomplish what I need.

Thanks in advance. I didn’t expect this to be such an obscure part of the GStreamer API.

to further elaborate. Here is a python port of the code in the mpegtsmux documentation:

import gi
gi.require_version('Gst', '1.0')
gi.require_version('GstMpegts', '1.0')
gi.require_version('GObject', '2.0')
from gi.repository import Gst, GstMpegts, GObject

PIPELINE_STR="videotestsrc num-buffers=100 ! x265enc ! queue ! mpegtsmux name=mux ! filesink location=/tmp/test.ts"

def advertise_service(mux):
    sdt = GstMpegts.SDT()
    sdt.actual_ts = True
    sdt.transport_stream_id = 42

    service = GstMpegts.SDTService()
    service.service_id = 42
    service.running_status = \
        GstMpegts.RunningStatus.RUNNING + service.service_id;
    service.EIT_schedule_flag = False
    service.EIT_present_following_flag = False
    service.free_CA_mode = False

    desc = GstMpegts.Descriptor.from_dvb_service(
      GstMpegts.DVBServiceType.DIGITAL_TELEVISION,
      "some-service")

    service.descriptors.append(desc)
    sdt.services.append(service)

    section = GstMpegts.section_from_sdt(sdt)
    section.send_event(mux)


Gst.init(None)

pipeline = Gst.parse_launch(PIPELINE_STR)

loop = GObject.MainLoop()
mux = pipeline.get_by_name("mux")
advertise_service(mux)

def end_of_stream(bus, message):
    if message.type == Gst.MessageType.EOS:
        loop.quit()

    return True

bus = pipeline.get_bus()
bus.add_signal_watch()
bus.connect("message::eos", end_of_stream)


pipeline.set_state(Gst.State.PLAYING)

try:
    loop.run()
except:
    pass        

pipeline.set_state(Gst.State.NULL)

Unfortunately, that does not return anything in ffprobe (it is entirely possible ffprobe is the wrong place to retrieve this information, let me know). I expect to see ‘some-service’ in the output somewhere.

Input #0, mpegts, from '/tmp/test.ts':
  Duration: 00:00:03.33, start: 3600.000000, bitrate: 1381 kb/s
  Program 1 
    Stream #0:0[0x41]: Video: hevc (Rext) ([36][0][0][0] / 0x0024), yuv444p(tv, smpte170m), 320x240 [SAR 1:1 DAR 4:3], 30 fps, 30 tbr, 90k tbn, 30 tbc

A little more digging. (I hope someone out there can help!)

It appears these two lines in my python port do nothing:

    service.descriptors.append(desc)
    sdt.services.append(service)

When running under pdb, and checking the values of those two arrays, they’re both empty after the append calls.