From fc5c72f0b7e958e1fcca399a05b1332d1f95b224 Mon Sep 17 00:00:00 2001 From: PierreDillard <7gaspard77@gmail.com> Date: Mon, 5 Aug 2024 10:05:31 +0200 Subject: [PATCH 01/28] updates levels, keywords-cloud, modal --- .gitignore | 2 + docs/Howtos/encoding.md | 8 +- docs/Howtos/filters-oneliners.md | 38 ++-- docs/Howtos/gpac-mp4box.md | 2 +- docs/Howtos/inspecting.md | 8 +- docs/Howtos/mp4box-filters.md | 10 +- docs/Howtos/mp4box-inplace.md | 6 +- docs/Howtos/network-capture.md | 4 +- docs/Howtos/nodejs.md | 14 +- docs/Howtos/pipes.md | 6 +- docs/Howtos/playlist.md | 6 +- docs/Howtos/raw-formats.md | 8 +- docs/Howtos/sockets.md | 2 +- docs/data/keywords.json | 142 +++++++++++++ docs/glossary.md/encode.md | 35 ++++ docs/glossary.md/encrypt.md | 39 ++++ docs/javascripts/cache.js | 8 + docs/javascripts/domManipulation.js | 119 +++++++++++ docs/javascripts/fetchFunctions.js | 59 ++++++ docs/javascripts/keywordsDisplay.js | 51 +++++ docs/javascripts/keywordsFinder.js | 30 +++ docs/javascripts/levels.js | 173 ++++++++++++++++ docs/javascripts/main.js | 24 +++ docs/javascripts/modalFunctions.js | 55 +++++ docs/stylesheets/collapse_section.css | 103 +++++++++ docs/stylesheets/extra.css | 143 ++++++++----- docs/stylesheets/feedback.css | 4 + docs/stylesheets/glossary-pages.css | 6 + docs/stylesheets/keyword-cloud.css | 121 +++++++++++ docs/stylesheets/levels.css | 84 ++++++++ docs/stylesheets/modal.css | 83 ++++++++ docs/stylesheets/toggle.css | 69 +++++++ docs/stylesheets/top.css | 32 +++ mkdocs.yml | 48 ++++- overrides/base.html | 276 +++++++++++++++++++++++++ overrides/main.html | 4 + overrides/partials/header.html | 76 +++++++ overrides/partials/keywords-cloud.html | 31 +++ overrides/partials/nav-items.html | 152 ++++++++++++++ overrides/partials/nav.html | 31 +++ overrides/partials/search.html | 44 ++++ overrides/partials/toc-item.html | 19 ++ overrides/partials/toc.html | 28 +++ overrides/partials/top.html | 8 + 44 files changed, 2100 insertions(+), 111 deletions(-) create mode 100644 docs/data/keywords.json create mode 100644 docs/glossary.md/encode.md create mode 100644 docs/glossary.md/encrypt.md create mode 100644 docs/javascripts/cache.js create mode 100644 docs/javascripts/domManipulation.js create mode 100644 docs/javascripts/fetchFunctions.js create mode 100644 docs/javascripts/keywordsDisplay.js create mode 100644 docs/javascripts/keywordsFinder.js create mode 100644 docs/javascripts/levels.js create mode 100644 docs/javascripts/main.js create mode 100644 docs/javascripts/modalFunctions.js create mode 100644 docs/stylesheets/collapse_section.css create mode 100644 docs/stylesheets/feedback.css create mode 100644 docs/stylesheets/glossary-pages.css create mode 100644 docs/stylesheets/keyword-cloud.css create mode 100644 docs/stylesheets/levels.css create mode 100644 docs/stylesheets/modal.css create mode 100644 docs/stylesheets/toggle.css create mode 100644 docs/stylesheets/top.css create mode 100644 overrides/base.html create mode 100644 overrides/main.html create mode 100644 overrides/partials/header.html create mode 100644 overrides/partials/keywords-cloud.html create mode 100644 overrides/partials/nav-items.html create mode 100644 overrides/partials/nav.html create mode 100644 overrides/partials/search.html create mode 100644 overrides/partials/toc-item.html create mode 100644 overrides/partials/toc.html create mode 100644 overrides/partials/top.html diff --git a/.gitignore b/.gitignore index 8f997d18..ecb5b0f8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ .ignore/ site/ .venv/ +venv/ __pycache__/ site.* .DS_Store +node_modules/ diff --git a/docs/Howtos/encoding.md b/docs/Howtos/encoding.md index e533a029..58765026 100644 --- a/docs/Howtos/encoding.md +++ b/docs/Howtos/encoding.md @@ -1,4 +1,4 @@ -# Overview +# Overview {: data-level="all" } We discuss here how to use encoders in GPAC. @@ -6,7 +6,7 @@ GPAC filter graph resolution always targets the shortest possible path between t For example, if the source is an AAC file and the destination is an ISOBMFF file, since ISOBMFF accepts AAC data inputs, no encoder will be used. But if the source is an AAC file and the destination is an MP3 file, the graph resolver will load a decoder and an MP3 encoder to move source data from AAC to MP3. -# Encoding Video +# Encoding Video {: data-level="beginner" } ## Encoding from a raw video file @@ -99,7 +99,7 @@ You can use the [ffsws](ffsws) filter to rescale videos in your pipeline: This will resize (downscale or upscale) input to a resolution of 1280x720 without checking aspect ratio, and encode to AVC at 1 mbps. To keep aspect ratio, use `ffsws:osize=1280x720:keepar=full`. -# Encoding Audio +# Encoding Audio {: data-level="beginner" } ## Encoding from files This is basically the same as video @@ -120,7 +120,7 @@ You can use the [audio resampler](resample) filter to change your input signal c This will resample the input signal to stereo 48000 Hz and encode to AAC at 128k. -# Transcoding AV file +# Transcoding AV file {: data-level="beginner" } Combine the above tow steps: diff --git a/docs/Howtos/filters-oneliners.md b/docs/Howtos/filters-oneliners.md index 958e9d4a..48ac8408 100644 --- a/docs/Howtos/filters-oneliners.md +++ b/docs/Howtos/filters-oneliners.md @@ -1,4 +1,9 @@ -# Foreword + + + + +# Foreword {: data-level="all" } + This page contains one-liners illustrating the many possibilities of GPAC filters architecture. For a more detailed information, it is highly recommended that you read: @@ -13,7 +18,7 @@ To get a better understanding of each command illustrated in this case, it is re Whenever an option is specified, e.g. `dest.mp4:foo`, you can get more info and locate the parent filter of this option using `gpac -h foo`. The filter session is by default quiet, except for warnings and error reporting. To get information on the session while running, use [-r](gpac_general#r) option. To get more runtime information, use the [log system](core_logs). - +sectionLevel === null || Given the configurable nature of the filter architecture, most examples given in one context can be reused in another context. For example: - from the dump examples: @@ -45,7 +50,7 @@ GPAC filters can use either: _NOTE This page is under permanent construction, feel free to contribute !_ -# Source Inspection +# Source Inspection {: data-level="beginner" } Check if a source is supported and get quick information on the media streams - _see [filter](inspect), [howto](inspecting)_ ``` @@ -95,7 +100,7 @@ Count all AC3 audio streams in a source - _see [filter](probe), [doc](filters_g res = `gpac -i source @#CodecID=ac3 probe` ``` -# Dumping and decoding +# Dumping and decoding Extract AVC track, potentially transcoding - _see [filter](writegen)_ ``` @@ -147,7 +152,7 @@ Extract all AAC audio tracks - _see [doc](filters_general#complex-links)_ gpac -i source reframer @#CodecID=aac -o dump_$ID$:dynext ``` -# Multiplexing +# Multiplexing {: data-level="beginner" } Mux sources to MP4 - _see [filter](mp4mx)_ ``` @@ -192,7 +197,7 @@ Mux sources to MKV (for builds with FFmpeg support) - _see [filter](ffmx)_ gpac -i source -o dst.mkv ``` -# Remultiplexing +# Remultiplexing {: data-level="beginner" } Remux sources to MP4 forcing bitstream reparsing (all syntaxes are equivalent) ``` @@ -201,7 +206,7 @@ MP4Box -add source:unframer -new dst.mp4 ``` -# Encoding +# Encoding {: data-level="beginner" } Encode an MP3 to an AAC file at 100 kbps - _see [filter](ffenc)_ ``` @@ -258,7 +263,7 @@ gpac -i source enc:c=avc:b=3m:pass2 -o dst.mp4 ``` -# Rescaling +# Rescaling rescale without respecting aspect ratio ``` @@ -276,7 +281,7 @@ gpac -i source1.mp4 ffsws:osize=510x512:osr=3/2 vout ``` -# Encryption +# Encryption Encrypt and dash several sources using a single drm configuration - _see [filter](cecrypt)_ ``` @@ -294,7 +299,7 @@ gpac -i source.mp4 dasher:gencues cecrypt:cfile=roll_seg.xml -o live.mpd ``` -# Piping and sockets +# Piping and sockets Grab a compressed stream (e.g. AVC|H264) from stdin, remove all non I-frames and non-video PIDs and output as raw 264 over stdout - _see [filter](fout) [howto](pipes)_ ``` @@ -402,7 +407,7 @@ gpac -i source::#HLSMExt=vfoo,vbar=video::#HLSVExt=#fooVideo,#bar1=optVideo -i s ``` -# Time modification +# Time modification _Note: If the source is an MP4 file, it is much simpler/faster to perform these operations using MP4Box_ @@ -427,7 +432,7 @@ gpac -i source restamp:fps=30000/1001:rawv=force -o dst ``` -# Source splitting +# Source splitting {: data-level="beginner" } Extract from 1min to 2min30s - _see [filter](restamp)_ ``` @@ -457,7 +462,7 @@ gpac -i source reframer:xs=T00:01:00,T00:05:20:xe=T00:02:30 -o dst ``` -# Playlists and source concatenation +# Playlists and source concatenation {: data-level="beginner" } Loop file forever playback - _see [filter](flist)_ ``` @@ -483,7 +488,7 @@ ls *.mp4 > pl.m3u gpac -i pl.m3u vout ``` -# Playback +# Playback { : data-level="beginner" } Basic playback - _see [howto](filters-playback)_ ``` @@ -554,7 +559,7 @@ Decode source MP4 enabling only the minimum filters: gpac -blacklist=-fin,mp4dmx,ffdec,vout source.mp4 vout ``` -# FFmpeg support +# FFmpeg support Set gpac as RTMP output server ``` @@ -771,5 +776,4 @@ Fractal-like animated video reuse ] } ] -``` - +``` \ No newline at end of file diff --git a/docs/Howtos/gpac-mp4box.md b/docs/Howtos/gpac-mp4box.md index 69173f31..6305e497 100644 --- a/docs/Howtos/gpac-mp4box.md +++ b/docs/Howtos/gpac-mp4box.md @@ -1,4 +1,4 @@ -# MP4Box vs gpac +# MP4Box vs gpac {: data-level="all" } Following the introduction of the filter architecture and the gpac application, you may have a hard time choosing between MP4Box and gpac. diff --git a/docs/Howtos/inspecting.md b/docs/Howtos/inspecting.md index 6088c80d..3519ba39 100644 --- a/docs/Howtos/inspecting.md +++ b/docs/Howtos/inspecting.md @@ -1,9 +1,9 @@ -# Overview +# Overview {: data-level="all" } We discuss here how to use the [inspect](inspect) filter to get information on sources in GPAC. -# Media Streams inspection +# Media Streams inspection {: data-level="beginner" } ``` gpac -i source.mp4 inspect @@ -55,6 +55,7 @@ The start and duration of the inspection can be modified: ``` gpac -i source.mp4 inspect:deep:start=10:dur=1 ``` + This will force inspecting from start time 10 second (or previous access point) for 1 second. # Filtering the inspection @@ -76,6 +77,7 @@ The full list of options for the packet information log is given in the [inspect ``` gpac -i source.mp4 inspect:interleave=false:fmt="PCK%num% DTS=%dts% CTS=%cts% SAP=%sap% size=%size%%lf%":log=dump.txt ``` + This is the same as above with some nicer formatting of the output: ``` PCK1 DTS=0 CTS=0 SAP=1 size=200 @@ -111,4 +113,4 @@ Using the mode `analyse=bs` will give, for supported media types, the fields rea This is still a work in progress, more media types need to be added and deeper analysis of the packets should be done (for now only the slice headers are parsed). -This should however help you check consistency of systems layer information (timing, SAP, dependency information) with frame properties. +This should however help you check consistency of systems layer information (timing, SAP, dependency information) with frame properties. \ No newline at end of file diff --git a/docs/Howtos/mp4box-filters.md b/docs/Howtos/mp4box-filters.md index b9511047..c0b7b904 100644 --- a/docs/Howtos/mp4box-filters.md +++ b/docs/Howtos/mp4box-filters.md @@ -1,4 +1,4 @@ -# Overview +# Overview {: data-level="all" } We discuss here how to use the [MP4Box](MP4Box-introduction) together with filters in GPAC. @@ -12,9 +12,9 @@ As discussed [here](Rearchitecture), the following features of MP4Box are now us At the time being, only media importing and DASHing operations can be extended to use other filters. -# Media Importing +# Media Importing -# Customizing source and mux parameters +# Customizing source and mux parameters {: data-level="beginner" } It is possible to provide additional parameters to both source and destination during an import operations. Most MP4Box parameters are mapped to the filter engine and they have roughly the same names, but the opposite is not true (i.e. most options of the various filters in GPAC are not mapped as MP4Box options). - source options are declared using `:sopt:` separator @@ -53,7 +53,7 @@ The filter statistics can be seen by using the `fstat` option: MP4Box -add source.aac:fstat -new file.mp4 ``` -# Filtering input streams +# Filtering input streams {: data-level="beginner" } Assume you have an input file with several tracks/media streams, such as two videos in AVC and HEVC, and audios in english and french, and you only want to import AVC and english audio from that source file. With regular MP4Box usage, you would need to know the track IDs of the desired tracks. If you have a collection of such files with varying track IDs, you would need complex dumping and analyzing of the file tracks to extract their track IDs and import them. @@ -125,7 +125,7 @@ You may ask yourself whether using MP4Box or gpac is more efficient for such an - The filter architecture does not support (for the moment) reading and writing in the same file, so if you need to add a track to an existing file, you must use MP4Box for that. -# DASHing +# DASHing {: data-level="beginner" } It is possible to provide a filter chain to each source being DASHed with MP4Box. diff --git a/docs/Howtos/mp4box-inplace.md b/docs/Howtos/mp4box-inplace.md index dea65ac0..9c891f31 100644 --- a/docs/Howtos/mp4box-inplace.md +++ b/docs/Howtos/mp4box-inplace.md @@ -1,4 +1,4 @@ -# Overview +# Overview {: data-level="all" } As of GPAC 2.0, MP4Box supports in-place editing of MP4 files. @@ -32,14 +32,14 @@ MP4Box -ab TEST -itags artist=GPAC movie.mp4 Tests for in-place storage are available [here](https://github.com/gpac/testsuite/blob/master/scripts/mp4box-inplace.sh). -# Flat storage files +# Flat storage files {: data-level="all" } In files stored with flat storage (`-flat` in MP4Box), the media data is placed before the structured data (`moov` and `meta` box) and usually does not need any shifting. There is one exception to this: when adding brands, since they must be located first in the file, it is necessary to shift the media data. There is currently no way to reserve space for future brand edition in MP4Box, and any brand add operation will result in media data shift. We therefore recommend using interleaved storage (`moov`/ `meta` first). -# Interleaved files +# Interleaved files {: data-level="all" } In files stored with interleaved storage (`-inter` in MP4Box), the media data is placed after the structured data (`moov` and `meta` box) and may need shifting whenever the size of {ftyp, moov, meta} boxes changes. In order to avoid shifting when this size decreases, a `free` box is inserted before the media data. When this size increases, the media data is shifted if writing the structured data will overwrite the start of the media data. Otherwise, a `free` box is written between the `moov+meta` boxes and the media data. diff --git a/docs/Howtos/network-capture.md b/docs/Howtos/network-capture.md index f8807fab..ccaa1fa0 100644 --- a/docs/Howtos/network-capture.md +++ b/docs/Howtos/network-capture.md @@ -1,8 +1,8 @@ -# Overview +# Overview {: data-level="all" } We discuss here how to use network captures with GPAC 2.3-DEV or above. -# Overview + GPAC can: diff --git a/docs/Howtos/nodejs.md b/docs/Howtos/nodejs.md index 5e69a92b..c20dc782 100644 --- a/docs/Howtos/nodejs.md +++ b/docs/Howtos/nodejs.md @@ -1,4 +1,4 @@ -# Overview +# Overview {: data-level="all" } We discuss here how to use [GPAC Filters](Filters) in NodeJS. @@ -11,7 +11,7 @@ You can also have a look at the [test script](https://github.com/gpac/gpac/tree __Warning GPAC NodeJS bindings are only available starting from GPAC 2.0.__ -# Before you begin +# Before you begin {: data-level="beginner" } The GPAC NodeJS bindings are using [n-api](https://nodejs.org/api/n-api.html) for interfacing with libgpac filter session, while providing an object-oriented wrapper hiding all GPAC C design. @@ -82,7 +82,7 @@ Running this should print your current GPAC version. A test program [gpac.js](https://github.com/gpac/gpac/blob/master/share/nodejs/test/gpac.js) exercising most of the NodeJS GPAC bindings is available in `gpac/share/nodejs/test` -# Tuning up GPAC +# Tuning up GPAC {: data-level="beginner" } The first thing to do is to initialize libgpac. This is done by default while importing the bindings with the following settings: @@ -117,7 +117,7 @@ gpac.set_logs("dash@info"); ``` -# Setting up filter sessions +# Setting up filter sessions {: data-level="beginner" } ## Simple sessions To create a filter session, the simplest way is to use all defaults value, creating a single-threaded blocking session: @@ -254,7 +254,7 @@ Note that (as in GPAC JS or Python) properties referring to constant values are - AudioFormat: string containing the audio format name -# Custom Filters +# Custom Filters {: data-level="expert" } You can define your own filter(s) to interact with the media pipeline. As usual in GPAC filters, a custom filter can be a source, a sink or any other filter. It can consume packets from input PIDs and produce packets on output PIDs. @@ -437,7 +437,7 @@ fs.run(); -# Custom GPAC callbacks +# Custom GPAC callbacks {: data-level="expert" } Some callbacks from libgpac are made available in NodeJS ## Remotery interaction @@ -716,7 +716,7 @@ _NOTE When running the session in multi-thread mode, file IO callbacks are alway -# Multithread support +# Multithread support {: data-level="expert" } Multithreaded filter sessions can be used with NodeJS, however the binding currently only supports executing callbacks into NodeJS from the main thread (main NodeJS or worker). diff --git a/docs/Howtos/pipes.md b/docs/Howtos/pipes.md index d2642abb..6d1f49e3 100644 --- a/docs/Howtos/pipes.md +++ b/docs/Howtos/pipes.md @@ -1,10 +1,10 @@ -# Overview +# Overview {: data-level="all" } We discuss here how to use pipes in GPAC. GPAC supports data piping on Linux, OSX and Windows. The pipe is unidirectional, and can be used as source or as destination. -# Input pipe +# Input pipe {: data-level="beginner" } ## Simple reception of data @@ -36,7 +36,7 @@ The above command will run forever, since broken pipe messages are ignored. You There is currently no way to signal from the sender that the session should be closed, we might add this feature in the near future. -# Output pipe +# Output pipe Assume you have an app that consumes AVC|H264 in Annex B format from a pipe `myavcpipe`. You can direct GPAC output to this pipe: diff --git a/docs/Howtos/playlist.md b/docs/Howtos/playlist.md index 05a23824..82c0468e 100644 --- a/docs/Howtos/playlist.md +++ b/docs/Howtos/playlist.md @@ -1,4 +1,4 @@ -# Overview +# Overview {: data-level="all" } We discuss here how to use the [flist](flist) filter to deal with playlists in GPAC. @@ -10,7 +10,7 @@ In both modes, when switching sources, the filter will match streams (PIDs) base The filter will move to the next item once all PIDs are done playing. It will then adjust the timeline of the following source by repositioning the new source smallest initial timestamp to the greatest time (timestamp+duration) of the last source. -# File list mode +# File list mode {: data-level="beginner" } ``` gpac flist:srcs=file.mp4:floop=-1 vout @@ -32,7 +32,7 @@ gpac flist:srcs=images/*.png:fdur=1:fsort=date -o slide.mp4 ``` The above command will gather all files with extension `png` in directory `images` ordered by their file creation date, each image lasting for 1 second, and output as a PNG track in MP4 format. -# Playlist mode +# Playlist mode ## General usage The playlist mode allows you to build complex source sequences. Think of it as piping the output of several sequential `gpac` executions into a consuming `gpac` instance. diff --git a/docs/Howtos/raw-formats.md b/docs/Howtos/raw-formats.md index 2adbdb7b..8070e72b 100644 --- a/docs/Howtos/raw-formats.md +++ b/docs/Howtos/raw-formats.md @@ -1,9 +1,9 @@ -# Overview +# Overview {: data-level="all" } We discuss here how to work with RAW, uncompressed audio and video data in GPAC. -# RAW Video +# RAW Video {: data-level="beginner" } ## Extracting raw video from a file @@ -89,7 +89,7 @@ This will resize and extract the video frames from start time 10s until end rang The above command will load a raw YUV420 planar 8-bits file using a resolution of 128x128 pixels, and display it using the [video output](vout) filter. The default frame rate is 25, as indicated in [the raw video reframer](rfrawvid). -# RAW Audio +# RAW Audio {: data-level="beginner" } This is very similar to raw video. ## Extracting raw audio from a file @@ -109,7 +109,7 @@ The above command will dump the audio content from `source.mp4` into a 16-bit li The above command will load a raw 16-bit little endian PCM file using 44100 Hz sample rate, and play it using the [audio output](aout) filter. The default channel count is 2, as indicated in [the raw audio reframer](rfpcm). -# RAW Audio and Video +# RAW Audio and Video {: data-level="beginner" } ## Direct extraction ```gpac -i source.mp4 -o test.pcm -o test.yuv``` diff --git a/docs/Howtos/sockets.md b/docs/Howtos/sockets.md index 86750e59..89241790 100644 --- a/docs/Howtos/sockets.md +++ b/docs/Howtos/sockets.md @@ -1,4 +1,4 @@ -# Overview +# Overview {: data-level="all" } We discuss here how to use sockets in GPAC for generic IO. diff --git a/docs/data/keywords.json b/docs/data/keywords.json new file mode 100644 index 00000000..91a9e283 --- /dev/null +++ b/docs/data/keywords.json @@ -0,0 +1,142 @@ + +{ + "definitions": { + "SOURCE": { + "description": "In GPAC, a source is an input element that provides media data to be processed. It is typically the starting point of a media processing pipeline.", + "level": "beginner" + }, + "SINK": { + "description": "A sink in GPAC is an output element that receives processed media data. It is usually the endpoint of a media processing pipeline.", + "level": "beginner" + }, + "INPUT": { + "description": "An input refers to any data fed into the GPAC processing system, including video, audio, and other multimedia streams.", + "level": "beginner" + }, + "OUTPUT": { + "description": "Output in GPAC refers to the processed data that is generated after media processing, ready for storage or display.", + "level": "beginner" + }, + "PID": { + "description": "PID stands for Packet Identifier. It is used in GPAC to uniquely identify different streams within a media file, such as audio, video, and subtitles.", + "level": "expert" + }, + "PACKETS": { + "description": "Packets are units of data transmitted over a network or stored in a media file. In GPAC, packets are processed to extract media streams.", + "level": "beginner" + }, + "DATA": { + "description": "Data in GPAC refers to the raw media content, including video frames, audio samples, and subtitles, that is processed and manipulated.", + "level": "beginner" + }, + "STREAM": { + "description": "A stream is a sequence of media data (audio, video, etc.) that is processed by GPAC. Streams are identified by PIDs and can be multiplexed or demultiplexed.", + "level": "beginner" + }, + "MEDIA": { + "description": "Media refers to various types of content such as video, audio, and images. GPAC processes different media types to enable playback, editing, and streaming.", + "level": "beginner" + }, + "FRAME": { + "description": "A frame is a single image in a sequence of images that make up a video. GPAC processes frames during video encoding and decoding.", + "level": "beginner" + }, + "OPTION": { + "description": "Options in GPAC are parameters that configure the behavior of filters and processing tasks. They can be set to customize media processing.", + "level": "beginner" + }, + "PROPERTY": { + "description": "Properties in GPAC refer to attributes of media streams or filters, such as resolution, bitrate, and codec type, that affect processing.", + "level": "all" + }, + "CODEC": { + "description": "A codec is a software or hardware tool that encodes or decodes media data. GPAC supports various codecs for compressing and decompressing media streams.", + "level": "beginner" + }, + "SESSION": { + "description": "A session in GPAC is a context for managing the lifecycle of media processing tasks, including loading, configuring, and executing filters.", + "level": "expert" + }, + "LINK": { + "description": "Links in GPAC connect different filters and processing elements, forming a processing chain or graph. They facilitate the flow of media data.", + "level": "beginner" + }, + "CONNECTIONS": { + "description": "Connections in GPAC refer to the relationships between filters in a processing graph. They define how data flows from one filter to another.", + "level": "beginner" + }, + "CHAIN": { + "description": "A chain in GPAC is a sequence of connected filters that process media data in a specific order. Chains define the processing pipeline.", + "level": "beginner" + }, + "GRAPH": { + "description": "A graph in GPAC represents the entire media processing workflow, consisting of nodes (filters) and edges (connections) that define data flow.", + "level": "all" + }, + "DECODING": { + "description": "Decoding is the process of converting compressed media data back into its original format. GPAC uses various codecs to decode audio and video streams.", + "level": "beginner" + }, + "REFRAMER": { + "description": "A reframer in GPAC adjusts the timing and structure of media frames to meet specific requirements, such as alignment for streaming.", + "level": "expert" + }, + + "ENCRYPT": { + "description": "Encryption in GPAC refers to the process of securing media content by converting it into a code to prevent unauthorized access. GPAC supports various encryption standards like CENC and ISMA.", + "level": "expert" + }, + "ENCODE": { + "description": "Encoding in GPAC is the process of converting raw media data into a compressed format using codecs. This is essential for reducing file sizes and preparing media for streaming or storage.", + "level": "all" + }, + "MP4": { + "description": "MP4 is a standardized multimedia file format for storing video, audio, subtitles and still images. It is widely used for streaming and media storage.", + "level": "beginner" + }, + + "DASH": { + "description": "DASH stands for Dynamic Adaptive Streaming over HTTP, a technique that enables high-quality streaming of media content over the internet.", + "level": "all" + } + , + "SCENE": { + "description": "Scene in GPAC refers to a multimedia presentation, including video, audio, and interactive elements, typically described using BIFS or X3D.", + "level": "all" + }, + "MPD": { + "description": "MPD stands for Media Presentation Description, a file that provides metadata about the media content in a DASH session.", + "level": "beginner" + }, + + "BOX": { + "description": "A box in GPAC refers to a container or structure within a media file format, such as an MP4 box, used to store metadata and media data.", + "level": "beginner" + } + , + "RAW": { + "description": "Raw in GPAC refers to uncompressed media data that has not been processed or encoded.", + "level": "beginner" + }, + "GROUP": { + "description": "A group in GPAC refers to a collection of related items, such as tracks or filters, that are processed or managed together.", + "level": "beginner" + }, + "PROFILE": { + "description": "A profile in GPAC refers to a set of predefined settings or configurations for media processing tasks, such as encoding profiles.", + "level": "beginner" + } + , + "XML": { + "description": "XML stands for eXtensible Markup Language, used in GPAC for storing and exchanging metadata and configuration settings.", + "level": "all" + } + , + "TILE": { + "description": "A tile in GPAC refers to a rectangular section of a video frame, used in tiling and parallel processing of video content.", + "level": "all" + } + + + } +} \ No newline at end of file diff --git a/docs/glossary.md/encode.md b/docs/glossary.md/encode.md new file mode 100644 index 00000000..a7c3f45d --- /dev/null +++ b/docs/glossary.md/encode.md @@ -0,0 +1,35 @@ + +`encode` is a function that allows you to encode multimedia files into various formats using specified codecs. + +## Reference + +### +```bash +encode(input_file, output_file, codec)` +``` +## Usage + +- **Encoding video files** +- **Encoding audio files** +- **Transcoding multimedia streams to different formats** +- **Setting encoding parameters such as bitrate and resolution** + +## Troubleshooting + +### The output file is not playing correctly +- Verify that the codec specified is compatible with the input multimedia file. + +### Encoding is very slow +- Ensure your system has sufficient resources and consider reducing the quality or complexity of the encoding settings. + +## Example + +```bash +encode("input.mp4", "output.mp4", "libx264") +``` + +## Parameters + +- **input_file**: Path to the multimedia file to be encoded. +- **output_file**: Path where the encoded file will be saved. +- **codec**: Codec to be used for encoding (e.g., libx264 for H.264 encoding). \ No newline at end of file diff --git a/docs/glossary.md/encrypt.md b/docs/glossary.md/encrypt.md new file mode 100644 index 00000000..69cf5a7b --- /dev/null +++ b/docs/glossary.md/encrypt.md @@ -0,0 +1,39 @@ + + + +`encrypt` is a function that allows you to encrypt multimedia files using specified encryption keys. + +```bash +encrypt(input_file, output_file, key_file) +``` + +#### Reference +```bash +encrypt("input.mp4", "output.mp4", "keyfile.xml") +``` + +#### Usage + +- Encrypting video files +- Encrypting audio files +- Encrypting live multimedia streams +- Managing encryption keys + +#### Troubleshooting + +- **My output file is corrupted after encryption** + - Verify that the key file is correct and compatible with the input multimedia file. +- **Permission error when accessing the key file** + - Ensure you have the necessary permissions to read the key file. + +#### Example + +```bash +encrypt("input.mp4", "output.mp4", "keyfile.xml") +``` + +#### Parameters + +- **input_file**: Path to the multimedia file to be encrypted. +- **output_file**: Path where the encrypted file will be saved. +- **key_file**: Path to the key file used for encryption. \ No newline at end of file diff --git a/docs/javascripts/cache.js b/docs/javascripts/cache.js new file mode 100644 index 00000000..087a88ff --- /dev/null +++ b/docs/javascripts/cache.js @@ -0,0 +1,8 @@ +function getCache(key) { + let cache = localStorage.getItem(key); + return cache ? JSON.parse(cache) : {}; +} + +function setCache(key, value) { + localStorage.setItem(key, JSON.stringify(value)); +} \ No newline at end of file diff --git a/docs/javascripts/domManipulation.js b/docs/javascripts/domManipulation.js new file mode 100644 index 00000000..1ac9b347 --- /dev/null +++ b/docs/javascripts/domManipulation.js @@ -0,0 +1,119 @@ +//Handle toogle button to switch between NAV and TOC + +document.addEventListener("DOMContentLoaded", function () { + const toggleButton = document.getElementById("toggle-button"); + const tocContent = document.getElementById("toc-content"); + const navContent = document.getElementById("nav-content"); + let isNavIsVisible = true; + + toggleButton.addEventListener("click", function () { + + if (isNavIsVisible) { + navContent.style.display = "none"; + tocContent.style.display = "block"; + } else { + navContent.style.display = "block"; + tocContent.style.display = "none"; + } + isNavIsVisible = !isNavIsVisible; + }); + + tocContent.style.display = "none"; + navContent.style.display = "block"; +}); + +document.addEventListener("DOMContentLoaded", function () { + if (window.location.pathname.includes("/glossary/")) { + document.body.classList.add("glossary-page"); + } +}); + +// Collapse sections + +document.addEventListener("DOMContentLoaded", function () { + + const articleInner = document.querySelector('.md-content__inner'); + const h1Element = articleInner.querySelector('h1'); + const feedbackForm = articleInner.querySelector('.md-feedback'); + + function handleAllSection(section, h2) { + if (section.classList.contains('active')) { + section.setAttribute('data-was-active', 'true'); + } else { + section.removeAttribute('data-was-active'); + } + } + + if (h1Element && feedbackForm) { + + const articleContentDiv = document.createElement('div'); + articleContentDiv.classList.add('article-content'); + + const fragment = document.createDocumentFragment(); + + let sibling = h1Element.nextElementSibling; + while (sibling && sibling !== feedbackForm) { + const nextSibling = sibling.nextElementSibling; + fragment.appendChild(sibling); + sibling = nextSibling; + } + + articleContentDiv.appendChild(fragment); + h1Element.insertAdjacentElement('afterend', articleContentDiv); + } + + const articleContent = document.querySelector('.article-content'); + + if (articleContent) { + const h2Elements = articleContent.querySelectorAll('h2'); + + h2Elements.forEach(h2 => { + const content = []; + let sibling = h2.nextElementSibling; + + while (sibling && sibling.tagName !== 'H2') { + content.push(sibling); + sibling = sibling.nextElementSibling; + } + + let collapseSection = document.createElement('div'); + collapseSection.classList.add('collapse-section'); + + const collapseContent = document.createElement('div'); + collapseContent.classList.add('collapse-content'); + content.forEach(element => collapseContent.appendChild(element)); + + h2.parentNode.insertBefore(collapseSection, h2); + collapseSection.appendChild(h2); + collapseSection.appendChild(collapseContent); + + if (!h2.querySelector('.collapse-icon')) { + const collapseIcon = document.createElement('span'); + collapseIcon.classList.add('collapse-icon'); + collapseIcon.innerHTML = ''; + h2.appendChild(collapseIcon); + } + + h2.addEventListener('click', function () { + collapseSection.classList.toggle('active'); + if (h2.dataset.level === 'all') { + handleAllSection(collapseSection, h2); + } + }); + }); + } +}); +//Handle "all" sections + +function initializeAllSections() { + const allSections = document.querySelectorAll('.collapse-section'); + allSections.forEach(section => { + const h2Element = section.querySelector('h2'); + if (h2Element && h2Element.dataset.level === 'all') { + section.classList.add('active'); + section.setAttribute('data-was-active', 'true'); + } + }); +} + +document.addEventListener("DOMContentLoaded", initializeAllSections); \ No newline at end of file diff --git a/docs/javascripts/fetchFunctions.js b/docs/javascripts/fetchFunctions.js new file mode 100644 index 00000000..7242954e --- /dev/null +++ b/docs/javascripts/fetchFunctions.js @@ -0,0 +1,59 @@ +function fetchKeywords(currentPageMdPath, cachedKeywords, cachedDefinitions) { + fetch('/data/keywords.json') + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); + }) + .then(data => { + const allDefinitions = data.definitions; + const lexique = Object.keys(allDefinitions); + findKeywordsInContent(currentPageMdPath, lexique, (filteredKeywords) => { + cachedKeywords[currentPageMdPath] = filteredKeywords; + setCache('keywordsCache', cachedKeywords); + const selectedLevel = localStorage.getItem('userLevel') || 'beginner'; + displayKeywords(filteredKeywords, cachedDefinitions, allDefinitions, selectedLevel); + }); + }) + .catch(error => console.error('Error fetching keywords:', error)); +} + +function fetchDefinitions(keyword, cachedDefinitions) { + fetch('/data/keywords.json') + .then(response => response.json()) + .then(data => { + const definition = data.definitions[keyword]; + if (definition) { + cachedDefinitions[keyword] = definition; + setCache('definitionsCache', cachedDefinitions); + openModal(keyword, definition); + } else { + console.error('Definition not found for keyword:', keyword); + } + }) + .catch(error => console.error('Error fetching definition:', error)); +} + +//Get the Markdown content +function fetchMarkdownContent(currentPageMdPath) { + return fetch(currentPageMdPath) + .then(response => { + return response.text(); + }) + .then(htmlContent => { + const parser = new DOMParser(); + const doc = parser.parseFromString(htmlContent, 'text/html'); + const mdContent = doc.querySelector('.md-content[data-md-component="content"]'); + if (mdContent) { + return mdContent.textContent; + } else { + console.warn(`Content element not found in the parsed HTML`); + return ''; + } + }) + .catch(error => { + console.error('Error fetching markdown content:', error); + throw error; + }); +} \ No newline at end of file diff --git a/docs/javascripts/keywordsDisplay.js b/docs/javascripts/keywordsDisplay.js new file mode 100644 index 00000000..e530fd69 --- /dev/null +++ b/docs/javascripts/keywordsDisplay.js @@ -0,0 +1,51 @@ +function displayKeywords(keywords, cachedDefinitions, allDefinitions, selectedLevel) { + const wordCloudElement = document.querySelector('.words-cloud'); + const wordCloudList = document.getElementById('dynamic-words-cloud'); + wordCloudList.innerHTML = ''; + + const sizes = ['size-1', 'size-2', 'size-3', 'size-4', 'size-5']; + const colors = ['color-1', 'color-2', 'color-3', 'color-4']; + + let displayedKeywordsCount = 0; + + const totalRelevantKeywords = keywords.filter(keyword => { + const definition = allDefinitions[keyword]; + return definition && (definition.level === selectedLevel || definition.level === 'all'); + }).length; + + keywords.forEach((keyword, index) => { + const definition = allDefinitions[keyword]; + + if (definition && (definition.level === selectedLevel || definition.level === 'all')) { + displayedKeywordsCount++; + + const li = document.createElement('li'); + const a = document.createElement('a'); + a.href = "#"; + a.textContent = keyword; + a.className = sizes[index % sizes.length] + ' ' + colors[index % colors.length]; + + a.addEventListener('click', function (event) { + event.preventDefault(); + if (cachedDefinitions[keyword]) { + openModal(keyword, cachedDefinitions[keyword]); + } else { + fetchDefinitions(keyword, cachedDefinitions); + } + }); + + li.appendChild(a); + wordCloudList.appendChild(li); + } + }); + + if (displayedKeywordsCount > 0) { + wordCloudElement.classList.remove('hidden'); + } else { + wordCloudElement.classList.add('hidden'); + } + + if (displayedKeywordsCount < totalRelevantKeywords) { + console.warn(`Some relevant keywords (${totalRelevantKeywords - displayedKeywordsCount}) could not be displayed.`); + } +} \ No newline at end of file diff --git a/docs/javascripts/keywordsFinder.js b/docs/javascripts/keywordsFinder.js new file mode 100644 index 00000000..8a7c1a6b --- /dev/null +++ b/docs/javascripts/keywordsFinder.js @@ -0,0 +1,30 @@ +//delete links in markdown content +function cleanMarkdownContent(content) { + return content.replace(/\[[^\]]*\]\([^)]*\)/g, ''); +} + +function cleanWord(word) { + return word.replace(/[.,!?(){}[\]`-]/g, '').toUpperCase(); +} + +function findKeywordsInContent(currentPageMdPath, lexique, callback) { + fetchMarkdownContent(currentPageMdPath) + .then(content => { + const cleanContent = cleanMarkdownContent(content); + const wordCounts = {}; + + const words = cleanContent.split(/\s+/); + + words.forEach(word => { + const cleanedWord = cleanWord(word); + if (lexique.includes(cleanedWord)) { + wordCounts[cleanedWord] = (wordCounts[cleanedWord] || 0) + 1; + } + }); + + const filteredKeywords = Object.keys(wordCounts).filter(word => wordCounts[word] >= 2); + + callback(filteredKeywords); + }) + .catch(error => console.error('Error fetching markdown content:', error)); +} \ No newline at end of file diff --git a/docs/javascripts/levels.js b/docs/javascripts/levels.js new file mode 100644 index 00000000..87d2cff8 --- /dev/null +++ b/docs/javascripts/levels.js @@ -0,0 +1,173 @@ +let levelSwitch, switchLabel, currentPageMdPath; + +function initializeLevelManagement() { + levelSwitch = document.getElementById("level-switch"); + switchLabel = document.querySelector(".switch-label"); + const savedLevel = localStorage.getItem("userLevel") || "expert"; + + levelSwitch.checked = savedLevel === "expert"; + updateSwitchLabel(); + + addLevelTags(); + + filterContent(savedLevel); + updateTOCVisibility(savedLevel); + currentPageMdPath = getCurrentPageMdPath(); + levelSwitch.addEventListener("change", handleLevelChange); +} + + +function getCurrentPageMdPath() { + let currentPagePath = window.location.pathname; + if (currentPagePath.endsWith("/")) { + currentPagePath = currentPagePath.slice(0, -1); + } + return currentPagePath.replace(".html", ".md"); +} +function handleLevelChange() { + const selectedLevel = levelSwitch.checked ? "expert" : "beginner"; + localStorage.setItem("userLevel", selectedLevel); + updateSwitchLabel(); + filterContent(selectedLevel); + updateTOCVisibility(selectedLevel); + + let cachedKeywords = getCache("keywordsCache"); + let cachedDefinitions = getCache("definitionsCache"); + fetchKeywords(currentPageMdPath, cachedKeywords, cachedDefinitions); +} +//Display the level of the user +function updateSwitchLabel() { + switchLabel.textContent = levelSwitch.checked ? "Expert" : "Beginner"; +} + +function addLevelTags() { + const allContent = document.querySelectorAll("[data-level]"); + allContent.forEach((element) => { + const level = element.dataset.level; + if (level) { + const existingTag = element.querySelector(".level-tag"); + if (!existingTag) { + const tag = document.createElement("span"); + tag.className = `level-tag level-${level}`; + tag.textContent = level.charAt(0).toUpperCase() + level.slice(1); + element.insertBefore(tag, element.firstChild); + } + } + }); +} +function isHowtosSection() { + return window.location.pathname.includes('/Howtos/'); +} +function filterContent(level) { + const articleContent = document.querySelector('.article-content'); + if (!articleContent) return; + + const sections = articleContent.querySelectorAll('.collapse-section'); + + if (isHowtosSection()) { + const hasBeginnerSections = Array.from(sections).some(section => + section.querySelector('h2[data-level="beginner"]') + ); + + sections.forEach(section => { + const h2Element = section.querySelector('h2'); + const sectionLevel = h2Element ? h2Element.dataset.level : null; + const isAllSection = sectionLevel === 'all'; + const span = section.querySelector('.level-tag'); + + if (level === 'expert' || isAllSection) { + handleExpertOrAllSection(section, span, level, isAllSection); + } else if (level === 'beginner') { + if (hasBeginnerSections) { + handleBeginnerSectionWithTags(section, sectionLevel, isAllSection, span); + } else { + handleBeginnerSectionWithoutTags(section, sectionLevel, isAllSection, span); + } + } + }); + } else { + // For sections that are not in "Howtos" section + sections.forEach(section => { + section.classList.remove('hidden-level'); + const span = section.querySelector('.level-tag'); + if (span) span.style.display = 'none'; + }); + } +} + +function handleBeginnerSectionWithTags( + section, + sectionLevel, + isAllSection, + span +) { + if (sectionLevel === "beginner" || isAllSection) { + section.classList.remove("hidden-level"); + if (span) { + span.style.display = sectionLevel === "beginner" ? "" : "none"; + } + } else { + section.classList.add("hidden-level"); + } +} + +function handleBeginnerSectionWithoutTags( + section, + sectionLevel, + isAllSection, + span +) { + section.classList.remove("hidden-level"); + if (span) { + span.style.display = "none"; + } +} + +//Remove tag "expert" and "all" +function handleExpertOrAllSection(section, span, level, isAllSection) { + section.classList.remove("hidden-level"); + if (span && (level === "expert" || isAllSection)) { + span.style.display = "none"; + } +} +function updateTOCVisibility(level) { + if (!isHowtosSection()) { + // Hors section Howtos, tout afficher + document.querySelectorAll(".md-nav__item, .md-nav").forEach((item) => { + item.style.removeProperty('display'); + }); + return; + } + + const tocItems = document.querySelectorAll(".md-nav__list > .md-nav__item"); + + tocItems.forEach((item) => { + const link = item.querySelector(":scope > a.md-nav__link"); + if (link) { + const subNav = item.querySelector('.md-nav'); + const subItems = subNav ? subNav.querySelectorAll('.md-nav__item') : []; + + if (level === "expert") { + // En mode expert, tout afficher + item.style.removeProperty('display'); + subItems.forEach(subItem => subItem.style.removeProperty('display')); + } else { + // En mode beginner + item.style.removeProperty('display'); // Toujours afficher les éléments de premier niveau + + // Cacher les sous-éléments + subItems.forEach(subItem => subItem.style.display = 'none'); + } + } + }); +} +document.addEventListener("DOMContentLoaded", function () { + initializeLevelManagement(); + const savedLevel = localStorage.getItem("userLevel") || "expert"; + updateTOCVisibility(savedLevel); + + let cachedKeywords = getCache("keywordsCache"); + let cachedDefinitions = getCache("definitionsCache"); + + fetchKeywords(currentPageMdPath, cachedKeywords, cachedDefinitions); +}); diff --git a/docs/javascripts/main.js b/docs/javascripts/main.js new file mode 100644 index 00000000..0a097e1f --- /dev/null +++ b/docs/javascripts/main.js @@ -0,0 +1,24 @@ +document.addEventListener('DOMContentLoaded', function () { + let cachedKeywords = getCache('keywordsCache'); + let cachedDefinitions = getCache('definitionsCache'); + + let currentPagePath = window.location.pathname; + + if (currentPagePath.endsWith('/')) { + currentPagePath = currentPagePath.slice(0, -1); + } + + const currentPageMdPath = currentPagePath.replace('.html', '.md'); + + fetchKeywords(currentPageMdPath, cachedKeywords, cachedDefinitions); + +}); + +document.addEventListener('DOMContentLoaded', function() { + initializeLevelManagement(); + + let cachedKeywords = getCache('keywordsCache'); + let cachedDefinitions = getCache('definitionsCache'); + + fetchKeywords(currentPageMdPath, cachedKeywords, cachedDefinitions); +}); \ No newline at end of file diff --git a/docs/javascripts/modalFunctions.js b/docs/javascripts/modalFunctions.js new file mode 100644 index 00000000..d3751cc4 --- /dev/null +++ b/docs/javascripts/modalFunctions.js @@ -0,0 +1,55 @@ +function openModal(keyword, definition) { + + + const modal = document.getElementById("modal"); + const modalTitle = document.getElementById("modal-title"); + const modalDefinition = document.getElementById("modal-definition"); + // TODO: navigation to the keyword page is under development + + /* const modalLink = document.getElementById("modal-link"); */ + + if (modalTitle && modalDefinition /* && modalLink */) { + let descriptionText; + if (typeof definition === 'string') { + descriptionText = definition; + } else if (definition && typeof definition === 'object' && definition.description) { + descriptionText = definition.description; + } else { + descriptionText = 'Definition not available'; + } + /* const glossaryPageUrl = `${window.location.origin}/glossary/${keyword.toLowerCase()}/`; */ + + /* Removed the redirection logic */ + modalTitle.textContent = keyword; + modalDefinition.textContent = descriptionText; + /* modalLink.href = `${window.location.origin}/glossary/${keyword.toLowerCase()}/`; */ + modal.classList.remove("hidden"); + modal.style.display = "block"; + /* modalLink.classList.remove("hidden"); */ + modalLink.href = `${window.location.origin}/glossary/${keyword.toLowerCase()}/`; + modal.classList.remove("hidden"); + modal.style.display = "block"; + modalLink.classList.remove("hidden"); + + } else { + console.error('Modal elements not found'); + } +} + +document.addEventListener("DOMContentLoaded", function () { + document.getElementById("close-modal").addEventListener("click", function () { + const modal = document.getElementById("modal"); + modal.classList.add("hidden"); + modal.style.display = "none"; + }); + + document.getElementById("modal").addEventListener("click", function (event) { + if (event.target === event.currentTarget) { + const modal = document.getElementById("modal"); + modal.classList.add("hidden"); + modal.style.display = "none"; + } + }); + + window.openModal = openModal; +}); \ No newline at end of file diff --git a/docs/stylesheets/collapse_section.css b/docs/stylesheets/collapse_section.css new file mode 100644 index 00000000..45aef5e0 --- /dev/null +++ b/docs/stylesheets/collapse_section.css @@ -0,0 +1,103 @@ +.collapse-section { + margin-bottom: 1.5em; + border-radius: 8px; + overflow: hidden; + transition: box-shadow 0.1s ease; + box-shadow: 0 0 0 rgba(0, 0, 0, 0); +} + +.collapse-section:hover { + border-color: transparent; + +} + +.collapse-section h2 { + margin: 0; + padding: 1em; + background-color: #ebe8e8; + cursor: pointer; + display: flex; + align-items: center; + justify-content: space-between; + font-size: 1.2em; + color: #333; + transition: background-color 0.3s ease-in-out, color 0.3s ease-in-out; +} + +.collapse-section h2:hover { + background-color: #dddcdc; +} + +.collapse-section.active h2 { + color: #d94412; +} + +[data-md-color-scheme="slate"] .collapse-section { + background-color:#333; +} + + + +[data-md-color-scheme="slate"] .collapse-section h2 { + background-color: #333; + color: #e0e0e0; +} + +[data-md-color-scheme="slate"] .collapse-section h2:hover { + background-color: #3a3a3a; +} + +[data-md-color-scheme="slate"] .collapse-section.active h2 { + color: #ff6e42; +} + +[data-md-color-scheme="slate"] .collapse-section.active h2 { + color: #ff6e42; +} + +.collapse-icon { + transition: transform 0.3s ease; +} + +.collapse-section.active { + max-height: none; +} + +.collapse-section.active .collapse-icon { + transform: rotate(180deg); +} + +.collapse-content { + max-height: 0; + overflow: hidden; + transition: max-height 0.1s ease-in-out; + padding: 0 1em; +} + +.collapse-section.active .collapse-content { + max-height: 100%; + padding: 1em; +} + +[data-md-color-scheme="slate"] .collapse-content { + background-color: #1E2229; + color: #e0e0e0; +} + +.content-wrapper { + display: flex; + flex-direction: column; + min-height: 100vh; + overflow-y: auto; +} + +.article-content { + flex-grow: 1; +} + +.md-footer { + position: sticky; + bottom: 0; + width: 100%; + z-index: 10; +} \ No newline at end of file diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index b56f5af1..f8b6b669 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -1,56 +1,97 @@ #welcome { - text-align: center; - margin-bottom: 1em; -} - -#home-logo { + text-align: center; margin-bottom: 1em; -} - -[data-md-color-scheme="slate"] { - --md-hue: 217; - --md-typeset-a-color: #d94412 !important; -} - -[data-md-color-scheme="gpac"] { - /* header background */ - --md-primary-fg-color: #d7d7d7; - /* header text */ - --md-primary-bg-color: #141414; - - --md-primary-fg-color--light: hsla(0, 0, 95%, 1); - --md-primary-fg-color--dark: hsla(0, 0, 25%, 1); - - --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7); + } + + #home-logo { + margin-bottom: 1em; + } + + [data-md-color-scheme="slate"] { + --md-hue: 217; + --md-typeset-a-color: #d94412 !important; + --md-accent-keywords-color: hsla(0, 0%, 75%, 1) !important; + --md-icon-color: #a7a7a7; - --md-accent-fg-color: #d94412; - --md-accent-fg-color--transparent: #d944121f; - --md-accent-bg-color: #d94412; - --md-accent-bg-color--light: #d94412e0; - - --md-typeset-a-color: #d94412; + --md-glossary-bg-color: #2e2e2e; + --md-glossary-text-color: #e0e0e0; + --md-keyword-cloud-bg-color: #3a3a3a; + --md-keyword-cloud-text-color: #f0f0f0; + } + + [data-md-color-scheme="gpac"] { + /* header background */ + --md-primary-fg-color: #E0E0E0; + /* header text */ + --md-primary-bg-color: #141414; + + --md-primary-fg-color--light: hsla(0, 0%, 95%, 1); + --md-primary-fg-color--dark: hsla(0, 0%, 25%, 1); + + --md-primary-bg-color--light: #F0F0F0; + + --md-accent-fg-color: #d94412; + --md-accent-fg-color--transparent: #d944121f; + --md-accent-keywords-color: hsla(0, 0%, 25%, 1) !important; + --md-accent-bg-color: #d94412; + --md-accent-bg-color--light: #d94412e0; + + --md-typeset-a-color: #d94412; + --md-icon-color: #8e8686; + + /* default is too bright on poorly calibrated screens */ + --md-code-bg-color: #e0e0e0; + + --md-glossary-bg-color: #f5f5f5; + --md-glossary-text-color: #333333; + --md-keyword-cloud-bg-color: #ffffff; + --md-keyword-cloud-text-color: #000000; + + /* force tabs to match gpac.io styles */ + .md-tabs__item .md-tabs__link { + transition: unset !important; + background: none !important; + color: inherit !important; + opacity: 1 !important; + } + + .md-tabs__item:focus, + .md-tabs__item:hover { + background-color: #151515 !important; + color: #fff !important; + } + + .md-tabs__item--active { + color: var(--md-typeset-a-color); + font-style: italic; + } - /* default is too bright on pourly calibrated screens */ - --md-code-bg-color: #e0e0e0; - - /* - force tabs to match gpac.io styles - */ - .md-tabs__item .md-tabs__link { - transition: unset !important; - background: none !important; - color: inherit!important; - opacity: 1 !important; - } - .md-tabs__item:focus, - .md-tabs__item:hover { - background-color: #151515 !important; - color: #fff !important; - } - - .md-tabs__item--active { - color: var(--md-typeset-a-color); - font-style: italic; - } + .md-header__option { + background-color: var(--md-primary-fg-color) !important; + } + } + + .md-typeset .highlight button { + display: block !important; + } + + .md-footer { + flex-shrink: 0; + } + + + .glossary-container, + .words-cloud { + background-color: var(--md-glossary-bg-color); + color: var(--md-glossary-text-color); + } + + .words-cloud-container { + background-color: var(--md-keyword-cloud-bg-color); + color: var(--md-keyword-cloud-text-color); + } + -} + .words-cloud a { + color: var(--md-typeset-a-color); + } \ No newline at end of file diff --git a/docs/stylesheets/feedback.css b/docs/stylesheets/feedback.css new file mode 100644 index 00000000..68536b98 --- /dev/null +++ b/docs/stylesheets/feedback.css @@ -0,0 +1,4 @@ + +.md-feedback__icon.md-icon[data-md-value="1"]:hover svg path { + fill: #45a049; +} \ No newline at end of file diff --git a/docs/stylesheets/glossary-pages.css b/docs/stylesheets/glossary-pages.css new file mode 100644 index 00000000..558ab371 --- /dev/null +++ b/docs/stylesheets/glossary-pages.css @@ -0,0 +1,6 @@ + + +body.glossary-page h1 { + color: #d94412 !important; + font-weight: bold; +} \ No newline at end of file diff --git a/docs/stylesheets/keyword-cloud.css b/docs/stylesheets/keyword-cloud.css new file mode 100644 index 00000000..3a3fa2b9 --- /dev/null +++ b/docs/stylesheets/keyword-cloud.css @@ -0,0 +1,121 @@ +.words-cloud-container { + height: auto; + max-height: 60vh; + overflow-y: auto; + text-align: center; + margin-top: 1rem; + border-radius: 8px; + overflow: hidden; + + } + [data-md-color-scheme="slate"] .words-cloud-container { + background-color: #333; + } + + .words-cloud { + padding: 0.5rem; + background-color: #f4f4f4; + border: 1px solid #e0e0e0; + border-radius: 8px; + margin-right: 1rem; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + transition: background-color 0.3s ease-in-out; + border-radius: 8px; + margin-right: 1rem; + + + } + + [data-md-color-scheme="slate"] .words-cloud { + background-color: #333; + border-color:#333; + box-shadow: 0 1px 2px rgba(255, 255, 255, 0.05); + + } + .words-cloud-container:hover .words-cloud { + background-color: #dddcdc; + + } + + [data-md-color-scheme="slate"] .words-cloud-container:hover .words-cloud { + background-color: #333; + + } + + + .words-cloud__title { + font-size: 14px; + text-transform: uppercase; + text-align: center; + padding: 0.5rem; + margin-bottom: 0.5rem; + color: #333; + border-bottom: 1px solid #e0e0e0; + } + + [data-md-color-scheme="slate"] .words-cloud__title { + color: #e0e0e0; + border-bottom-color: #333; + } + + .words-container { + display: flex; + justify-content: center; + align-items: center; + flex-wrap: wrap; + } + + #dynamic-words-cloud { + display: flex; + justify-content: center; + align-items: center; + flex-wrap: wrap; + margin: 0; + padding: 0; + list-style: none; + } + + .words-cloud li { + display: inline-block; + padding: 0.25rem 0.5rem; + transition: transform 0.2s, color 0.2s; + } + + .words-cloud a { + text-decoration: none; + } + + .words-cloud .size-1 { font-size: 10px; } + .words-cloud .size-2 { font-size: 12px; } + .words-cloud .size-3 { font-size: 14px; } + .words-cloud .size-4 { font-size: 16px; } + .words-cloud .size-5 { font-size: 18px; } + + .words-cloud .color-1 { color: var(--md-accent-fg-color); } + .words-cloud .color-2 { color: #666; } + .words-cloud .color-3 { color: #333; } + .words-cloud .color-4 { color: #d94412; } + + [data-md-color-scheme="slate"] .words-cloud .color-1 { color: var(--md-accent-fg-color); } + [data-md-color-scheme="slate"] .words-cloud .color-2 { color: #bbb; } + [data-md-color-scheme="slate"] .words-cloud .color-3 { color: #ddd; } + [data-md-color-scheme="slate"] .words-cloud .color-4 { color: #ff6633; } + + .words-cloud li:hover { + transform: scale(1.1); + color: var(--md-accent-fg-color); + } + + .modal-link { + display: inline-block; + margin-top: 1rem; + font-size: 14px; + color: var(--md-accent-fg-color); + text-decoration: none; + border-bottom: 1px dotted var(--md-accent-fg-color); + } + + [data-md-color-scheme="slate"] .modal-link { + color: var(--md-accent-fg-color); + border-bottom-color: var(--md-accent-fg-color); + } \ No newline at end of file diff --git a/docs/stylesheets/levels.css b/docs/stylesheets/levels.css new file mode 100644 index 00000000..f0bc2eb1 --- /dev/null +++ b/docs/stylesheets/levels.css @@ -0,0 +1,84 @@ +.hidden-level { + display: none; + } + + .level-tag { + padding: 2px 6px; + border-radius: 4px; + font-size: 0.8em; + font-weight: bold; + margin-right: 5px; + } + + .level-beginner { background-color: #e6f3e6; color: #2e7d32; } + .level-expert { background-color: #ffebee; color: #c62828; } + + [data-md-color-scheme="slate"] .level-beginner { + font-weight: 600; + color: #86efac; + background-color: #14532d + } + + .switch-container { + display: flex; + align-items: center; + + + } + + .switch { + position: relative; + display: inline-block; + width: 60px; + height: 34px; + } + + .switch input { + opacity: 0; + width: 0; + height: 0; + } + + .slider { + position: absolute; + cursor: pointer; + top: 10px; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + transition: .4s; + } + + .slider:before { + position: absolute; + content: ""; + height: 26px; + width: 26px; + left: 4px; + bottom: 0; + background-color: white; + transition: .4s; + } + + input:checked + .slider { + background-color: #d94412; + } + + input:checked + .slider:before { + transform: translateX(30px); + } + + .slider.round { + border-radius: 34px; + } + + .slider.round:before { + border-radius: 50%; + } + + .switch-label { + margin-top: 10px; + margin-left: 10px; + font-weight: bold; + } \ No newline at end of file diff --git a/docs/stylesheets/modal.css b/docs/stylesheets/modal.css new file mode 100644 index 00000000..3b59ed64 --- /dev/null +++ b/docs/stylesheets/modal.css @@ -0,0 +1,83 @@ +.modal { + z-index: 1000; + position: absolute; + display: none; + margin-top: 10px; + width: 100%; + max-width: 400px; + left: 50%; + transform: translateX(-50%); +} + +.modal-content { + background-color:#E0E0E0; + border: 1px solid #e0e0e0; + border-radius: 4px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + padding: 20px; + width: 100%; + transition: opacity 0.25s ease; +} + +.modal-header { + display: flex; + justify-content: center; + align-items: center; + margin-bottom: 16px; +} + +.modal-header i { + color: #d94412; + font-size: 20px; + margin-right: 8px; +} + +.modal-header h2 { + margin: 0; + font-size: 1 rem; + font-weight: bold; + color: #333; +} + +.modal-content__definition { + font-size: 14px; + line-height: 1.6; + color: #4a4a4a; + text-align: justify; + margin-bottom: 16px; +} + +.modal-close { + position: absolute; + top: 8px; + right: 8px; + background: none; + border: none; + font-size: 18px; + cursor: pointer; + color: #666; + transition: color 0.2s ease; +} + +.modal-close:hover { + color: #d94412; +} + +.modal-link { + display: inline-block; + padding: 6px 12px; + background-color: #d94412; + color: #ffffff; + text-decoration: none; + border-radius: 4px; + font-size: 14px; + transition: background-color 0.2s ease; +} + +.modal-link:hover { + background-color: #b23600; +} + +.hidden { + display: none !important; +} \ No newline at end of file diff --git a/docs/stylesheets/toggle.css b/docs/stylesheets/toggle.css new file mode 100644 index 00000000..0b786fdb --- /dev/null +++ b/docs/stylesheets/toggle.css @@ -0,0 +1,69 @@ + +@media screen and (max-width: 76.25em) { + .toggle-btn, + .toggle-btn i{ + display: none; + } +} + +@media screen and (min-width: 76.25em) { + .md-nav--secondary .md-nav__title .md-nav__icon { + display: block; + } +} +@media screen and (min-width: 76.25em) { + .md-nav__icon { + transition: background-color .25s; + width: auto; + } +} + +@media screen and (min-width: 76.25em) { + .md-nav__icon:hover { + background-color: transparent; + } +} + + +.toggle-btn { + margin-bottom: 0.2rem; + background-color: transparent; + color: white; + border: none; + border-radius: 50%; + cursor: pointer; + font-size: 16px; + display: flex; + align-items: center; + justify-content: center; + width: 50%; + z-index: 99999; + transition: background-color 0.3s ease, transform 0.3s ease; +} + +.toggle-btn i:hover { + background-color: #D7D7D7; +} + +.toggle-btn i { + color: #d94412e0; + transition: transform 0.3s ease; +} + +.hidden { + opacity: 0; + visibility: hidden; + transition: opacity 0.5s ease, visibility 0.5s ease; +} + +.visible { + opacity: 1; + visibility: visible; + transition: opacity 0.5s ease, visibility 0.5s ease; +} + + + +.span-toggle { + margin-right: 4px; +} \ No newline at end of file diff --git a/docs/stylesheets/top.css b/docs/stylesheets/top.css new file mode 100644 index 00000000..e3c02d4f --- /dev/null +++ b/docs/stylesheets/top.css @@ -0,0 +1,32 @@ +.md-top.md-icon { + margin-left: 75% !important; + top: 85% !important; + background-color: #fff !important; + color: #8e8686; + transition: background-color 0.3s ease, color 0.3s ease, border 0.3s ease; + } + + .md-top:focus, .md-top:hover { + background-color: #d7d7d7 !important; + color: black !important; + border: 1px solid var(--md-default-fg-color--light) !important; + transition-duration: 300ms !important; + } + + .md-top[hidden] { + transition-duration: 300ms !important; + } + + + [data-md-color-scheme="slate"] .md-top.md-icon { + background-color: #2e2e2e !important; + color: #a7a7a7; + border: 1px solid #444; + } + + [data-md-color-scheme="slate"] .md-top:focus, + [data-md-color-scheme="slate"] .md-top:hover { + background-color: #3a3a3a !important; + color: #ffffff !important; + border: 1px solid #ff6633 !important; + } \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 440a1754..3e94af79 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,10 +1,34 @@ site_name: GPAC wiki site_url: https://wiki.gpac.io/ site_author: GPAC contributors +extra: + keywords_file: 'data/keywords.json' +extra_javascript: + + - javascripts/main.js + - javascripts/levels.js + - javascripts/fetchFunctions.js + - javascripts/domManipulation.js + - javascripts/keywordsFinder.js + - javascripts/keywordsDisplay.js + - javascripts/cache.js + - javascripts/modalFunctions.js + + extra_css: - stylesheets/extra.css + - stylesheets/keyword-cloud.css + - stylesheets/top.css + - stylesheets/toggle.css + - stylesheets/modal.css + - stylesheets/glossary-pages.css + - stylesheets/collapse_section.css + - stylesheets/feedback.css + - stylesheets/levels.css + - https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css theme: name: material + custom_dir: overrides logo: images/gpac-logo.svg favicon: images/favicon-32x32.png palette: @@ -25,8 +49,10 @@ theme: icon: material/brightness-4 name: Switch to light mode features: + - navigation.top - tables - def_list + - content.code.select - content.code.annotate - content.code.copy - content.tooltips @@ -39,6 +65,7 @@ theme: - search.highlight - search.share - search.suggest + - search repo_url: https://github.com/gpac/gpac/ plugins: - search @@ -46,14 +73,17 @@ plugins: hooks: - scripts/mkdocs_hooks.py markdown_extensions: - - admonition - - attr_list - - toc: +- admonition +- attr_list +- toc: permalink: true - - nl2br - - sane_lists - - pymdownx.details - - pymdownx.superfences +- nl2br +- sane_lists +- md_in_html +- pymdownx.details +- pymdownx.superfences +- pymdownx.inlinehilite + extra: analytics: provider: google @@ -356,3 +386,7 @@ nav: - TTXT Format: xmlformats/TTXT-Format-Documentation.md - NHML format: xmlformats/NHML-Format.md - NHNT format: xmlformats/NHNT-Format.md + # TODO: implement keyword pages + # - Glossary: + # - glossary/encode.md + # - glossary/encrypt.md \ No newline at end of file diff --git a/overrides/base.html b/overrides/base.html new file mode 100644 index 00000000..1541680e --- /dev/null +++ b/overrides/base.html @@ -0,0 +1,276 @@ +{#- + This file was automatically generated - do not edit + -#} + {% import "partials/language.html" as lang with context %} + + + + {% block site_meta %} + + + {% if page.meta and page.meta.description %} + + {% elif config.site_description %} + + {% endif %} + {% if page.meta and page.meta.author %} + + {% elif config.site_author %} + + {% endif %} + {% if page.canonical_url %} + + {% endif %} + {% if page.previous_page %} + + {% endif %} + {% if page.next_page %} + + {% endif %} + {% if "rss" in config.plugins %} + + + {% endif %} + + + {% endblock %} + {% block htmltitle %} + {% if page.meta and page.meta.title %} + {{ page.meta.title }} - {{ config.site_name }} + {% elif page.title and not page.is_homepage %} + {{ page.title | striptags }} - {{ config.site_name }} + {% else %} + {{ config.site_name }} + {% endif %} + {% endblock %} + {% block styles %} + + {% if config.theme.palette %} + {% set palette = config.theme.palette %} + + {% endif %} + {% include "partials/icons.html" %} + {% endblock %} + {% block libs %} + {% for script in config.extra.polyfills %} + {{ script | script_tag }} + {% endfor %} + {% endblock %} + {% block fonts %} + {% if config.theme.font != false %} + {% set text = config.theme.font.get("text", "Roboto") %} + {% set code = config.theme.font.get("code", "Roboto Mono") %} + + + + {% endif %} + {% endblock %} + {% for path in config.extra_css %} + + {% endfor %} + {% include "partials/javascripts/base.html" %} + {% block analytics %} + {% include "partials/integrations/analytics.html" %} + {% endblock %} + {% if page.meta and page.meta.meta %} + {% for tag in page.meta.meta %} + + {% endfor %} + {% endif %} + {% block extrahead %}{% endblock %} + + {% set direction = config.theme.direction or lang.t("direction") %} + {% if config.theme.palette %} + {% set palette = config.theme.palette %} + {% if not palette is mapping %} + {% set palette = palette | first %} + {% endif %} + {% set scheme = palette.scheme | d("default", true) %} + {% set primary = palette.primary | d("indigo", true) %} + {% set accent = palette.accent | d("indigo", true) %} + + {% else %} + + {% endif %} + {% set features = config.theme.features or [] %} + + + + +
+ {% if page.toc | first is defined %} + {% set skip = page.toc | first %} + + {{ lang.t("action.skip") }} + + {% endif %} +
+
+ {% if self.announce() %} + + {% endif %} +
+ {% if config.extra.version %} + + {% endif %} + {% block header %} + {% include "partials/header.html" %} + {% endblock %} +
+ {% block hero %}{% endblock %} + {% block tabs %} + {% if "navigation.tabs.sticky" not in features %} + {% if "navigation.tabs" in features %} + {% include "partials/tabs.html" %} + {% endif %} + {% endif %} + {% endblock %} +
+
+ {% block site_nav %} + {% if nav %} + {% if page.meta and page.meta.hide %} + {% set hidden = "hidden" if "navigation" in page.meta.hide %} + {% endif %} + + {% endif %} + {% if "toc.integrate" not in features %} + {% if page.meta and page.meta.hide %} + {% set hidden = "hidden" if "toc" in page.meta.hide %} + {% endif %} + + {% endif %} + {% endblock %} + {% block container %} +
+
+ {% block content %} + {% include "partials/content.html" %} + {% endblock %} +
+
+ {% endblock %} + {% include "partials/javascripts/content.html" %} +
+ + + + {% if "navigation.top" in features %} + {% include "partials/top.html" %} + {% endif %} +
+ {% block footer %} + {% include "partials/footer.html" %} + {% endblock %} +
+
+
+
+ {% if "navigation.instant.progress" in features %} + {% include "partials/progress.html" %} + {% endif %} + {% if config.extra.consent %} + + {% include "partials/javascripts/consent.html" %} + {% endif %} + {% block config %} + {%- set app = { + "base": base_url, + "features": features, + "translations": {}, + "search": "assets/javascripts/workers/search.b8dbb3d2.min.js" | url + } -%} + {%- if config.extra.version -%} + {%- set mike = config.plugins.get("mike") -%} + {%- if not mike or mike.config.version_selector -%} + {%- set _ = app.update({ "version": config.extra.version }) -%} + {%- endif -%} + {%- endif -%} + {%- if config.extra.tags -%} + {%- set _ = app.update({ "tags": config.extra.tags }) -%} + {%- endif -%} + {%- set translations = app.translations -%} + {%- for key in [ + "clipboard.copy", + "clipboard.copied", + "search.result.placeholder", + "search.result.none", + "search.result.one", + "search.result.other", + "search.result.more.one", + "search.result.more.other", + "search.result.term.missing", + "select.version" + ] -%} + {%- set _ = translations.update({ key: lang.t(key) }) -%} + {%- endfor -%} + + {% endblock %} + {% block scripts %} + + + + {% for script in config.extra_javascript %} + {{ script | script_tag }} + {% endfor %} + {% endblock %} + + + + \ No newline at end of file diff --git a/overrides/main.html b/overrides/main.html new file mode 100644 index 00000000..4c8ce422 --- /dev/null +++ b/overrides/main.html @@ -0,0 +1,4 @@ +{#- + This file was automatically generated - do not edit + -#} + {% extends "base.html" %} \ No newline at end of file diff --git a/overrides/partials/header.html b/overrides/partials/header.html new file mode 100644 index 00000000..dfa23cde --- /dev/null +++ b/overrides/partials/header.html @@ -0,0 +1,76 @@ +{#- + This file was automatically generated - do not edit + -#} + {% set class = "md-header" %} + {% if "navigation.tabs.sticky" in features %} + {% set class = class ~ " md-header--shadow md-header--lifted" %} + {% elif "navigation.tabs" not in features %} + {% set class = class ~ " md-header--shadow" %} + {% endif %} +
+
+ + Expert +
+ + + + {% if "navigation.tabs.sticky" in features %} + {% if "navigation.tabs" in features %} + {% include "partials/tabs.html" %} + {% endif %} + {% endif %} +
\ No newline at end of file diff --git a/overrides/partials/keywords-cloud.html b/overrides/partials/keywords-cloud.html new file mode 100644 index 00000000..31e6dbbd --- /dev/null +++ b/overrides/partials/keywords-cloud.html @@ -0,0 +1,31 @@ + + \ No newline at end of file diff --git a/overrides/partials/nav-items.html b/overrides/partials/nav-items.html new file mode 100644 index 00000000..5089a60e --- /dev/null +++ b/overrides/partials/nav-items.html @@ -0,0 +1,152 @@ +{#- + This file was automatically generated - do not edit + -#} + {% macro render_status(nav_item, type) %} + {% set class = "md-status md-status--" ~ type %} + {% if config.extra.status and config.extra.status[type] %} + + + {% else %} + + {% endif %} + {% endmacro %} + {% macro render_content(nav_item, ref = nav_item) %} + {% if nav_item.is_page and nav_item.meta.icon %} + {% include ".icons/" ~ nav_item.meta.icon ~ ".svg" %} + {% endif %} + + {{ ref.title }} + + {% if nav_item.is_page and nav_item.meta.status %} + {{ render_status(nav_item, nav_item.meta.status) }} + {% endif %} + {% endmacro %} + {% macro render_pruned(nav_item, ref = nav_item) %} + {% set first = nav_item.children | first %} + {% if first and first.children %} + {{ render_pruned(first, ref) }} + {% else %} + + {{ render_content(ref) }} + {% if nav_item.children | length > 0 %} + + {% endif %} + + {% endif %} + {% endmacro %} + {% macro render(nav_item, path, level) %} + {% set class = "md-nav__item" %} + {% if nav_item.active %} + {% set class = class ~ " md-nav__item--active" %} + {% endif %} + {% if nav_item.pages %} + {% if page in nav_item.pages %} + {% set nav_item = page %} + {% endif %} + {% endif %} + {% if nav_item.children %} + {% set indexes = [] %} + {% if "navigation.indexes" in features %} + {% for nav_item in nav_item.children %} + {% if nav_item.is_index and not index is defined %} + {% set _ = indexes.append(nav_item) %} + {% endif %} + {% endfor %} + {% endif %} + {% if "navigation.tabs" in features %} + {% if level == 1 and nav_item.active %} + {% set class = class ~ " md-nav__item--section" %} + {% set is_section = true %} + {% endif %} + {% if "navigation.sections" in features %} + {% if level == 2 and nav_item.parent.active %} + {% set class = class ~ " md-nav__item--section" %} + {% set is_section = true %} + {% endif %} + {% endif %} + {% elif "navigation.sections" in features %} + {% if level == 1 %} + {% set class = class ~ " md-nav__item--section" %} + {% set is_section = true %} + {% endif %} + {% endif %} + {% if "navigation.prune" in features %} + {% if not is_section and not nav_item.active %} + {% set class = class ~ " md-nav__item--pruned" %} + {% set is_pruned = true %} + {% endif %} + {% endif %} +
  • + {% if not is_pruned %} + {% set checked = "checked" if nav_item.active %} + {% if "navigation.expand" in features and not checked %} + {% set indeterminate = "md-toggle--indeterminate" %} + {% endif %} + + {% if not indexes %} + {% set tabindex = "0" if not is_section %} + + {% else %} + {% set index = indexes | first %} + {% set class = "md-nav__link--active" if index == page %} + + {% endif %} + + {% else %} + {{ render_pruned(nav_item) }} + {% endif %} +
  • + {% elif nav_item == page %} +
  • + {% set toc = page.toc %} + + {% set first = toc | first %} + {% if first and first.level == 1 %} + {% set toc = first.children %} + {% endif %} + {% if toc %} + + {% endif %} + + {{ render_content(nav_item) }} + + {% if toc %} + {% include "partials/toc.html" %} + {% endif %} +
  • + {% else %} +
  • + + {{ render_content(nav_item) }} + +
  • + {% endif %} + {% endmacro %} \ No newline at end of file diff --git a/overrides/partials/nav.html b/overrides/partials/nav.html new file mode 100644 index 00000000..38a2d63a --- /dev/null +++ b/overrides/partials/nav.html @@ -0,0 +1,31 @@ +{#- + This file was automatically generated - do not edit + -#} + {% import "partials/nav-item.html" as item with context %} + {% set class = "md-nav md-nav--primary" %} + {% if "navigation.tabs" in features %} + {% set class = class ~ " md-nav--lifted" %} + {% endif %} + {% if "toc.integrate" in features %} + {% set class = class ~ " md-nav--integrated" %} + {% endif %} + \ No newline at end of file diff --git a/overrides/partials/search.html b/overrides/partials/search.html new file mode 100644 index 00000000..44a023bf --- /dev/null +++ b/overrides/partials/search.html @@ -0,0 +1,44 @@ + +{#- + This file was automatically generated - do not edit + -#} + + \ No newline at end of file diff --git a/overrides/partials/toc-item.html b/overrides/partials/toc-item.html new file mode 100644 index 00000000..eac8d153 --- /dev/null +++ b/overrides/partials/toc-item.html @@ -0,0 +1,19 @@ +{#- + This file was automatically generated - do not edit + -#} +
  • + + + {{ toc_item.title }} + + + {% if toc_item.children %} + + {% endif %} +
  • \ No newline at end of file diff --git a/overrides/partials/toc.html b/overrides/partials/toc.html new file mode 100644 index 00000000..f28d8392 --- /dev/null +++ b/overrides/partials/toc.html @@ -0,0 +1,28 @@ +{#- + This file was automatically generated - do not edit + -#} + {% set title = lang.t("toc") %} + {% if config.mdx_configs.toc and config.mdx_configs.toc.title %} + {% set title = config.mdx_configs.toc.title %} + {% endif %} + \ No newline at end of file diff --git a/overrides/partials/top.html b/overrides/partials/top.html new file mode 100644 index 00000000..0095af3f --- /dev/null +++ b/overrides/partials/top.html @@ -0,0 +1,8 @@ +{#- + This file was automatically generated - do not edit + -#} + \ No newline at end of file From 537e2abe4f0aef024a55b2fe67b19e9148178612 Mon Sep 17 00:00:00 2001 From: PierreDillard <7gaspard77@gmail.com> Date: Mon, 5 Aug 2024 12:51:02 +0200 Subject: [PATCH 02/28] add content-tagging-system.md --- content-tagging-system.md | 114 +++++++++++++++++++++++++++++++++++++ docs/javascripts/levels.js | 48 +++++++++------- 2 files changed, 141 insertions(+), 21 deletions(-) create mode 100644 content-tagging-system.md diff --git a/content-tagging-system.md b/content-tagging-system.md new file mode 100644 index 00000000..66ec7bce --- /dev/null +++ b/content-tagging-system.md @@ -0,0 +1,114 @@ + + +# GPAC Wiki Content Tagging and Level Switch System + +## Overview + +The GPAC wiki implements a content tagging and level switch system to provide a customized reading experience for users with different levels of expertise. This system allows users to toggle between "Beginner" and "Expert" modes, affecting the visibility of content sections and keywords displayed. + +## Purpose + +The main goals of this system are: +1. To help users new to GPAC access simpler, more approachable content. +2. To provide more in-depth, technical content for experienced users. +3. To create a dynamic reading experience that adapts to the user's knowledge level. + +## Implementation + +### Content Tagging + +Content tagging is implemented in Markdown files using the `attr_list` extension. Tags are applied to H1 headers (single `#` in Markdown) using the following syntax: + +```markdown +# Section Title {: data-level="beginner" } +``` + +Available levels: +- `beginner`: Content suitable for newcomers to GPAC. +- `expert`: More advanced content (default if not specified). +- `all`: Content that appears in both beginner and expert modes. + +### Level Switch + +The level switch is a toggle located in the top-left corner of the user interface. It allows users to switch between "Beginner" and "Expert" modes. + +Default setting: The default level is set to "Expert", displaying all documentation. + +User preference storage: The selected level is stored in the browser's local storage, persisting between sessions. + +## Functionality + +### Content Visibility + +When switching between levels: + +1. Expert mode: + - All sections are visible. + - The full Table of Contents (TOC) is displayed, including all sections and subsections. + +2. Beginner mode: + - Only sections tagged as "beginner" or "all" are visible. + - Sections not explicitly tagged may disappear. + - The TOC displays only visible sections without subsections. + +### Keywords Cloud + +The keywords cloud is dynamic and changes based on the selected level: + +- In Expert mode: All keywords are displayed. +- In Beginner mode: Only keywords tagged as "beginner" or "all" are shown. + +Keywords are defined in the `data/keywords.json` file with the following structure: + +```json +{ + "KEYWORD": { + "description": "Keyword description", + "level": "beginner" + } +} +``` + +When a user clicks on a keyword, a modal appears with the keyword's definition. + +## Implementation Details + +### Key Files + +- `javascripts/levels.js`: Main logic for level switching and content filtering. +- `javascripts/domManipulation.js`: Handles DOM manipulation for showing/hiding content. + +### Howtos Section + +The level switching functionality is currently implemented only in the "Howtos" section of the documentation. + + +## Developer Guidelines + +1. Tagging new content: + - Always tag H1 headers in Markdown files. + - Use the format: `# Title {: data-level="level" }` where `level` is "beginner", "expert", or "all". + - The "all" sections are visible to beginners and experts. + Any section tagged with "all" will have its collapse menu unfolded. + They are often used for "Overview" sections. + - Untagged sections are considered "expert" by default. + +2. Adding new keywords: + - Add new keywords to the `data/keywords.json` file. + - Include a description and appropriate level tag. + +3. Testing: + - Test new content in both Beginner and Expert modes to ensure proper visibility. + - Verify that the TOC updates correctly when switching levels. + - Check that the keywords cloud updates appropriately. + +## Known Limitations + +1. The level switching feature is currently only available in the "Howtos" section. +2. The visibility of untagged sections in Beginner mode may not be consistent across all pages. + +## Future Enhancements + +1. Implement direct linking to keyword pages (e.g., `/glossary/${keyword}`). +2. Extend the level switching functionality to other sections of the documentation. +3. Improve the visibility and user experience of the level switch toggle. diff --git a/docs/javascripts/levels.js b/docs/javascripts/levels.js index 87d2cff8..ad508e6f 100644 --- a/docs/javascripts/levels.js +++ b/docs/javascripts/levels.js @@ -131,32 +131,38 @@ function handleExpertOrAllSection(section, span, level, isAllSection) { } } function updateTOCVisibility(level) { - if (!isHowtosSection()) { - // Hors section Howtos, tout afficher - document.querySelectorAll(".md-nav__item, .md-nav").forEach((item) => { - item.style.removeProperty('display'); + if (level !== 'beginner') { + + document.querySelectorAll('.md-nav__item').forEach(item => { + item.style.display = ''; }); return; } - const tocItems = document.querySelectorAll(".md-nav__list > .md-nav__item"); - - tocItems.forEach((item) => { - const link = item.querySelector(":scope > a.md-nav__link"); + const h2Elements = document.querySelectorAll('h2[data-level]'); + const tocItems = document.querySelectorAll('.md-nav__item'); + + const beginnerIds = new Set(); + const sectionWithSubsections = new Set(); + h2Elements.forEach(h2 => { + if (h2.getAttribute('data-level') === 'beginner') { + beginnerIds.add(h2.id); + } + }); + + + // Hide non-beginner TOC items and show beginner ones. + tocItems.forEach(item => { + const link = item.querySelector('a.md-nav__link'); if (link) { - const subNav = item.querySelector('.md-nav'); - const subItems = subNav ? subNav.querySelectorAll('.md-nav__item') : []; - - if (level === "expert") { - // En mode expert, tout afficher - item.style.removeProperty('display'); - subItems.forEach(subItem => subItem.style.removeProperty('display')); - } else { - // En mode beginner - item.style.removeProperty('display'); // Toujours afficher les éléments de premier niveau - - // Cacher les sous-éléments - subItems.forEach(subItem => subItem.style.display = 'none'); + const href = link.getAttribute('href'); + if (href && href.startsWith('#')) { + const id = href.slice(1); + if (!beginnerIds.has(id)) { + item.style.display = 'none'; + } else { + item.style.display = ''; + } } } }); From 4a033ced081c68ac8fc325a3dfa9bdb5f8f2b66e Mon Sep 17 00:00:00 2001 From: PierreDillard <7gaspard77@gmail.com> Date: Mon, 5 Aug 2024 13:43:12 +0200 Subject: [PATCH 03/28] update content-tagging-system.md with spacing between code blocks --- content-tagging-system.md | 22 ++++++++++++++++++++++ docs/Filters/avmix.md | 9 ++++++++- docs/images/bad_format.png | Bin 0 -> 30089 bytes docs/images/good_format.png | Bin 0 -> 29003 bytes 4 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 docs/images/bad_format.png create mode 100644 docs/images/good_format.png diff --git a/content-tagging-system.md b/content-tagging-system.md index 66ec7bce..38130513 100644 --- a/content-tagging-system.md +++ b/content-tagging-system.md @@ -102,6 +102,28 @@ The level switching functionality is currently implemented only in the "Howtos" - Verify that the TOC updates correctly when switching levels. - Check that the keywords cloud updates appropriately. +## Best Practices for Content Formatting + +When adding or editing content in the GPAC wiki, it's crucial to follow certain formatting practices to ensure readability and proper rendering of the documentation. One key aspect to pay attention to is the spacing between code blocks and other elements. + +### Spacing Between Code Blocks + +Always add a blank line between code blocks and surrounding text. This practice prevents rendering issues and improves readability. + +#### Good Practice: + +Example: + +![Code block formatting issue](docs/images/good_format.png) + +#### Bad Practice: + +Avoid this: +Example: + +![Code block formatting issue](docs/images/bad_format.png) + + ## Known Limitations 1. The level switching feature is currently only available in the "Howtos" section. diff --git a/docs/Filters/avmix.md b/docs/Filters/avmix.md index 3c331ec6..3d138d5c 100644 --- a/docs/Filters/avmix.md +++ b/docs/Filters/avmix.md @@ -231,12 +231,14 @@ Example ``` in=ipid://#foo=bar ``` + This will use pids having property `foo` with value `bar`, regardless of source filter ID. Example ``` in=ipid://TEST#foo=bar ``` + This will use pids having property `foo` with value `bar` coming from filter with ID `TEST`. When using the `ipid://` scheme, filter chains cannot be specified (in accepts a single argument) and `port` is ignored. @@ -548,6 +550,7 @@ Example ``` { "script": "let s=get_scene('s1'); let rot = s.get('rotation'); rot += 10; s.set('rotation', rot); return 2;" } ``` + This will change scene `s1` rotation every 2 seconds ## Watchers @@ -584,12 +587,14 @@ Example ``` {'watch': 's1@rotation', 'target': 's2@rotation'} ``` + This will copy s1.rotation to s2.rotation. Example ``` {'watch': 's1@rotation', 'target': 'get_scene('s2').set('rotation', -value); } -``` +``` + This will copy the -1*s1.rotation to s2.rotation. ### Watching UI events @@ -611,6 +616,7 @@ Example ``` {'watch': 'mousemove', 'target': 'let s = mouse_over(evt); get_scene('s2').set('fill', (s && (s.id=='s1') ? 'white' : 'black' );'} ``` + This will set s1 fill color to white of mouse is over s2 and to black otherwise. ## Styles @@ -879,6 +885,7 @@ Example ``` "shape": "this.path.add_rectangle(0, 0, this.width, this.height); let el = new evg.Path().ellipse(0, 0, this.width, this.height/3); this.path.add_path(el); this.tx_adjust = true;" ``` + In this example, the texture mapping will be adjusted to the desired size. The global variables and functions are available (c.f. `gpac -h avmix:global`): diff --git a/docs/images/bad_format.png b/docs/images/bad_format.png new file mode 100644 index 0000000000000000000000000000000000000000..21cc8d56d325b7ffc6d08a493d6d300f80b7ccbb GIT binary patch literal 30089 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 literal 0 HcmV?d00001 diff --git a/docs/images/good_format.png b/docs/images/good_format.png new file mode 100644 index 0000000000000000000000000000000000000000..c8585317b86b454e38a4cc08a7b5337964e059d1 GIT binary patch literal 29003 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 Hqmcgty? Date: Mon, 5 Aug 2024 15:29:49 +0200 Subject: [PATCH 04/28] tagging howtos section --- docs/Howtos/dash/DASH-intro.md | 2 +- docs/Howtos/dash/LL-DASH.md | 4 ++++ docs/Howtos/dash/LL-HLS.md | 2 ++ docs/stylesheets/collapse_section.css | 20 +++++++++++++++++--- docs/stylesheets/levels.css | 2 +- 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/docs/Howtos/dash/DASH-intro.md b/docs/Howtos/dash/DASH-intro.md index e3128179..6d306434 100644 --- a/docs/Howtos/dash/DASH-intro.md +++ b/docs/Howtos/dash/DASH-intro.md @@ -1,4 +1,4 @@ -## Foreword + GPAC has extended support for MPEG-DASH and HLS content generation and playback. diff --git a/docs/Howtos/dash/LL-DASH.md b/docs/Howtos/dash/LL-DASH.md index e2e5b0c0..1299bd17 100644 --- a/docs/Howtos/dash/LL-DASH.md +++ b/docs/Howtos/dash/LL-DASH.md @@ -79,6 +79,7 @@ You can now do a live (dynamic) DASH session: ``` MP4Box -dash-live 10000 -subdur 10000 -frag 1000 -profile live -out DST_URL source1 ... source2 ``` + This will generate a live session, dashing 10s (`-subdur`) of content into 10s (`-dash-live`) segments, each containing 10 fragments of 1s (`-frag`). If your sources are live, you don't need to specify the `-subdur`option since real-time regulation of source is not needed. or @@ -86,6 +87,7 @@ or ``` gpac -i source1 -i source2 -o DST_URL:segdur=10:cdur=1:profile=live:dmode=dynamic:sreg ``` + This will generate a live session, dashing content into 10s (`segdur`) segments, each containing 10 fragments of 1s (`cdur`), performing real-time regulation after each segment generation (`sreg`). If your sources are live, you don't need to specify the `sreg`option since real-time regulation of source is not needed. @@ -95,6 +97,7 @@ The above commands do not perform fragment regulation, which means that the cont MP4Box -frag-rt -dash-live 10000 -frag 1000 -profile live -out res/live.mpd source1 source2 gpac -i source1 -i source2 reframer:rt=on -o res/live.mpd:segdur=10:cdur=1:profile=live:dmode=dynamic ``` + The `-frag-rt` simply injects a [reframer](reframer) in the filter graph performing real-time regulation on media frames. This will ensure that both segments and fragments are written to disk in real-time. @@ -110,6 +113,7 @@ You now have low-latency producing of your DASH session, however in terms of DAS MP4Box -ast-offset 9000 -dash-live 10000 -frag 1000 -profile live -out res/live.mpd udp://IP:PORT gpac -i udp://IP:PORT -o res/live.mpd:segdur=10:cdur=1:profile=live:dmode=dynamic:asto=9 ``` + In the above example, we indicate the requests can be issued 9s (`asto` or `-ast-offset`) before the segment is fully produced. Your typical setup should have `cdur + asto = segdur`. If `asto` is greater than `segdur + cdur`, this will results in 404; if is is less, this will increase the client delay to the live edge. diff --git a/docs/Howtos/dash/LL-HLS.md b/docs/Howtos/dash/LL-HLS.md index 519662f8..29cc46ac 100644 --- a/docs/Howtos/dash/LL-HLS.md +++ b/docs/Howtos/dash/LL-HLS.md @@ -24,12 +24,14 @@ In file mode, each part file is the full segment name appended with `.N`, with ` MP4Box -frag-rt -dash-live 10000 -frag 1000 -profile live -out res/live.m3u8:llhls=br source1 source2 gpac -i source1 -i source2 reframer:rt=on -o res/live.m3u8:segdur=10:cdur=1:profile=live:dmode=dynamic:llhls=br ``` + In the above example, we indicate the LLHLS parts are byte-ranges in the segment being produced. ``` MP4Box -frag-rt -dash-live 10000 -frag 1000 -profile live -out res/live.m3u8:llhls=sf source1 source2 gpac -i source1 -i source2 reframer:rt=on -o res/live.m3u8:segdur=10:cdur=1:profile=live:dmode=dynamic:llhls=sf ``` + In the above example, we indicate the LLHLS parts are independent files. diff --git a/docs/stylesheets/collapse_section.css b/docs/stylesheets/collapse_section.css index 45aef5e0..8d35e13b 100644 --- a/docs/stylesheets/collapse_section.css +++ b/docs/stylesheets/collapse_section.css @@ -14,14 +14,28 @@ .collapse-section h2 { margin: 0; padding: 1em; - background-color: #ebe8e8; + background-color: #e7e6e6; cursor: pointer; display: flex; align-items: center; - justify-content: space-between; font-size: 1.2em; color: #333; - transition: background-color 0.3s ease-in-out, color 0.3s ease-in-out; + transition: background-color 0.3s ease-in-out; + position: relative; +} + + +.collapse-section h2 .headerlink { + position: absolute; + right: 10px; + top: 50%; + transform: translateY(-50%); +} + + +.collapse-section h2 .collapse-icon { + margin-right: 30px; + margin-left: 15px; } .collapse-section h2:hover { diff --git a/docs/stylesheets/levels.css b/docs/stylesheets/levels.css index f0bc2eb1..3cfeb2cd 100644 --- a/docs/stylesheets/levels.css +++ b/docs/stylesheets/levels.css @@ -7,7 +7,7 @@ border-radius: 4px; font-size: 0.8em; font-weight: bold; - margin-right: 5px; + margin-right: 15px; } .level-beginner { background-color: #e6f3e6; color: #2e7d32; } From c7cc8c3882f06c8aa62aea7a5c9bbfaf492cdab0 Mon Sep 17 00:00:00 2001 From: PierreDillard <7gaspard77@gmail.com> Date: Mon, 5 Aug 2024 16:56:05 +0200 Subject: [PATCH 05/28] improve toggle button style --- docs/javascripts/domManipulation.js | 5 +++-- docs/javascripts/levels.js | 2 ++ docs/stylesheets/extra.css | 4 +++- docs/stylesheets/toggle.css | 32 ++++++++++++++++------------- mkdocs.yml | 1 + overrides/base.html | 4 ++-- overrides/partials/header.html | 4 ++-- 7 files changed, 31 insertions(+), 21 deletions(-) diff --git a/docs/javascripts/domManipulation.js b/docs/javascripts/domManipulation.js index 1ac9b347..d0d96ecf 100644 --- a/docs/javascripts/domManipulation.js +++ b/docs/javascripts/domManipulation.js @@ -7,13 +7,15 @@ document.addEventListener("DOMContentLoaded", function () { let isNavIsVisible = true; toggleButton.addEventListener("click", function () { - + console.log("Button clicked"); if (isNavIsVisible) { navContent.style.display = "none"; tocContent.style.display = "block"; + toggleButton.setAttribute('data-md-tooltip', 'Switch to Navigation'); } else { navContent.style.display = "block"; tocContent.style.display = "none"; + toggleButton.setAttribute('data-md-tooltip', 'Switch to Table of Contents'); } isNavIsVisible = !isNavIsVisible; }); @@ -21,7 +23,6 @@ document.addEventListener("DOMContentLoaded", function () { tocContent.style.display = "none"; navContent.style.display = "block"; }); - document.addEventListener("DOMContentLoaded", function () { if (window.location.pathname.includes("/glossary/")) { document.body.classList.add("glossary-page"); diff --git a/docs/javascripts/levels.js b/docs/javascripts/levels.js index ad508e6f..718b7184 100644 --- a/docs/javascripts/levels.js +++ b/docs/javascripts/levels.js @@ -55,6 +55,8 @@ function addLevelTags() { } }); } + + function isHowtosSection() { return window.location.pathname.includes('/Howtos/'); } diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index f8b6b669..9a444b69 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -94,4 +94,6 @@ .words-cloud a { color: var(--md-typeset-a-color); - } \ No newline at end of file + } + + \ No newline at end of file diff --git a/docs/stylesheets/toggle.css b/docs/stylesheets/toggle.css index 0b786fdb..f1da905b 100644 --- a/docs/stylesheets/toggle.css +++ b/docs/stylesheets/toggle.css @@ -26,28 +26,32 @@ .toggle-btn { - margin-bottom: 0.2rem; - background-color: transparent; - color: white; + margin-left: 15px; + margin-bottom: 15px; + padding: 8px; + background-color: var(--md-primary-fg-color); + color: var(--md-primary-bg-color); border: none; - border-radius: 50%; + border-radius: 4px; cursor: pointer; - font-size: 16px; - display: flex; - align-items: center; - justify-content: center; - width: 50%; - z-index: 99999; - transition: background-color 0.3s ease, transform 0.3s ease; -} + transition: background-color 0.3s ease; + } + + [data-md-color-scheme="slate"] .toggle-btn:hover{ + background-color: #3a3a3a; + + } -.toggle-btn i:hover { - background-color: #D7D7D7; +.toggle-btn:hover { + + background-color: #dddcdc; + } .toggle-btn i { color: #d94412e0; transition: transform 0.3s ease; + font-size: 18px; } .hidden { diff --git a/mkdocs.yml b/mkdocs.yml index 3e94af79..658a87b0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -14,6 +14,7 @@ extra_javascript: - javascripts/cache.js - javascripts/modalFunctions.js + extra_css: - stylesheets/extra.css diff --git a/overrides/base.html b/overrides/base.html index 1541680e..f56a10fb 100644 --- a/overrides/base.html +++ b/overrides/base.html @@ -161,8 +161,8 @@
    -
    diff --git a/overrides/partials/header.html b/overrides/partials/header.html index dfa23cde..94ffa51f 100644 --- a/overrides/partials/header.html +++ b/overrides/partials/header.html @@ -11,9 +11,9 @@
    - Expert +
    From 591b1b9e777748f551cbc24de920bf3490a08f67 Mon Sep 17 00:00:00 2001 From: PierreDillard <7gaspard77@gmail.com> Date: Tue, 6 Aug 2024 14:14:07 +0200 Subject: [PATCH 06/28] add tooltips for switch level and search function/move the switch container near dark mode icon --- docs/MP4Box/MP4Box.md | 2 +- docs/javascripts/domManipulation.js | 32 +++++++++++-- docs/javascripts/levels.js | 35 +++++++++------ docs/javascripts/main.js | 4 +- docs/javascripts/search.js | 69 +++++++++++++++++++++++++++++ docs/stylesheets/search.css | 10 +++++ mkdocs.yml | 2 + overrides/base.html | 6 +++ overrides/partials/header.html | 15 ++++--- 9 files changed, 149 insertions(+), 26 deletions(-) create mode 100644 docs/javascripts/search.js create mode 100644 docs/stylesheets/search.css diff --git a/docs/MP4Box/MP4Box.md b/docs/MP4Box/MP4Box.md index fbb998f1..91e1684f 100644 --- a/docs/MP4Box/MP4Box.md +++ b/docs/MP4Box/MP4Box.md @@ -1,4 +1,4 @@ -# Overview +# Overview {: data-level ='all'} The multimedia packager available in GPAC is called MP4Box. It is mostly designed for processing ISOBMF files (e.g. MP4, 3GP), but can also be used to import/export media from container files like AVI, MPG, MKV, MPEG-2 TS ... diff --git a/docs/javascripts/domManipulation.js b/docs/javascripts/domManipulation.js index d0d96ecf..09fe8ff1 100644 --- a/docs/javascripts/domManipulation.js +++ b/docs/javascripts/domManipulation.js @@ -4,25 +4,48 @@ document.addEventListener("DOMContentLoaded", function () { const toggleButton = document.getElementById("toggle-button"); const tocContent = document.getElementById("toc-content"); const navContent = document.getElementById("nav-content"); + const isDarkMode = document.body.getAttribute('data-md-color-scheme') === 'slate'; let isNavIsVisible = true; - + + + if (toggleButton) { + console.log("Creating tippy instance for toggle button"); + tippy(toggleButton, { + content: 'Toggle Nav/Toc', + placement: 'left', + theme: isDarkMode ? 'dark' : 'light', + trigger: 'mouseenter', + hideOnClick: false + }); + } toggleButton.addEventListener("click", function () { - console.log("Button clicked"); + if (isNavIsVisible) { navContent.style.display = "none"; tocContent.style.display = "block"; - toggleButton.setAttribute('data-md-tooltip', 'Switch to Navigation'); } else { navContent.style.display = "block"; tocContent.style.display = "none"; - toggleButton.setAttribute('data-md-tooltip', 'Switch to Table of Contents'); } isNavIsVisible = !isNavIsVisible; }); tocContent.style.display = "none"; navContent.style.display = "block"; + + function activatePermanentLinkSections() { + const permanentLinkSections = document.querySelectorAll('h2:has(a[title="Permanent link"])'); + permanentLinkSections.forEach(section => { + section.classList.add('active'); + }); + } + + // Appeler la fonction au chargement de la page + activatePermanentLinkSections() + }); + + document.addEventListener("DOMContentLoaded", function () { if (window.location.pathname.includes("/glossary/")) { document.body.classList.add("glossary-page"); @@ -32,6 +55,7 @@ document.addEventListener("DOMContentLoaded", function () { // Collapse sections document.addEventListener("DOMContentLoaded", function () { + const articleInner = document.querySelector('.md-content__inner'); const h1Element = articleInner.querySelector('h1'); diff --git a/docs/javascripts/levels.js b/docs/javascripts/levels.js index 718b7184..ab9a009c 100644 --- a/docs/javascripts/levels.js +++ b/docs/javascripts/levels.js @@ -34,6 +34,7 @@ function handleLevelChange() { let cachedKeywords = getCache("keywordsCache"); let cachedDefinitions = getCache("definitionsCache"); fetchKeywords(currentPageMdPath, cachedKeywords, cachedDefinitions); + document.dispatchEvent(new Event('levelChanged')); } //Display the level of the user function updateSwitchLabel() { @@ -143,6 +144,7 @@ function updateTOCVisibility(level) { const h2Elements = document.querySelectorAll('h2[data-level]'); const tocItems = document.querySelectorAll('.md-nav__item'); + const beginnerIds = new Set(); const sectionWithSubsections = new Set(); @@ -151,24 +153,31 @@ function updateTOCVisibility(level) { beginnerIds.add(h2.id); } }); - + if (beginnerIds.size === 0) { + // If no beginner sections, show all TOC items + tocItems.forEach(item => { + item.style.display = ''; + }); + } else { // Hide non-beginner TOC items and show beginner ones. - tocItems.forEach(item => { - const link = item.querySelector('a.md-nav__link'); - if (link) { - const href = link.getAttribute('href'); - if (href && href.startsWith('#')) { - const id = href.slice(1); - if (!beginnerIds.has(id)) { - item.style.display = 'none'; - } else { - item.style.display = ''; - } - } + tocItems.forEach(item => { + const link = item.querySelector('a.md-nav__link'); + if (link) { + const href = link.getAttribute('href'); + if (href && href.startsWith('#')) { + const id = href.slice(1); + if (!beginnerIds.has(id)) { + item.style.display = 'none'; + } else { + item.style.display = ''; + } } + } }); } +} + document.addEventListener("DOMContentLoaded", function () { initializeLevelManagement(); const savedLevel = localStorage.getItem("userLevel") || "expert"; diff --git a/docs/javascripts/main.js b/docs/javascripts/main.js index 0a097e1f..acd73b71 100644 --- a/docs/javascripts/main.js +++ b/docs/javascripts/main.js @@ -16,9 +16,11 @@ document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function() { initializeLevelManagement(); + initializeTOCClickHandler(); let cachedKeywords = getCache('keywordsCache'); let cachedDefinitions = getCache('definitionsCache'); fetchKeywords(currentPageMdPath, cachedKeywords, cachedDefinitions); -}); \ No newline at end of file +}); + diff --git a/docs/javascripts/search.js b/docs/javascripts/search.js new file mode 100644 index 00000000..6bcdd380 --- /dev/null +++ b/docs/javascripts/search.js @@ -0,0 +1,69 @@ +document.addEventListener('DOMContentLoaded', function() { + + + const searchInput = document.querySelector('input.md-search__input'); + + + let tippyInstance = null; + + function updateTooltip() { + + const isBeginnerMode = localStorage.getItem("userLevel") === "beginner"; + const isDarkMode = document.body.getAttribute('data-md-color-scheme') === 'slate'; + + + if (isBeginnerMode) { + console.log("Beginner mode: creating/updating tooltip"); + if (!tippyInstance && searchInput) { + console.log("Creating new tippy instance"); + tippyInstance = tippy(searchInput, { + content: '⚠️ For best search results, switch to expert mode', + placement: 'left', + theme: isDarkMode ? 'dark' : 'light', + trigger: 'focus', + hideOnClick: true + }); + } else if (tippyInstance) { + console.log("Updating existing tippy instance"); + tippyInstance.setProps({ + theme: isDarkMode ? 'dark' : 'light' + }); + } + } else { + console.log("Expert mode: destroying tooltip if it exists"); + if (tippyInstance) { + tippyInstance.destroy(); + tippyInstance = null; + } + } + } + + if (searchInput) { + console.log("Search input found, setting up tooltip"); + + setTimeout(() => { + console.log("Initializing tooltip after delay"); + updateTooltip(); + }, 100); + + + document.addEventListener('levelChanged', function() { + console.log("Level changed event detected"); + updateTooltip(); + }); + + + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.type === 'attributes' && mutation.attributeName === 'data-md-color-scheme') { + console.log("Color scheme changed"); + updateTooltip(); + } + }); + }); + + observer.observe(document.body, { attributes: true }); + } else { + console.log("Search input not found"); + } +}); \ No newline at end of file diff --git a/docs/stylesheets/search.css b/docs/stylesheets/search.css new file mode 100644 index 00000000..0bfdbca2 --- /dev/null +++ b/docs/stylesheets/search.css @@ -0,0 +1,10 @@ +.tippy-box[data-theme~='light'] { + background-color: #f0f0f0; + color: #333; + border: 1px solid #d9d9d9; + font-size: 14px; +} + +.tippy-box[data-theme~='light'][data-placement^='top'] > .tippy-arrow::before { + border-top-color: #f0f0f0; +} \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 658a87b0..25621e0c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -13,6 +13,7 @@ extra_javascript: - javascripts/keywordsDisplay.js - javascripts/cache.js - javascripts/modalFunctions.js + - javascripts/search.js @@ -26,6 +27,7 @@ extra_css: - stylesheets/collapse_section.css - stylesheets/feedback.css - stylesheets/levels.css + - stylesheets/search.css - https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css theme: name: material diff --git a/overrides/base.html b/overrides/base.html index f56a10fb..06ade32c 100644 --- a/overrides/base.html +++ b/overrides/base.html @@ -49,6 +49,7 @@ {% set palette = config.theme.palette %} {% endif %} + {% include "partials/icons.html" %} {% endblock %} {% block libs %} @@ -81,6 +82,11 @@ {% endfor %} {% endif %} {% block extrahead %}{% endblock %} + {% block tippy %} + + + + {% endblock %} {% set direction = config.theme.direction or lang.t("direction") %} {% if config.theme.palette %} diff --git a/overrides/partials/header.html b/overrides/partials/header.html index 94ffa51f..fa0d4ff1 100644 --- a/overrides/partials/header.html +++ b/overrides/partials/header.html @@ -8,13 +8,7 @@ {% set class = class ~ " md-header--shadow" %} {% endif %}
    -
    - - -
    +
    +
    + + +
    {% if config.theme.palette %} {% if not config.theme.palette is mapping %} {% include "partials/palette.html" %} From 894ee9f4f65cae5c9a37383f38d1dbe3eadb608a Mon Sep 17 00:00:00 2001 From: PierreDillard <7gaspard77@gmail.com> Date: Tue, 6 Aug 2024 16:09:07 +0200 Subject: [PATCH 07/28] tagg Howtos section --- docs/Howtos/The-GPAC-Log-System.md | 9 ++++----- docs/Howtos/avmix_tuto.md | 6 +++--- docs/Howtos/custom-boxes.md | 4 ++-- docs/Howtos/dynamic_rc.md | 2 +- docs/Howtos/encryption/encryption-clearkey.md | 8 ++++---- docs/Howtos/encryption/encryption-filters.md | 3 ++- docs/Howtos/heif/heif-extraction.md | 8 ++++---- docs/Howtos/heif/heif-import.md | 8 ++++---- docs/Howtos/jsf/evg.md | 8 ++++---- docs/Howtos/jsf/jsdash.md | 2 +- docs/Howtos/jsf/jsfilter.md | 8 ++++---- docs/Howtos/jsf/jshttp.md | 6 +++--- docs/Howtos/jsf/jssession.md | 4 ++-- docs/Howtos/jsf/webgl.md | 2 +- docs/Howtos/jsf/webgl_three.md | 2 +- docs/Howtos/python.md | 9 +++++---- docs/Howtos/realtime.md | 4 ++-- docs/Howtos/route.md | 2 +- docs/Howtos/scenecoding/MPEG-4-BIFS-Textual-Format.md | 2 +- docs/Howtos/scenecoding/MPEG-4-XMT-Format.md | 2 +- docs/Howtos/scenecoding/SceneCodingIntro.md | 2 +- docs/Howtos/scte35-markers.md | 4 ++-- 22 files changed, 53 insertions(+), 52 deletions(-) diff --git a/docs/Howtos/The-GPAC-Log-System.md b/docs/Howtos/The-GPAC-Log-System.md index 7c6b2013..35475553 100644 --- a/docs/Howtos/The-GPAC-Log-System.md +++ b/docs/Howtos/The-GPAC-Log-System.md @@ -1,10 +1,10 @@ -# Introduction +# Introduction {:data-level="all"} A log is a way of keeping record of what's happening when executing a software. The GPAC framework has log capabilities to analyze what is going on when running MP4Box, gpac or other libgpac based applications. This article explains the features of the log system and how to use it. For more information on latest syntax and options, [check here](core_logs). -# Overview +# Overview {:data-level="all"} The GPAC log system is based on two orthogonal concepts: @@ -22,8 +22,7 @@ The general syntax is: Concrete examples are given further in this article. -# Example of available Tools - +# Example of available Tools - GF_LOG_CORE (core) : log message from the core library (init, threads, network calls, etc) - GF_LOG_FILTERS (filters) : log message from the filter session - GF_LOG_CONTAINER (container): log message from a bitstream parser (IsoMedia, MPEG-2 TS, OGG, ...) @@ -43,7 +42,7 @@ Concrete examples are given further in this article. - GF_LOG_ALL (all) : all available logs -# Available Levels +# Available Levels - GF_LOG_QUIET (quiet) : disable all Log message - GF_LOG_ERROR (error) : log message describes an error diff --git a/docs/Howtos/avmix_tuto.md b/docs/Howtos/avmix_tuto.md index a20ffbe9..1dbbf14a 100644 --- a/docs/Howtos/avmix_tuto.md +++ b/docs/Howtos/avmix_tuto.md @@ -1,4 +1,4 @@ -# Overview +# Overview {:data-level="all"} We discuss here how to use the AVMix filter to do offline or live video editing. This filter is a [JS filter](jsf) and can be modified fairly simply. @@ -32,7 +32,7 @@ By default, the filter will run in live mode, and will never stop. You can run i ``` -# General concepts +# General concepts {:data-level="beginner"} ## Declaring media To declare media for the mixer, you need to use a sequence object along with your media source. Typically: @@ -380,7 +380,7 @@ And the video sequence will start ! You can use for start and stop time values: - date: will use the date as the start/stop time -# Some examples +# Some examples {:data-level="beginner"} ## Transparent Logo insertion Simplified version of above example, with a single source and logo diff --git a/docs/Howtos/custom-boxes.md b/docs/Howtos/custom-boxes.md index 425b1b78..e8300406 100644 --- a/docs/Howtos/custom-boxes.md +++ b/docs/Howtos/custom-boxes.md @@ -1,4 +1,4 @@ -# Overview +# Overview {:data-level="all"} We discuss here how to customize ISOBMFF files using box patches. Please first check the XML [Box Patch](BoxPatch) syntax before reading. @@ -66,7 +66,7 @@ This will remove the new box if present in the track header of the track with ID -# Injecting a box patch in your workflow +# Injecting a box patch in your workflow {: data-level="beginner"} You may also want to customize your ISOBMFF files while they are being produced in a filter chain. diff --git a/docs/Howtos/dynamic_rc.md b/docs/Howtos/dynamic_rc.md index 30230939..79bcd7eb 100644 --- a/docs/Howtos/dynamic_rc.md +++ b/docs/Howtos/dynamic_rc.md @@ -1,4 +1,4 @@ -# Overview +# Overview {: data-level="all"} We discuss here how to implement dynamic rate control using [GPAC Filters](Filters). Examples are in Python but API is also available for [JSF](jsfilter) and [NodeJS](nodejs). diff --git a/docs/Howtos/encryption/encryption-clearkey.md b/docs/Howtos/encryption/encryption-clearkey.md index 842effff..1d1ed2a2 100644 --- a/docs/Howtos/encryption/encryption-clearkey.md +++ b/docs/Howtos/encryption/encryption-clearkey.md @@ -1,4 +1,4 @@ -# Overview +# Overview {:data-level="all"} We discuss here how to use ClearKey encryption in GPAC. @@ -10,11 +10,11 @@ For more info, check https://github.com/Dash-Industry-Forum/ClearKey-Content-Pro Fetching of keys through ClearKey is supported in GPAC decryptor and does not need to be configured. -# DRM config file +# DRM config file {:data-level="beginner"} ClearKey can be used with any scheme types defined in CENC, and does not need any specific info (e.g. PSSH). -# Using MP4Box +# Using MP4Box {:data-level="beginner"} You first need to encrypt your source: @@ -35,7 +35,7 @@ If each source has its own licence URL: ``` -# Using gpac for single-pass encrypt and dash +# Using gpac for single-pass encrypt and dash If all streams share the same ClearKey licence server URL, specify `ckurl` option of dasher: diff --git a/docs/Howtos/encryption/encryption-filters.md b/docs/Howtos/encryption/encryption-filters.md index 146e7dce..68c8e4b6 100644 --- a/docs/Howtos/encryption/encryption-filters.md +++ b/docs/Howtos/encryption/encryption-filters.md @@ -1,4 +1,4 @@ -# Overview +# Overview {:data-level="all"} We discuss here how to use encryption in a filter chain in GPAC. @@ -65,6 +65,7 @@ Another possibility is to define the `CryptInfo` PID property rather than using ``` gpac -i udp://localhost:1234/:#CrypTrack=(audio)drm_audio.xml,(video)drm_video.xml cecrypt -o dest.mpd:profile=live:dmode=dynamic ``` + This example assigns: - a `CryptInfo` property to `drm_audio.xml` for PIDs of type audio diff --git a/docs/Howtos/heif/heif-extraction.md b/docs/Howtos/heif/heif-extraction.md index 7a8a3b84..d9b19140 100644 --- a/docs/Howtos/heif/heif-extraction.md +++ b/docs/Howtos/heif/heif-extraction.md @@ -1,11 +1,11 @@ -# Overview +# Overview {:data-level="all"} We discuss here how to extract items from HEIF file collections. For track extraction, use the regular tools from GPAC. A HEIF image collection will contain several items packed in a single `meta` box in ISOBMFF. These items will usually share the same coding type, although this is not a requirement. We only discuss here HEIF version 1 files, for which each item is an intra picture. We assume the image collection is made of HEVC items. -# Extracting images +# Extracting images {:data-level="beginner"} You can use MP4Box to manually extract each item, see [MP4Box -h meta](mp4box-meta-opts): @@ -25,7 +25,7 @@ This will dump each item in `dump_$ItemID$.hvc`, with `$ItemID$` being replaced -# Transcoding as images +# Transcoding as images {:data-level="beginner"} Transcoding is not possible using MP4Box. Using gpac, it is a fairly simple process: @@ -37,7 +37,7 @@ This will transcode each item to JPEG in `dump_$ItemID$.jpg`, with `$ItemID$` b Each item will be declared as a media PID. This implies that there will be one decoder instance and one encoder instance created for each media PID. -# Transcoding as a sequence of images +# Transcoding as a sequence of images {:data-level="beginner"} One way to optimize the previous drawback of high resource usage is by declaring all items as a single track using [-itt](mp4dmx#itt) option of the MP4 demultiplexer: diff --git a/docs/Howtos/heif/heif-import.md b/docs/Howtos/heif/heif-import.md index 01a5f29b..40c10938 100644 --- a/docs/Howtos/heif/heif-import.md +++ b/docs/Howtos/heif/heif-import.md @@ -1,9 +1,9 @@ -# Overview +# Overview {:data-level="all"} We discuss here how to import items to create HEIF file collections. For track import, use the regular tools from GPAC. -# Importing images +# Importing images {:data-level="beginner"} You can use MP4Box to manually import each item, see [MP4Box -h meta](mp4box-meta-opts). @@ -42,7 +42,7 @@ MP4Box -add-image src.hvc:primary:time=3-180/30 -new image.heic ``` -# Filtering while importing +# Filtering while importing {:data-level="beginner"} MP4Box can be used together with filters in gpac, as discussed [here](mp4box-filters). This section illustrates how this feature can be used in various use cases. ## Importing a subset of frames @@ -59,7 +59,7 @@ The following example imports a JPG image and transcodes it to HEVC at 5 mbps: MP4Box -add-image src.jpg:primary@@enc:c=avc:b=5m -new image.heic ``` -# Creating images in a file with video +# Creating images in a file with video {:data-level="beginner"} MP4Box can create images from a file with one or more video tracks and save the combined video+items. The syntax used is the same as the examples above except the source file is not set. diff --git a/docs/Howtos/jsf/evg.md b/docs/Howtos/jsf/evg.md index 481c1d09..c2ff2790 100644 --- a/docs/Howtos/jsf/evg.md +++ b/docs/Howtos/jsf/evg.md @@ -1,4 +1,4 @@ -# Overview +# Overview {:data-level="all"} We discuss here how to use the [JavaScript Filter](jsf) to generate 2D vector graphics in GPAC. The [JS scripts](https://github.com/gpac/testsuite/tree/filters/media/jsf) in the gpac test suite are also a good source of examples. @@ -12,18 +12,18 @@ For a simpler and/or more customized support of 2D graphics, GPAC includes JavaS Please note that the EVG API is an immediate mode API and does not track objects drawn. If you want to partially redraw the surface, you will need to track changes and use clippers to redraw only changed areas. -# Setting up EVG +# Setting up EVG {:data-level="beginner"} You need to import the EVG JavaScript bindings in your script: ``` import * as evg from 'evg' ``` -# Setting up a canvas +# Setting up a canvas {:data-level="beginner"} In order to draw, a JS EVG filter requires a canvas, called surface in the API. This surface allows writing to a memory buffer in the desired pixel format, and can be setup in different ways. -# Creating a canvas +# Creating a canvas {:data-level="beginner"} The simplest way to create a canvas is to allocate your own memory and setup the canvas: diff --git a/docs/Howtos/jsf/jsdash.md b/docs/Howtos/jsf/jsdash.md index b0a3c6f2..9424ef04 100644 --- a/docs/Howtos/jsf/jsdash.md +++ b/docs/Howtos/jsf/jsdash.md @@ -1,4 +1,4 @@ -# Overview +# Overview We discuss here how to implement your custom DASH rate adaptation logic in JS. diff --git a/docs/Howtos/jsf/jsfilter.md b/docs/Howtos/jsf/jsfilter.md index 04e2df5e..4f56cc2d 100644 --- a/docs/Howtos/jsf/jsfilter.md +++ b/docs/Howtos/jsf/jsfilter.md @@ -1,11 +1,11 @@ -# Overview +# Overview {:data-level="all"} We discuss here how to use the [JavaScript Filter](jsf) to write JavaScript-based custom filters in GPAC. The [JS scripts](https://github.com/gpac/testsuite/tree/filters/media/jsf) in the gpac test suite are also a good source of examples. The JS filter provides JS bindings to the GPAC filter architecture. JavaScript support in GPAC is powered by QuickJS. Check the documentation of the [JS APIs](https://doxygen.gpac.io/group__jsf__grp.html) for more details. -# Principles +# Principles {:data-level="beginner"} In order to load, a JS filter requires a source JS, specified in the filter [js](jsf#js) option. A short-cut syntax is to directly specify the js script. In other words, the following syntaxes are equivalent: ``` @@ -48,7 +48,7 @@ If you need to pass JS data across filters, you will have to serialize to JSON y - send it as associated property on existing packets -# Declaring a filter (optional) +# Declaring a filter (optional) {:data-level="beginner"} The first thing to do when creating a filter is to setup a few things about your filter. @@ -584,7 +584,7 @@ vout_f.set_source(filter, "MuxSrc=myPid"); ``` -# Including filters in your distribution +# Including filters in your distribution {:data-level=beginner"} JS files located in GPAC distribution or in the directories indicated using [-js-dirs](core_options#js-dirs) option can describe filters usable by the filter engine based on their name (file without extension or directory name). diff --git a/docs/Howtos/jsf/jshttp.md b/docs/Howtos/jsf/jshttp.md index 73c077cd..6661fdf4 100644 --- a/docs/Howtos/jsf/jshttp.md +++ b/docs/Howtos/jsf/jshttp.md @@ -1,11 +1,11 @@ -# Overview +# Overview {: data-level ="all"} We discuss here how to implement your custom HTTP server logic in JS. A custom logic can be defined using a standalone script, or by attaching a JS object to the httpout filter in a JS session. -# Standalone script +# Standalone script {: data-level ="beginner"} The standalone mode works by specifying a JS file to the httpin filter using its [-js](httpout#js) option: @@ -40,7 +40,7 @@ httpout.on_request = function (request) ``` -# Attaching from a JS session +# Attaching from a JS session {: data-level ="beginner"} The first step in your JS is to create an object implementing the callback previously indicated: diff --git a/docs/Howtos/jsf/jssession.md b/docs/Howtos/jsf/jssession.md index ce959589..a53f9c5a 100644 --- a/docs/Howtos/jsf/jssession.md +++ b/docs/Howtos/jsf/jssession.md @@ -1,4 +1,4 @@ -# Overview +# Overview {: data-level ="all"} We discuss here how to use [gpac](gpac_general) or the [JavaScript Filter](jsf) to query and control from JavaScript the filter session in GPAC. The [JS scripts](https://github.com/gpac/testsuite/tree/filters/media/jsf) in the gpac test suite are also a good source of examples. @@ -314,6 +314,6 @@ my_filter.process = function() ``` -# Other tools +# Other tools {: data-level ="beginner"} Some GPAC core functions are made available through JS for prompt handling, bitstream parsing, file and directory IO, check [the documentation](https://doxygen.gpac.io/group__core__grp.html). diff --git a/docs/Howtos/jsf/webgl.md b/docs/Howtos/jsf/webgl.md index 725971e0..7b0b25d9 100644 --- a/docs/Howtos/jsf/webgl.md +++ b/docs/Howtos/jsf/webgl.md @@ -1,4 +1,4 @@ -# Overview +# Overview {: data-level ="all"} We discuss here how to use the [JavaScript Filter](jsf) to generate 3D vector graphics in GPAC. The [JS scripts](https://github.com/gpac/testsuite/tree/filters/media/jsf) in the GPAC test suite are also a good source of examples. diff --git a/docs/Howtos/jsf/webgl_three.md b/docs/Howtos/jsf/webgl_three.md index 88a25204..082e4513 100644 --- a/docs/Howtos/jsf/webgl_three.md +++ b/docs/Howtos/jsf/webgl_three.md @@ -1,4 +1,4 @@ -# Foreword +# Foreword {: data-level ="all"} We discuss here how to use the [JavaScript Filter](jsf) WebGL along with Three.js in GPAC. diff --git a/docs/Howtos/python.md b/docs/Howtos/python.md index 514f9514..963065b6 100644 --- a/docs/Howtos/python.md +++ b/docs/Howtos/python.md @@ -1,4 +1,4 @@ -# Overview +# Overview {:data-level="all"} We discuss here how to use [GPAC Filters](Filters) in Python. @@ -12,7 +12,7 @@ __Warning__ GPAC Python bindings are only available starting from GPAC 2.0. -# Before you begin +# Before you begin {:data-level="all"} The GPAC Python bindings use [ctypes](https://docs.python.org/3/library/ctypes.html) for interfacing with libgpac filter session, while providing an object-oriented wrapper hiding all ctypes internals and GPAC C design. @@ -49,7 +49,7 @@ Running this should print your current GPAC version. You can also install libgpac bindings using PIP, see [this post](https://github.com/gpac/gpac/issues/2161#issuecomment-1087281505). -# Tuning up GPAC +# Tuning up GPAC The first thing to do is to initialize libgpac. This is done by default while importing the bindings with the following settings: @@ -118,7 +118,7 @@ fs.delete() gpac.close() ``` -## Non-blocking sessions +## Non-blocking sessions A non-blocking session will need to be called on regular basis to process pending filter tasks. It is useful if you need to do other tasks while the session is running and do not want to use callbacks from GPAC for that. @@ -184,6 +184,7 @@ You can specify the usual link filtering as an optional argument to `set_source` ``` f_dst.set_source(reframer, "#PID=1") ``` + This will instruct that the destination only accepts PIDs coming from the reframer filter, and with ID 1. diff --git a/docs/Howtos/realtime.md b/docs/Howtos/realtime.md index 84e09dd6..0d852483 100644 --- a/docs/Howtos/realtime.md +++ b/docs/Howtos/realtime.md @@ -1,9 +1,9 @@ -# Overview +# Overview {:data-level ="all"} We discuss here how to simulate real-time sources in GPAC. -# Introduction +# Introduction {:data-level ="all"} Assume you have one or several sources dispatching data in a non real-time fashion, such as a local file, an HTTP download or a pipe input. You may want to produce data in real-time, for DASH, HLS, MPEG-2 TS or HTTP delivery. GPAC comes with the [reframer](reframer) filter, in charge of forcing a de-multiplexing of input data. This filter supports several features including: diff --git a/docs/Howtos/route.md b/docs/Howtos/route.md index 088a3d73..08b308b5 100644 --- a/docs/Howtos/route.md +++ b/docs/Howtos/route.md @@ -1,4 +1,4 @@ -# Overview +# Overview {: data-level="all"} GPAC supports both sending and receiving data using the ROUTE (Real-time Object delivery over Unidirectional Transport) [RFC9223](https://www.rfc-editor.org/rfc/rfc9223) protocol. diff --git a/docs/Howtos/scenecoding/MPEG-4-BIFS-Textual-Format.md b/docs/Howtos/scenecoding/MPEG-4-BIFS-Textual-Format.md index dc690212..31f89724 100644 --- a/docs/Howtos/scenecoding/MPEG-4-BIFS-Textual-Format.md +++ b/docs/Howtos/scenecoding/MPEG-4-BIFS-Textual-Format.md @@ -1,4 +1,4 @@ -# BT Format +# BT Format {:data-level="all"} BT stands for BIFS Text and is an exact textual representation of the MPEG-4 BIFS scene. Its syntax is the same as the VRML/X3D (.wrl and .x3dv files) ones for the scene description part, and it has been extended for other MPEG-4 tools (OD, OCI, IPMP). diff --git a/docs/Howtos/scenecoding/MPEG-4-XMT-Format.md b/docs/Howtos/scenecoding/MPEG-4-XMT-Format.md index 0d299700..00165185 100644 --- a/docs/Howtos/scenecoding/MPEG-4-XMT-Format.md +++ b/docs/Howtos/scenecoding/MPEG-4-XMT-Format.md @@ -1,4 +1,4 @@ -# XMT Format +# XMT Format {:data-level="all"} XMT is the official textural description of MPEG-4 scenes. It is part of ISO/IEC 14496-11, and is quite similar to the X3D XML language. diff --git a/docs/Howtos/scenecoding/SceneCodingIntro.md b/docs/Howtos/scenecoding/SceneCodingIntro.md index 37796265..6f047786 100644 --- a/docs/Howtos/scenecoding/SceneCodingIntro.md +++ b/docs/Howtos/scenecoding/SceneCodingIntro.md @@ -1,4 +1,4 @@ -# Multimedia scene description +# Multimedia scene description {:data-level="all" } A scene description is a language describing animations, interactivity, 2D and 3D shapes, audio and video relationship in a presentation. GPAC supports a variety of scene description languages: diff --git a/docs/Howtos/scte35-markers.md b/docs/Howtos/scte35-markers.md index a1719c05..7d699a7b 100644 --- a/docs/Howtos/scte35-markers.md +++ b/docs/Howtos/scte35-markers.md @@ -1,4 +1,4 @@ -# Overview +# Overview {:data-level="all"} We discuss here about the ability to deal with dynamic metadata such as SCTE-35 in GPAC Filters. The information is this page applies to other metadata such as ID3 markers (e.g. Nielsen), timecodes (TEMI, QT), or virtually any type of dynamic metadata. @@ -63,7 +63,7 @@ scte35_dump.xml would contain for instance such descriptions for a sample: ``` -# Remux and transmux +# Remux and transmux {:data-level="beginner"} GPAC is able to remux any to any, including but not limited to: - TS to TS From df8d1eecb8004566aeb85b2254e38b7b0655efb5 Mon Sep 17 00:00:00 2001 From: PierreDillard <7gaspard77@gmail.com> Date: Wed, 7 Aug 2024 08:21:56 +0200 Subject: [PATCH 08/28] add margin on md-typeset mark --- docs/MP4Box/MP4Box.md | 2 +- docs/stylesheets/extra.css | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/MP4Box/MP4Box.md b/docs/MP4Box/MP4Box.md index 91e1684f..c12fa429 100644 --- a/docs/MP4Box/MP4Box.md +++ b/docs/MP4Box/MP4Box.md @@ -1,4 +1,4 @@ -# Overview {: data-level ='all'} +# Overview {: data-level ="all"} The multimedia packager available in GPAC is called MP4Box. It is mostly designed for processing ISOBMF files (e.g. MP4, 3GP), but can also be used to import/export media from container files like AVI, MPG, MKV, MPEG-2 TS ... diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index 9a444b69..55638f1c 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -74,7 +74,9 @@ .md-typeset .highlight button { display: block !important; } - + .md-typeset mark { + margin: 0 0.3rem; + } .md-footer { flex-shrink: 0; } From 0ee60b465e4a4cd3d08cf8365c830d1163c5f1d1 Mon Sep 17 00:00:00 2001 From: PierreDillard <7gaspard77@gmail.com> Date: Tue, 3 Sep 2024 10:59:26 +0200 Subject: [PATCH 09/28] fix md in filters_general --- docs/Filters/filters_general.md | 145 +++++++++++++++++++++++--------- docs/Howtos/test.md | 13 +++ overrides/base.html | 2 +- 3 files changed, 118 insertions(+), 42 deletions(-) create mode 100644 docs/Howtos/test.md diff --git a/docs/Filters/filters_general.md b/docs/Filters/filters_general.md index c8c96b21..2ade42b4 100644 --- a/docs/Filters/filters_general.md +++ b/docs/Filters/filters_general.md @@ -60,11 +60,13 @@ Example ``` filter:ARG=http://foo/bar?yes:gpac:opt=VAL ``` + This will properly extract the URL. Example ``` filter:ARG=http://foo/bar?yes:opt=VAL -``` +``` + This will fail to extract it and keep `:opt=VAL` as part of the URL. The escape mechanism is not needed for local source, for which file existence is probed during argument parsing. It is also not needed for builtin protocol handlers (`avin://`, `video://`, `audio://`, `pipe://`) For schemes not using a server path, e.g. `tcp://` and `udp://`, the escape is not needed if a trailing `/` is appended after the port number. @@ -72,11 +74,13 @@ Example ``` -i tcp://127.0.0.1:1234:OPT ``` + This will fail to extract the URL and options. Example ``` -i tcp://127.0.0.1:1234/:OPT ``` + This will extract the URL and options. _Note: one trick to avoid the escape sequence is to declare the URLs option at the end, e.g. `f1:opt1=foo:url=http://bar`, provided you have only one URL parameter to specify on the filter._ @@ -85,6 +89,7 @@ Example ``` filter::opt1=UDP://IP:PORT/:someopt=VAL::opt2=VAL2 ``` + This will pass `UDP://IP:PORT/:someopt=VAL` to `opt1` without inspecting it, and `VAL2` to `opt2`. @@ -94,11 +99,13 @@ Example ``` "src=file.mp4" or "-src file.mp4" or "-i file.mp4" ``` + This will find a filter (for example `fin`) able to load `file.mp4`. The same result can be achieved by using `fin:src=file.mp4`. Example ``` "dst=dump.yuv" or "-dst dump.yuv" or "-o dump.yuv" -``` +``` + This will dump the video content in `dump.yuv`. The same result can be achieved by using `fout:dst=dump.yuv`. Specific source or sink filters may also be specified using `filterName:src=URL` or `filterName:dst=URL`. @@ -110,7 +117,8 @@ There is a special option called `gfreg` which allows specifying preferred filte Example ``` src=file.mp4:gfreg=ffdmx,ffdec -``` +``` + This will use _ffdmx_ to read `file.mp4` and _ffdec_ to decode it. This can be used to test a specific filter when alternate filter chains are possible. @@ -131,13 +139,15 @@ Example ``` gpac -i dump.yuv:size=320x240:fps=25 enc:c=avc:b=150000:g=50:cgop=true:fast=true -o raw.264 ``` + This creates a 25 fps AVC at 175kbps with a gop duration of 2 seconds, using closed gop and fast encoding settings for ffmpeg. The inverse operation (forcing a decode to happen) is possible using the _reframer_ filter. Example ``` gpac -i file.mp4 reframer:raw=av -o null -``` +``` + This will force decoding media from `file.mp4` and trash (send to `null`) the result (doing a decoder benchmark for example). ## Escaping option separators @@ -145,17 +155,20 @@ When a filter uses an option defined as a string using the same separator charac Example ``` f:a=foo:b=bar -``` +``` + This will set option `a` to `foo` and option `b` to `bar` on the filter. Example ``` f::a=foo:b=bar -``` +``` + This will set option `a` to `foo:b=bar` on the filter. Example ``` f:a=foo::b=bar:c::d=fun ``` + This will set option `a` to `foo`, `b` to `bar:c` and the option `d` to `fun` on the filter. # Filter linking [_LINK_] @@ -189,7 +202,8 @@ A filter with `RSID` set is not clonable. Example ``` gpac -i file.mp4 c=avc -o output -``` +``` + With this setup in _implicit mode_: - if the file has a video PID, it will connect to `enc` but not to `output`. The output PID of `enc` will connect to `output`. @@ -200,6 +214,7 @@ Example ``` gpac -cl -i file.mp4 c=avc -o output ``` + With this setup in _complete mode_: - if the file has a video PID, it will connect both to `enc` and to `output`, and the output PID of `enc` will connect to `output`. @@ -211,6 +226,7 @@ Example ``` gpac -i video1 reframer:saps=1 -i video2 ffsws:osize=128x72 -o output ``` + This will connect: - `video1` to `reframer` then `reframer` to `output` but will prevent `reframer` to `ffsws` connection. @@ -220,7 +236,8 @@ This will connect: Example ``` gpac -i video1 -i video2 reframer:saps=1 ffsws:osize=128x72 -o output -``` +``` + This will connect `video1` AND `video2` to `reframer->ffsws->output` The _implicit mode_ allows specifying linear processing chains (no PID fan-out except for final output(s)) without link directives, simplifying command lines for common cases. @@ -231,6 +248,7 @@ Example ``` gpac -i file.mp4 c=avc c=aac -o output ``` + If the file has a video PID, it will connect to `c=avc` but not to `output`. The output PID of `c=avc` will connect to `output`. If the file has an audio PID, it will connect to `c=aac` but not to `output`. The output PID of `c=aac` will connect to `output`. If the file has other PIDs than audio or video, they will connect to `output`. @@ -238,7 +256,8 @@ If the file has other PIDs than audio or video, they will connect to `output`. Example ``` gpac -i file.mp4 ffswf=osize:128x72 c=avc resample=osr=48k c=aac -o output -``` +``` + This will force: - `SRC(video)->ffsws->enc(video)->output` and prevent `SRC(video)->output`, `SRC(video)->enc(video)` and `ffsws->output` connections which would happen in _complete mode_. @@ -255,16 +274,19 @@ Example ``` fA fB @1 fC ``` + This indicates that `fC` only accepts inputs from `fA`. Example ``` fA fB fC @1 @0 fD ``` + This indicates that `fD` only accepts inputs from `fB` and `fC`. Example ``` fA fB fC ... @@1 fZ -``` +``` + This indicates that `fZ` only accepts inputs from `fB`. ## Complex links @@ -277,17 +299,20 @@ The `@` link directive is just a quick shortcut to set the following filter argu Example ``` fA fB @1 fC -``` +``` + This is equivalent to `fA:FID=1 fB fC:SID=1`. Example ``` fA:FID=1 fB fC:SID=1 -``` +``` + This indicates that `fC` only accepts input from `fA`, but `fB` might accept inputs from `fA`. Example ``` fA:FID=1 fB:FID=2 fC:SID=1 fD:SID=1,2 -``` +``` + This indicates that `fD` only accepts input from `fA` and `fB` and `fC` only from `fA` _Note: A filter with sourceID set cannot get input from filters with no IDs._ @@ -318,44 +343,52 @@ Example fA fB:SID=*#ServiceID=2 fA fB:SID=#ServiceID=2 ``` + This indicates to match connection between `fA` and `fB` only for PIDs with a `ServiceID` property of `2`. These extensions also work with the _LINK_ `@` shortcut. Example ``` fA fB @1#video fC -``` +``` + This indicates that `fC` only accepts inputs from `fA`, and of type video. Example ``` gpac -i img.heif @#ItemID=200 vout -``` +``` + This indicates to connect to `vout` only PIDs with `ItemID` property equal to `200`. Example ``` gpac -i vid.mp4 @#PID=1 vout -``` +``` + This indicates to connect to `vout` only PIDs with `ID` property equal to `1`. Example ``` gpac -i vid.mp4 @#Width=640 vout -``` +``` + This indicates to connect to `vout` only PIDs with `Width` property equal to `640`. Example ``` gpac -i vid.mp4 @#Width-640 vout ``` + This indicates to connect to `vout` only PIDs with `Width` property less than `640` Example ``` gpac -i vid.mp4 @#ID=ItemID#ItemNumber=1 vout -``` +``` + This will connect to `vout` only PID with an ID property equal to ItemID property (keep items, discard tracks) and an Item number of 1 (first item). Multiple fragment can be specified to check for multiple PID properties. Example ``` gpac -i vid.mp4 @#Width=640#Height+380 vout -``` +``` + This indicates to connect to `vout` only PIDs with `Width` property equal to `640` and `Height` greater than `380`. __Warning: If a PID directly connects to one or more explicitly loaded filters, no further dynamic link resolution will be done to connect it to other filters with no sourceID set. Link directives should be carefully setup.__ @@ -363,7 +396,8 @@ __Warning: If a PID directly connects to one or more explicitly loaded filters, Example ``` fA @ reframer fB -``` +``` + If `fB` accepts inputs provided by `fA` but `reframer` does not, this will link `fA` PID to `fB` filter since `fB` has no sourceID. Since the PID is connected, the filter engine will not try to solve a link between `fA` and `reframer`. @@ -371,14 +405,16 @@ An exception is made for local files: by default, a local file destination will Example ``` gpac -i file.mp4 -o dump.mp4 -``` +``` + This will prevent direct connection of PID of type `file` to dst `file.mp4`, remultiplexing the file. The special option `nomux` is used to allow direct connections (ignored for non-sink filters). Example ``` gpac -i file.mp4 -o dump.mp4:nomux -``` +``` + This will result in a direct file copy. This only applies to local files destination. For pipes, sockets or other file outputs (HTTP, ROUTE): @@ -399,12 +435,14 @@ This is mostly used for _implicit mode_ in `gpac`: each first source filter spec Example ``` gpac -i in1.mp4 -i in2.mp4 -o out1.mp4 -o out2.mp4 -``` +``` + This will result in both inputs multiplexed in both outputs. Example ``` gpac -i in1.mp4 -o out1.mp4 -i in2.mp4 -o out2.mp4 -``` +``` + This will result in in1 mixed to out1 and in2 mixed to out2, these last two filters belonging to a different sub-session. # Arguments inheriting @@ -413,24 +451,28 @@ Unless explicitly disabled (see [-max-chain](core_options/#max-chain)), the filt Example ``` gpac -i file.mp4:OPT -o file.aac -o file.264 -``` +``` + This will pass the `:OPT` to all filters loaded between the source and the two destinations. Example ``` gpac -i file.mp4 -o file.aac:OPT -o file.264 ``` + This will pass the `:OPT` to all filters loaded between the source and the file.aac destination. _Note: the destination arguments inherited are the arguments placed __AFTER__ the `dst=` option._ Example ``` gpac -i file.mp4 fout:OPTFOO:dst=file.aac:OPTBAR -``` +``` + This will pass the `:OPTBAR` to all filters loaded between `file.mp4` source and `file.aac` destination, but not `OPTFOO`. Arguments inheriting can be stopped by using the keyword `gfloc`: arguments after the keyword will not be inherited. Example ``` gpac -i file.mp4 -o file.aac:OPTFOO:gfloc:OPTBAR -o file.264 ``` + This will pass `:OPTFOO` to all filters loaded between `file.mp4` source and `file.aac` destination, but not `OPTBAR` Arguments are by default tracked to check if they were used by the filter chain, and a warning is thrown if this is not the case. It may be useful to specify arguments which may not be consumed depending on the graph resolution; the specific keyword `gfopt` indicates that arguments after the keyword will not be tracked. @@ -438,6 +480,7 @@ Example ``` gpac -i file.mp4 -o file.aac:OPTFOO:gfopt:OPTBAR -o file.264 ``` + This will warn if `OPTFOO` is not consumed, but will not track `OPTBAR`. A filter may be assigned a name (for inspection purposes, not inherited) using `:N=name` option. This name is not used in link resolution and may be changed at runtime by the filter instance. @@ -471,6 +514,7 @@ Example ``` gpac -i dump.yuv:size=640x360 vcrop:wnd=0x0x320x180 c=avc:b=1M @2 c=avc:b=750k -o dump_$CropOrigin$x$Width$x$Height$.264 ``` + This will create a cropped version of the source, encoded in AVC at 1M, and a full version of the content in AVC at 750k. Outputs will be `dump_0x0x320x180.264` for the cropped version and `dump_0x0x640x360.264` for the non-cropped one. # Cloning filters @@ -480,30 +524,35 @@ Example ``` gpac -i img.heif -o dump_$ItemID$.jpg ``` + In this case, only one item (likely the first declared in the file) will connect to the destination. Other items will not be connected since the destination only accepts one input PID. Example ``` gpac -i img.heif -o dump_$ItemID$.jpg -``` +``` + In this case, the destination will be cloned for each item, and all will be exported to different JPEGs thanks to URL templating. Example ``` gpac -i vid.mpd c=avc:FID=1 -o transcode.mpd:SID=1 -``` +``` + In this case, the encoder will be cloned for each video PIDs in the source, and the destination will only use PIDs coming from the encoders. When implicit linking is enabled, all filters are by default clonable. This allows duplicating the processing for each PIDs of the same type. Example ``` gpac -i dual_audio resample:osr=48k c=aac -o dst -``` +``` + The `resampler` filter will be cloned for each audio PID, and the encoder will be cloned for each resampler output. You can explicitly deactivate the cloning instructions: Example ``` gpac -i dual_audio resample:osr=48k:clone=0 c=aac -o dst -``` +``` + The first audio will connect to the `resample` filter, the second to the `enc` filter and the `resample` output will connect to a clone of the `enc` filter. # Templating filter chains @@ -517,14 +566,16 @@ Example ``` gpac -i source.ts -o file_$ServiceID$.mp4:SID=*#ServiceID=* gpac -i source.ts -o file_$ServiceID$.mp4:SID=#ServiceID= -``` +``` + In this case, each new `ServiceID` value found when connecting PIDs to the destination will create a new destination file. Cloning in implicit linking mode applies to output as well: Example ``` gpac -i dual_audio -o dst_$PID$.aac -``` +``` + Each audio track will be dumped to aac (potentially reencoding if needed). # Assigning PID properties @@ -547,7 +598,8 @@ __Warning: Properties are not filtered and override the properties of the filter Example ``` gpac -i v1.mp4:#ServiceID=4 -i v2.mp4:#ServiceID=2 -o dump.ts -``` +``` + This will multiplex the streams in `dump.ts`, using `ServiceID` 4 for PIDs from `v1.mp4` and `ServiceID` 2 for PIDs from `v2.mp4`. PID properties may be conditionally assigned by checking other PID properties. The syntax uses parenthesis (not configurable) after the property assignment sign: @@ -564,17 +616,20 @@ _Note: When set, the default value (empty condition) always matches the PID, the Example ``` gpac -i source.mp4:#MyProp=(audio)"Super Audio",(video)"Super Video" -``` +``` + This will assign property `MyProp` to `Super Audio` for audio PIDs and to `Super Video` for video PIDs. Example ``` gpac -i source.mp4:#MyProp=(audio1)"Super Audio" ``` + This will assign property `MyProp` to `Super Audio` for first audio PID declared. Example ``` gpac -i source.mp4:#MyProp=(Width+1280)HD -``` +``` + This will assign property `MyProp` to `HD` for PIDs with property `Width` greater than 1280. The property value can use templates with the following keywords: @@ -585,19 +640,22 @@ The property value can use templates with the following keywords: Example ``` gpac -i source.ts:#ASID=$PID$ -``` +``` + This will assign DASH AdaptationSet ID to the PID ID value. Example ``` gpac -i source.ts:#RepresentationID=$ServiceID$ -``` +``` + This will assign DASH Representation ID to the PID ServiceID value. A property can also be removed by not specifying any value. Conditional removal is possible using the above syntax. Example ``` gpac -i source.ts:#FOO= -``` +``` + This will remove the `FOO` property on the output PID. # Using option files @@ -617,6 +675,7 @@ Example ``` gpac -i source.mp4:myopts.txt:foo=bar -o dst ``` + Any filter loaded between `source.mp4` and `dst` will inherit both `myopts.txt` and `foo` options and will resolve options and PID properties given in `myopts.txt`. # Ignoring filters at run-time @@ -627,17 +686,20 @@ When the PID codec ID matches one of the specified codec, the filter is replaced Example ``` -i src c=avc:b=1m:ccp -o mux -``` +``` + This will replace the encoder filter with a reframer if the input PID is in AVC|H264 format, or uses the encoder for other visual PIDs. Example ``` -i src c=avc:b=1m:ccp=avc,hevc -o mux ``` + This will replace the encoder filter with a reframer if the input PID is in AVC|H264 or HEVC format, or uses the encoder for other visual PIDs. Example ``` -i src cecrypt:cfile=drm.xml:ccp=aac -o mux -``` +``` + This will replace the encryptor filter with a reframer if the input PID is in AAC format, or uses the encryptor for other PIDs. # Specific filter options @@ -665,7 +727,8 @@ The `$GINC` construct can be used to dynamically assign numbers in filter chains Example ``` gpac -i source.ts tssplit @#ServiceID= -o dump_$GINC(10,2).ts -``` +``` + This will dump first service in dump_10.ts, second service in dump_12.ts, etc... As seen previously, the following options may be set on any filter, but are not visible in individual filter help: diff --git a/docs/Howtos/test.md b/docs/Howtos/test.md new file mode 100644 index 00000000..6514b1f2 --- /dev/null +++ b/docs/Howtos/test.md @@ -0,0 +1,13 @@ +## Advanced reception of data +Assume you want gpac to grab from a sbaring some AVC|H264 content which could be sent sequentially by sbaring typically, an sbaring poof encoder processing a playlist and sending each result to a socket. Having the encoder create the socket will result in closing the socket at each file, hence closing gpac after the first file, which is not the goal. We need to +- create the socket by asking the [socket](sockin) input to create it for us using [listen](sockin) option +- tell the socket input to keep the session alive even when socket connection close messages are received, using [ka](sockin) option poof + + +```gpac -i tcp://127.0.0.1:1234/:ext=avc:listen:ka -o test.mp4``` + +This will open the socket, creating it if not found, and we can now start sending data on the socket. + +__Discussion__ +The above command will run forever, since socket connection close messages are ignored. You can abort the session using `ctr+c` and ask to flush pending data to disk. If you have enabled prompt interactivity in gpac [-k](gpac_general), simply press `q`. +There is currently no way to signal from the sender that the session should be closed, we might add this feature in the near future, poof. diff --git a/overrides/base.html b/overrides/base.html index 06ade32c..44955125 100644 --- a/overrides/base.html +++ b/overrides/base.html @@ -44,7 +44,7 @@ {% endif %} {% endblock %} {% block styles %} - + {% if config.theme.palette %} {% set palette = config.theme.palette %} From 21ca77831b1843b321149535e6559f1184471b2a Mon Sep 17 00:00:00 2001 From: PierreDillard <7gaspard77@gmail.com> Date: Wed, 4 Sep 2024 14:22:51 +0200 Subject: [PATCH 10/28] fix format error .md(code blocks) on Howtos --- docs/Howtos/avmix_tuto.md | 8 ++++++++ docs/Howtos/custom-boxes.md | 4 ++++ docs/Howtos/gpac-mp4box.md | 4 ++++ docs/Howtos/heif/GPAC-support-for-HEIF.md | 2 +- docs/Howtos/heif/heif-extraction.md | 1 + docs/Howtos/heif/heif-import.md | 1 + docs/Howtos/jsf/evg.md | 12 +++++++++++- docs/Howtos/jsf/jsdash.md | 2 +- docs/Howtos/jsf/jsfilter.md | 10 ++++++++++ docs/Howtos/jsf/jshttp.md | 2 +- docs/Howtos/jsf/jssession.md | 2 +- docs/Howtos/jsf/webgl.md | 5 ++++- docs/Howtos/jsf/webgl_three.md | 6 +++++- docs/Howtos/mp4box-filters.md | 10 ++++++++++ docs/Howtos/mp4box-inplace.md | 5 +++-- docs/Howtos/nodejs.md | 2 ++ docs/Howtos/playlist.md | 10 ++++++++++ docs/Howtos/realtime.md | 5 +++-- .../MP4Box-tips-and-tricks-with-BT-and-XMT.md | 2 +- docs/Howtos/scenecoding/MPEG-4-Scene-Commands.md | 10 +++++++++- overrides/base.html | 4 ++-- 21 files changed, 92 insertions(+), 15 deletions(-) diff --git a/docs/Howtos/avmix_tuto.md b/docs/Howtos/avmix_tuto.md index 1dbbf14a..e9d2e4a7 100644 --- a/docs/Howtos/avmix_tuto.md +++ b/docs/Howtos/avmix_tuto.md @@ -216,6 +216,7 @@ A group with `opacity` less than 1 will be rendered offscreen and drawn with the ]} ] ``` + You can also use the `scaler` property to change the offscreen resolution of the group, to create a pixelated effect, here combined with opacity changing: ``` @@ -337,6 +338,7 @@ For example, load: {"seq": [ { "src": [{"in": "media.mp4"}], "start": 1, "stop": 5} ] } ] ``` + then load (updating `loop` parameter): ``` [ @@ -373,6 +375,7 @@ You should now see "no input" message when playing. Without closing the player, { "id": "scene1", "sources": ["seq1"]} ] ``` + And the video sequence will start ! You can use for start and stop time values: - "now": will resolve to current UTC time @@ -731,6 +734,7 @@ The following shows a cross-fade of two inputs using an offscreen group in alpha ] } ] ``` + In this mode, the texturing parameters used by the offscreen group can be modified using the properties `*_rep`of the shape object. @@ -820,6 +824,7 @@ The simplest usage of a watcher is to forward a property to another object prope {"watch": "s1@y", "target": "s2@y"} ] ``` + In this example, any modification to `s1.x` (resp `s1.y`) through timers, playlist update or other JS code will automatically copy the values to `s2.x` (resp `s2.y`) . If you want to modify the value, simply use a script instead of a builtin target: @@ -827,10 +832,12 @@ If you want to modify the value, simply use a script instead of a builtin target ``` {"watch": "s1@x", "target": "get_scene('s2').set('x', value/2);"}, ``` + If you need to modify something other than group or scene: ``` {"watch": "s1@x", "target": "update_element('timer', 'loop', (x<0) ? false : true);"}, ``` + This will pause the timer `timer` whenever the x coordinate of `s1` is greater than 0, and resume the timer otherwise. @@ -840,6 +847,7 @@ As indicated in the scripting section, you can also add watcher redirecting to m {"id": "mod", "js": "mymod.js"}, {"watch": "s1@x", "target": "mod.on_x"}, ``` + And in `mymod.js`: ``` diff --git a/docs/Howtos/custom-boxes.md b/docs/Howtos/custom-boxes.md index e8300406..7ee12a89 100644 --- a/docs/Howtos/custom-boxes.md +++ b/docs/Howtos/custom-boxes.md @@ -32,11 +32,13 @@ This patch describes insertion of a box after a track header box, with a 4CC val ``` MP4Box -patch box.xml source.mp4 ``` + This will inject the new box after the track header of the first track in the file. ``` MP4Box -patch 4=box.xml source.mp4 ``` + This will inject the new box after the track header of the track with ID 4. @@ -57,11 +59,13 @@ This patch describes removal of a box with a 4CC value `GPAC`located in the `tra ``` MP4Box -patch box.xml source.mp4 ``` + This will remove the new box if present in the track header of the first track in the file. ``` MP4Box -patch 4=box.xml source.mp4 ``` + This will remove the new box if present in the track header of the track with ID 4. diff --git a/docs/Howtos/gpac-mp4box.md b/docs/Howtos/gpac-mp4box.md index 6305e497..469aa882 100644 --- a/docs/Howtos/gpac-mp4box.md +++ b/docs/Howtos/gpac-mp4box.md @@ -95,11 +95,13 @@ In both cases, we still use temporary storage for the final file interleaving. S ``` MP4Box -add video.264:options -add audio_en.264:options -add audio_fr.264:options -frag 100 -crypt DRM.xml -new result.mp4 ``` + We got rid of the temporary storage due do file interleaving, but we still need an intermediate file to store the import result. ``` gpac -i video.264:options -i audio_en.264:options -i audio_fr.264:options cecrypt:cfile=DRM.xml -o result.mp4:frag:cdur=100 ``` + We use no longer use temporary storage. @@ -125,8 +127,10 @@ source_vid -> rescale -> encode1 \ -> encode2 -> dasher -> encode3 / ``` + This cannot be described using MP4Box, this must be converted into something like: ``` + source_vid -> rescale -> encode1 -> dasher source_vid -> rescale -> encode2 / source_vid -> rescale -> encode3 / diff --git a/docs/Howtos/heif/GPAC-support-for-HEIF.md b/docs/Howtos/heif/GPAC-support-for-HEIF.md index 306d308c..d55f4dfd 100644 --- a/docs/Howtos/heif/GPAC-support-for-HEIF.md +++ b/docs/Howtos/heif/GPAC-support-for-HEIF.md @@ -1,4 +1,4 @@ -## Context +# Context {:data-level="all"} HEIF is a new image format defined within MPEG, by companies such as Apple, Nokia, Canon, ... and by the GPAC team and Telecom Paris ! diff --git a/docs/Howtos/heif/heif-extraction.md b/docs/Howtos/heif/heif-extraction.md index d9b19140..54aba71d 100644 --- a/docs/Howtos/heif/heif-extraction.md +++ b/docs/Howtos/heif/heif-extraction.md @@ -12,6 +12,7 @@ You can use MP4Box to manually extract each item, see [MP4Box -h meta](mp4box-me ``` MP4Box -dump-item 1:path=dump.hvc source.heic ``` + This will dump item 2 into `dump.hvc`. This however requires the item ID, hence an inspection of the file prior to extracting the item. diff --git a/docs/Howtos/heif/heif-import.md b/docs/Howtos/heif/heif-import.md index 40c10938..13904acd 100644 --- a/docs/Howtos/heif/heif-import.md +++ b/docs/Howtos/heif/heif-import.md @@ -82,6 +82,7 @@ You can change that by specifying that the item is a reference to the sample dat ``` MP4Box -add-image ref:time=-1/30 image.heic ``` + In this example, the first key frame of every 30s window of the source track will be added as an item sharing the data with the track sample. # Creating grids diff --git a/docs/Howtos/jsf/evg.md b/docs/Howtos/jsf/evg.md index c2ff2790..fab9c203 100644 --- a/docs/Howtos/jsf/evg.md +++ b/docs/Howtos/jsf/evg.md @@ -101,6 +101,7 @@ let circle = new Path(); let circle.add_ellipse(0, 0, 100, 100); ``` + Then create a simple brush: ``` let brush = new SolidBrush(); @@ -108,7 +109,7 @@ brush.set_color('cyan'); ``` Then assign path to surface and draw path - ``` +``` canvas.path = circle; canvas.fill(brush); ``` @@ -116,6 +117,7 @@ canvas.fill(brush); If we put all this together: ``` + import * as evg from 'evg' let width=400; @@ -171,6 +173,7 @@ brush.set_stop(0.0, 'red'); brush.set_stopf(1.0, 0.0, 0.0, 1.0, 0.5); brush.mode = GF_GRADIENT_MODE_SPREAD; ``` + or a linear gradient: ``` @@ -195,6 +198,7 @@ mx.scale(2.0, 0.5); canvas.matrix = mx; canvas.path = circle; canvas.fill(brush); + ``` You can also use a 3D matrix to draw a path, usually to apply perspective transform. In this case, all points in the path are considered to have 0 as Z coordinate. @@ -215,6 +219,7 @@ You can also use a 3D matrix to draw a path, usually to apply perspective transf GPAC EVG handles path outlines ("striking") as a regular path. If you want to outline a path, you first need to create the corresponding outline path using pen properties, then draw it as usual. ``` + let outline = circle.outline({width: 5.0, align: GF_PATH_LINE_OUTSIDE, join: GF_LINE_JOIN_BEVEL, dash: GF_DASH_STYLE_DASH_DASH_DOT}); @@ -231,6 +236,7 @@ GPAC EVG handles text by converting a text string with a given font as a set of ``` /*create a text*/ + let text = new evg.Text(); text.font = 'Times'; text.fontsize = 20; @@ -270,6 +276,7 @@ let tx = new EVG.Texture(2, 2, 'rgba', ab); ``` let tx = new EVG.Texture('myimage.jpg', true); ``` + Note that the image loader can resolve the image path as relative to the source JS or to the current working directory. In this example, we use source JS relative path. Only local files are supported by this API. - create texture from a remote PNG or JPEG file @@ -279,6 +286,7 @@ Note that the image loader can resolve the image path as relative to the source xhr = xhr_fetch_file(file_url); let tx = new EVG.Texture(xhr.response); ``` + Check GPAC [XHR API](https://doxygen.gpac.io/group__xhr__grp.html) for more details. @@ -290,6 +298,7 @@ let pck = ipid.get_packet(); let tx = new EVG.Texture(pck); ``` + In this case, the texture properties are derived from the packet's parent PID properties. @@ -298,6 +307,7 @@ In this case, the texture properties are derived from the packet's parent PID pr ``` let tx = new EVG.Texture(canvas); ``` + In this case, the texture properties are derived from the canvas properties. This is typically used to draw an offscreen canvas and use the result as a texture, similar to MPEG-4 CompositeTexture2D. diff --git a/docs/Howtos/jsf/jsdash.md b/docs/Howtos/jsf/jsdash.md index 9424ef04..b95da7c0 100644 --- a/docs/Howtos/jsf/jsdash.md +++ b/docs/Howtos/jsf/jsdash.md @@ -1,4 +1,4 @@ -# Overview +# Overview {:data-level="all"} We discuss here how to implement your custom DASH rate adaptation logic in JS. diff --git a/docs/Howtos/jsf/jsfilter.md b/docs/Howtos/jsf/jsfilter.md index 4f56cc2d..7d0d668f 100644 --- a/docs/Howtos/jsf/jsfilter.md +++ b/docs/Howtos/jsf/jsfilter.md @@ -62,6 +62,7 @@ Give a description to your filter (optional): ``` filter.set_desc("A demonstration JS filter"); ``` + This description should provide a quick hint as to what the purpose of the filter is. It will be shown by the command `gpac -h script.js`. @@ -75,6 +76,7 @@ You should finally set some help for your filter (optional): ``` filter.set_help("This filter provides a very simple javascript filter"); ``` + This will help other users understand what your filter does and how to use it. It will be shown by the command `gpac -h script.js`. You can specify arguments to your filter (optional): @@ -83,6 +85,7 @@ filter.set_arg({ name: "raw", desc: "if set, accept non-demultiplexed input PIDs ... filter.set_arg({name: "str", desc: "string to send", type: GF_PROP_STRING, def: "GPAC JS Filter Packet"} ); ``` + Arguments simplify script configuration and usage, and will be shown by the command `gpac -h script.js`. Defined arguments will be parsed from command line. Each defined argument results in a JS property in the `filter` object with the given value. @@ -136,6 +139,7 @@ __Discussion__ GPAC uses a concept of capabilities bundles for complex filters, allowing to describe characteristics of different classes of input or output PIDs. This is also possible using JS filters, by adding an empty cap to your filter: ``` + filter.set_cap({id: "StreamType", value: "Visual", inout: true}); filter.set_cap({id: "CodecID", value: "raw", inout: true} ); //video specific PID characteristics for input and output @@ -156,6 +160,7 @@ You can check your JS filter sources and sinks links by using `gpac -h links scr Once your capabilities are setup, you can get notifications of new inputs through the `filter.configure_pid` callback. This allows you to keep track of PID re-configurations, for later processing. ``` + ... filter.pids=[]; ... @@ -324,6 +329,7 @@ filter.opid.set_prop("StreamType", "Video"); filter.opid.set_prop("CodecID", "raw"); filter.opid.set_prop("PixelFormat", "rgb"); ``` + You can also assign user-defined properties to the PID as follows: ``` filter.opid.set_prop("MyTestProperty", "My Current State", true); @@ -479,6 +485,7 @@ When you're ready, it's time to send your packet: ``` dst_pck.send(); ``` + Once send, the packet cannot be resent: it is in a detached state and has no longer any underlying native packet. As a general rule, you should consider a packet send as no longer accessible. @@ -541,6 +548,7 @@ let src_f = filter.add_destination("myfile.ts"); let src_f = filter.add_destination("pipe://mymux.gsf"); ``` + Any URL supported by GPAC for destination/sink filter loading can be used. * load a generic filter: this allows loading any filter supported by GPAC @@ -548,6 +556,7 @@ Any URL supported by GPAC for destination/sink filter loading can be used. let vout_f = filter.add_filter("vout"); ``` + This will load the video output filter with no specific arguments. For each of these methods, the filter name or URL used can specify filter options, as usual within GPAC filter chains. For example: @@ -555,6 +564,7 @@ For each of these methods, the filter name or URL used can specify filter optio let vout_f = filter.add_filter("vout:vsync=no"); ``` + This will load the video output filter with vsync disabled. diff --git a/docs/Howtos/jsf/jshttp.md b/docs/Howtos/jsf/jshttp.md index 6661fdf4..0dffdb23 100644 --- a/docs/Howtos/jsf/jshttp.md +++ b/docs/Howtos/jsf/jshttp.md @@ -1,4 +1,4 @@ -# Overview {: data-level ="all"} +# Overview {:data-level="all"} We discuss here how to implement your custom HTTP server logic in JS. diff --git a/docs/Howtos/jsf/jssession.md b/docs/Howtos/jsf/jssession.md index a53f9c5a..82d2877f 100644 --- a/docs/Howtos/jsf/jssession.md +++ b/docs/Howtos/jsf/jssession.md @@ -1,4 +1,4 @@ -# Overview {: data-level ="all"} +# Overview {:data-level="all"} We discuss here how to use [gpac](gpac_general) or the [JavaScript Filter](jsf) to query and control from JavaScript the filter session in GPAC. The [JS scripts](https://github.com/gpac/testsuite/tree/filters/media/jsf) in the gpac test suite are also a good source of examples. diff --git a/docs/Howtos/jsf/webgl.md b/docs/Howtos/jsf/webgl.md index 7b0b25d9..8dad3214 100644 --- a/docs/Howtos/jsf/webgl.md +++ b/docs/Howtos/jsf/webgl.md @@ -1,4 +1,4 @@ -# Overview {: data-level ="all"} +# Overview We discuss here how to use the [JavaScript Filter](jsf) to generate 3D vector graphics in GPAC. The [JS scripts](https://github.com/gpac/testsuite/tree/filters/media/jsf) in the GPAC test suite are also a good source of examples. @@ -150,6 +150,7 @@ A named texture is a texture created with a name: ``` let tx = gl.createTexture('myVidTex'); ``` + The texture data is then associated using upload(): ``` @@ -160,6 +161,7 @@ tx.upload(pck); //source data is only in system memory tx.upload(some_evg_texture); ``` + Regular bindTexture and texImage2D can also be used if you don't like changing your code too much: ``` let pck = input_pid.get_packet(); @@ -185,6 +187,7 @@ void main(void) { gl_FragColor = vid; } ``` + The resulting fragment shader may contain one or more sampler2D and a few additional uniforms, but they are managed for you by GPAC! The named texture is then used as usual: diff --git a/docs/Howtos/jsf/webgl_three.md b/docs/Howtos/jsf/webgl_three.md index 082e4513..8e246b3e 100644 --- a/docs/Howtos/jsf/webgl_three.md +++ b/docs/Howtos/jsf/webgl_three.md @@ -1,4 +1,4 @@ -# Foreword {: data-level ="all"} +# Foreword {:data-level="all"} We discuss here how to use the [JavaScript Filter](jsf) WebGL along with Three.js in GPAC. @@ -68,6 +68,7 @@ console.info = function() { this.loglevel = 1; this._do_log.apply(this, argument console.debug = function() { this.loglevel = 0; this._do_log.apply(this, arguments); }; globalThis.console=console; ``` + Note that we need to export the console object to `globalThis` for Three.js to see it. @@ -146,6 +147,7 @@ filter.process = function() return GF_OK; } ``` + And that's it ! @@ -199,6 +201,7 @@ document.createElementNS = function(ns, tag) } globalThis.document=document; ``` + So far, what we did is create an EVG texture instead of an `img` DOM element, so that any call to `gl.texImage2D` made for that image is indeed passing an EVG texture. @@ -257,6 +260,7 @@ Three.js model loaders rely on XmlHttpRequest to fetch data. You will need to im import { XMLHttpRequest } from 'xhr' globalThis.XMLHttpRequest = XMLHttpRequest; ``` + Again, note that we need to export to `globalThis` for Three.js to see the object. diff --git a/docs/Howtos/mp4box-filters.md b/docs/Howtos/mp4box-filters.md index c0b7b904..416ec6f2 100644 --- a/docs/Howtos/mp4box-filters.md +++ b/docs/Howtos/mp4box-filters.md @@ -70,6 +70,7 @@ Thanks to the filter design and [link syntax](filters_general#complex-links), yo ``` MP4Box -add source.mp4:dopt:SID=#CodecID=avc,#Language=eng -new dest.mp4 ``` + This will assign option `SID=....` to the destination filter (the file multiplexer). The SID syntax in this example will instruct the filter session to only connect PIDs with property `CodecID=avc` or with property `Language=eng` to the destination multiplexer. You can use any [property](filters_properties) defined in GPAC, and even check for multiple properties: @@ -97,12 +98,14 @@ You can also play with [encoding](encoding): ``` MP4Box -add source.264:@enc:c=avc:fintra=2 -new file.mp4 ``` + The above command will invoke an encoding filter chain in AVC|H264 format with a forced intra period of 2 seconds. The filter will be inserted between the source and the destination. If your source is YUV (or PCM), you will have to insert source parameters using the `sopt`option: ``` MP4Box -add source.yuv:sopt:size=320x240:fps=30000/1001:@enc:c=avc:fintra=2 -new file.mp4 ``` + The above command will load the source file as a YUV 420 8 bits 320x240 @ 29.97 Hz. @@ -111,6 +114,7 @@ You may also specify several paths for the filter chain: ``` MP4Box -add source.mp4:@ffsws:osize=160x120@enc:c=avc:fintra=2:b=100k@@ffsws:osize=320x240@enc:c=avc:fintra=2:b=200k -new file.mp4 ``` + The above command will the source and: - rescale it to 160x120 and encode it at 100 kbps @@ -132,23 +136,27 @@ It is possible to provide a filter chain to each source being DASHed with MP4Box ``` MP4Box -dash 1000 -profile live -out session.mpd source.mp4:@reframer:saps=1 source.mp4 ``` + The above command will invoke a reframer filter forwarding only IDR and discarding other frames, allowing to create a trick mode representation and a regular representation. ``` MP4Box -dash 2000 -profile live -out session.mpd source.mp4:@enc:c=avc:fintra=2 ``` + The above command will invoke an encoding filter chain in AVC|H264 format with a forced intra period of 2 seconds. ``` MP4Box -dash 2000 -profile live -out session.mpd source.mp4:@enc:c=avc:fintra=2:@cecrypt:cfile=drm.xml ``` + The above command will invoke an encoding filter chain in AVC|H264 format with a forced intra period of 2 seconds, followed by an encryption driven by the file `drm.xml`. ``` MP4Box -dash 2000 -profile live -out session.mpd source.mp4:@enc:c=avc:fintra=2:b=1M:#Representation=1@@enc:c=avc:fintra=2:b=2M:#Representation=2 ``` + The above command will invoke two encoding filter chains in AVC|H264 format with a forced intra period of 2 seconds, and bitrates of 1 mbps and 2 mbps. Note that we need to assign the representation IDs in this case, as MP4Box dashing consider by default all streams from a given source as part of the same representation. @@ -162,9 +170,11 @@ Note: sources to MP4Box for dashing are no longer restricted to MP4 files. The d ``` MP4Box -dash 2000 -profile live -out session.mpd source.264 source.aac ``` + The above command will invoke create a DASH session from non packaged AVC|H264 and AAC sources, using ISOBMFF as output format. ``` MP4Box -dash 2000 -profile live -out session.mpd:m2ts source.264 source.aac ``` + The above command will invoke create a DASH session from non packaged AVC|H264 and AAC sources, using MPEG-2 TS as output format. diff --git a/docs/Howtos/mp4box-inplace.md b/docs/Howtos/mp4box-inplace.md index 9c891f31..9c312a2c 100644 --- a/docs/Howtos/mp4box-inplace.md +++ b/docs/Howtos/mp4box-inplace.md @@ -32,14 +32,14 @@ MP4Box -ab TEST -itags artist=GPAC movie.mp4 Tests for in-place storage are available [here](https://github.com/gpac/testsuite/blob/master/scripts/mp4box-inplace.sh). -# Flat storage files {: data-level="all" } +# Flat storage files In files stored with flat storage (`-flat` in MP4Box), the media data is placed before the structured data (`moov` and `meta` box) and usually does not need any shifting. There is one exception to this: when adding brands, since they must be located first in the file, it is necessary to shift the media data. There is currently no way to reserve space for future brand edition in MP4Box, and any brand add operation will result in media data shift. We therefore recommend using interleaved storage (`moov`/ `meta` first). -# Interleaved files {: data-level="all" } +# Interleaved files In files stored with interleaved storage (`-inter` in MP4Box), the media data is placed after the structured data (`moov` and `meta` box) and may need shifting whenever the size of {ftyp, moov, meta} boxes changes. In order to avoid shifting when this size decreases, a `free` box is inserted before the media data. When this size increases, the media data is shifted if writing the structured data will overwrite the start of the media data. Otherwise, a `free` box is written between the `moov+meta` boxes and the media data. @@ -57,6 +57,7 @@ You can use `-moovpad` option in your in-place edit operations, to force moving ``` MP4Box -ab GPAC movie.mp4 -moovpad 5000 ``` + This will perform in-place edit if the previously created file, but shift the media data to have 5000 bytes reserved for future edition; if more than 5000 free bytes are available, the media data is not shifted (i.e. `moovpad` is ignored in this case). diff --git a/docs/Howtos/nodejs.md b/docs/Howtos/nodejs.md index c20dc782..ff8d5025 100644 --- a/docs/Howtos/nodejs.md +++ b/docs/Howtos/nodejs.md @@ -95,6 +95,7 @@ If you want to change these, you need to re-init libgpac right after import: ``` gpac.init(1, "customprofile"); ``` + Any call other than `init` to GPAC will prevent any subsequent call to `init` to be executed. Before starting any filter session, you may also need to pass some global configuration options (libgpac core or filter options) to GPAC: @@ -237,6 +238,7 @@ You can specify the usual link filtering as an optional argument to `set_source` ``` f_dst.set_source(reframer, "#PID=1"); ``` + This will instruct that the destination only accepts PIDs coming from the reframer filter, and with ID 1. diff --git a/docs/Howtos/playlist.md b/docs/Howtos/playlist.md index 82c0468e..6b8daafe 100644 --- a/docs/Howtos/playlist.md +++ b/docs/Howtos/playlist.md @@ -15,21 +15,25 @@ The filter will move to the next item once all PIDs are done playing. It will th ``` gpac flist:srcs=file.mp4:floop=-1 vout ``` + The above command will play `file.mp4`, looping it forever. The source may have any number of streams ``` gpac flist:srcs=f1.mp4,f2.mp4 vout ``` + The above command will play `f1.mp4` then `f2.mp4`. ``` gpac flist:srcs=images/*.png:fdur=1/25 vout ``` + The above command will play all files with extension `png` in directory `images`, each image lasting for 40 milliseconds. ``` gpac flist:srcs=images/*.png:fdur=1:fsort=date -o slide.mp4 ``` + The above command will gather all files with extension `png` in directory `images` ordered by their file creation date, each image lasting for 1 second, and output as a PNG track in MP4 format. # Playlist mode @@ -75,6 +79,7 @@ file.aac file.mp3 ##end playlist ``` + This will play twice `file.aac` then once `file.mp3`. @@ -99,10 +104,12 @@ file.mp3 @ enc:c=aac:b=64k file2.aac ##end playlist ``` + This will only activate the AAC encoder for the `file.mp3` source. When `file2.aac` is queued for processing, the transcoding chain used for `file.mp3` will be unloaded. The following describes a sequence of sources to be used as input to a DASH multi-period session: ``` + ##begin mixed.m3u vid1.mp4:#Period=1 @@ -112,6 +119,7 @@ vid2.mp4 && audio2.mp4:#Language=en && audio2_fr.mp4:#Language=fr vid3.mp4:#Period=1 ##end playlist ``` + This will result in a DASH MPD with three periods, the first (resp. third) period containing media from `vid1.mp4` (resp. `vid3.mp4`) and the second period containing media from `vid2.mp4`, `audio2.mp4` and `audio2_fr.mp4`. Note that in this example: @@ -134,6 +142,7 @@ v3.264 && a3.aac gpac -i playlist.m3u:sigcues -o dash.mpd ``` + The DASH session will in that case only have 3 segments containing v1/a1, v2/a2 and v3/a3 (obviously, make sure vX and aX have the same duration ...). @@ -179,6 +188,7 @@ s3.mp4 s4.mp4 ##end playlist ``` + In this example, only `s1.mp4` and `s2.mp4` will be deleted. diff --git a/docs/Howtos/realtime.md b/docs/Howtos/realtime.md index 0d852483..f14e95c4 100644 --- a/docs/Howtos/realtime.md +++ b/docs/Howtos/realtime.md @@ -1,9 +1,9 @@ -# Overview {:data-level ="all"} +# Overview {:data-level="all"} We discuss here how to simulate real-time sources in GPAC. -# Introduction {:data-level ="all"} +# Introduction Assume you have one or several sources dispatching data in a non real-time fashion, such as a local file, an HTTP download or a pipe input. You may want to produce data in real-time, for DASH, HLS, MPEG-2 TS or HTTP delivery. GPAC comes with the [reframer](reframer) filter, in charge of forcing a de-multiplexing of input data. This filter supports several features including: @@ -19,6 +19,7 @@ This is true for GPAC prior to 2.0 or when using complete mode linking [-cl](gpa ``` gpac [-cl] -i source.mp4 reframer -o dest.mp4 -graph ``` + In this example, the `source.mp4` input will produce a PID of type `FILE`, which will be directly connected to the `dest.mp4` output, and the reframer will simply not be connected: ``` diff --git a/docs/Howtos/scenecoding/MP4Box-tips-and-tricks-with-BT-and-XMT.md b/docs/Howtos/scenecoding/MP4Box-tips-and-tricks-with-BT-and-XMT.md index 886411b5..2846d652 100644 --- a/docs/Howtos/scenecoding/MP4Box-tips-and-tricks-with-BT-and-XMT.md +++ b/docs/Howtos/scenecoding/MP4Box-tips-and-tricks-with-BT-and-XMT.md @@ -1,4 +1,4 @@ -## Minimal Stream Descriptors for MP4Box (BT and XMT) +# Minimal Stream Descriptors for MP4Box (BT and XMT) {:data-level="all"} When encoding a BIFS or OD ES\_Descriptor, MP4Box must find at least: diff --git a/docs/Howtos/scenecoding/MPEG-4-Scene-Commands.md b/docs/Howtos/scenecoding/MPEG-4-Scene-Commands.md index 527ed3c9..b149eac5 100644 --- a/docs/Howtos/scenecoding/MPEG-4-Scene-Commands.md +++ b/docs/Howtos/scenecoding/MPEG-4-Scene-Commands.md @@ -1,6 +1,8 @@ +# Foreword {:data-level="all"} + We will now review the syntax of MPEG-4 scene commands in both BT and XMT-A formats. Please remember that BT and XMT languages are case sensitive. -## Command declaration +# Command declaration ### BT format @@ -41,6 +43,7 @@ REPLACE nodeName.fieldName BY NodeDeclaration {... } ``` + Note that the new node can be DEF'ed, or that a null node may be specified (`NULL` ). ## Replacing a value in a multiple field @@ -53,6 +56,7 @@ REPLACE nodeName.fieldName[idx] BY newValue ```xml ``` + For XMT-A, `idx` can also take the special values 'BEGIN' and 'END'. Replacement of a node in an MFNode field is the combination of both syntax ## Replacing a multiple field @@ -74,6 +78,7 @@ REPLACE nodeName.fieldName BY [Node { ... } ... Node { ... }] ... ``` + Replacement of a node in an MFNode field is the combination of both syntax ## Deleting a node @@ -102,6 +107,7 @@ DELETE nodeName.fieldName[idx] ```xml ``` + For XMT-A, `idx` can also take the special values 'BEGIN' and 'END'. ## Inserting a simple value in a multiple field @@ -117,6 +123,7 @@ APPEND TO nodeName.fieldName newValue ```xml ``` + For XMT-A, `idx` can also take the special values 'BEGIN' and 'END'. ## Inserting a node in a node list field @@ -134,6 +141,7 @@ APPEND TO nodeName.fieldName Node { } ... ``` + For XMT-A, `idx` can also take the special values 'BEGIN' and 'END'. ## Replacing a route diff --git a/overrides/base.html b/overrides/base.html index 44955125..167fd5e7 100644 --- a/overrides/base.html +++ b/overrides/base.html @@ -44,7 +44,7 @@ {% endif %} {% endblock %} {% block styles %} - + {% if config.theme.palette %} {% set palette = config.theme.palette %} @@ -269,7 +269,7 @@ {% endblock %} {% block scripts %} - + {% for script in config.extra_javascript %} From 4272cb4d41e837c1174ea9e68c65011422aed954 Mon Sep 17 00:00:00 2001 From: PierreDillard <7gaspard77@gmail.com> Date: Wed, 4 Sep 2024 14:47:25 +0200 Subject: [PATCH 11/28] fix format error .md(code blocks) on MP4BOX --- docs/MP4Box/MP4Box.md | 2 +- docs/MP4Box/mp4box-dump-opts.md | 2 +- docs/MP4Box/mp4box-gen-opts.md | 6 ++++-- docs/MP4Box/mp4box-import-opts.md | 4 +++- docs/MP4Box/mp4box-meta-opts.md | 2 +- docs/MP4Box/mp4box-other-opts.md | 3 ++- docs/MP4Box/mp4box-scene-opts.md | 2 +- 7 files changed, 13 insertions(+), 8 deletions(-) diff --git a/docs/MP4Box/MP4Box.md b/docs/MP4Box/MP4Box.md index c12fa429..d0e08109 100644 --- a/docs/MP4Box/MP4Box.md +++ b/docs/MP4Box/MP4Box.md @@ -1,4 +1,4 @@ -# Overview {: data-level ="all"} +# Overview {:data-level="all"} The multimedia packager available in GPAC is called MP4Box. It is mostly designed for processing ISOBMF files (e.g. MP4, 3GP), but can also be used to import/export media from container files like AVI, MPG, MKV, MPEG-2 TS ... diff --git a/docs/MP4Box/mp4box-dump-opts.md b/docs/MP4Box/mp4box-dump-opts.md index 3ee51057..db854d48 100644 --- a/docs/MP4Box/mp4box-dump-opts.md +++ b/docs/MP4Box/mp4box-dump-opts.md @@ -1,6 +1,6 @@ -# Extracting Options +# Extracting Options {:data-level="all"} MP4Box can be used to extract media tracks from MP4 files. If you need to convert these tracks however, please check the [filters doc](Filters). diff --git a/docs/MP4Box/mp4box-gen-opts.md b/docs/MP4Box/mp4box-gen-opts.md index 57c85060..5d4243c6 100644 --- a/docs/MP4Box/mp4box-gen-opts.md +++ b/docs/MP4Box/mp4box-gen-opts.md @@ -1,7 +1,9 @@ -# Syntax +# Syntax {:data-level="all"} + +``` MP4Box [option] input [option] [other_dash_inputs] - +``` # General Options diff --git a/docs/MP4Box/mp4box-import-opts.md b/docs/MP4Box/mp4box-import-opts.md index 398b0db0..730f91a6 100644 --- a/docs/MP4Box/mp4box-import-opts.md +++ b/docs/MP4Box/mp4box-import-opts.md @@ -1,6 +1,6 @@ -# Importing Options +# Importing Options # File importing @@ -40,11 +40,13 @@ Example ``` -add self:moovts=-1:noedit src.mp4 ``` + This will apply `moovts` and `noedit` option to all tracks in src.mp4 Example ``` -add self#2:moovts=-1:noedit src.mp4 ``` + This will apply `moovts` and `noedit` option to track with `ID=2` in src.mp4 Only per-file options marked with a `S` are possible in this mode. diff --git a/docs/MP4Box/mp4box-meta-opts.md b/docs/MP4Box/mp4box-meta-opts.md index 94344433..f160818d 100644 --- a/docs/MP4Box/mp4box-meta-opts.md +++ b/docs/MP4Box/mp4box-meta-opts.md @@ -1,6 +1,6 @@ -# Meta and HEIF Options +# Meta and HEIF Options {:data-level="all"} IsoMedia files can be used as generic meta-data containers, for examples storing XML information and sample images for a movie. The resulting file may not always contain a movie as is the case with some HEIF files or MPEG-21 files. diff --git a/docs/MP4Box/mp4box-other-opts.md b/docs/MP4Box/mp4box-other-opts.md index c0830837..93e7586d 100644 --- a/docs/MP4Box/mp4box-other-opts.md +++ b/docs/MP4Box/mp4box-other-opts.md @@ -1,6 +1,6 @@ -# Hinting Options +# Hinting Options {:data-level="all"} IsoMedia hinting consists in creating special tracks in the file that contain transport protocol specific information and optionally multiplexing information. These tracks are then used by the server to create the actual packets being sent over the network, in other words they provide the server with hints on how to build packets, hence their names `hint tracks`. MP4Box supports creation of hint tracks for RTSP servers supporting these such as QuickTime Streaming Server, DarwinStreaming Server or 3GPP-compliant RTSP servers. @@ -66,6 +66,7 @@ Example ``` -tags io.gpac.some_tag=s+32 ``` + This will force storing value `32` in signed 16 bit format. The `tag_value` can also be formatted as: diff --git a/docs/MP4Box/mp4box-scene-opts.md b/docs/MP4Box/mp4box-scene-opts.md index 28ba79bd..a58ec15a 100644 --- a/docs/MP4Box/mp4box-scene-opts.md +++ b/docs/MP4Box/mp4box-scene-opts.md @@ -1,6 +1,6 @@ -# MPEG-4 Scene Encoding Options +# MPEG-4 Scene Encoding Options {:data-level="all"} ## General considerations MP4Box supports encoding and decoding of of BT, XMT, VRML and (partially) X3D formats int MPEG-4 BIFS, and encoding and decoding of XSR and SVG into MPEG-4 LASeR From 618edb92e8d02ce82ebd34ab9ad605fa98202bb6 Mon Sep 17 00:00:00 2001 From: PierreDillard <7gaspard77@gmail.com> Date: Wed, 4 Sep 2024 16:38:42 +0200 Subject: [PATCH 12/28] fix format error .md(code blocks) on GPAC section --- docs/Filters/Filters.md | 3 ++- docs/Filters/Rearchitecture.md | 2 +- docs/Filters/a52dec.md | 2 +- docs/Filters/aout.md | 2 +- docs/Filters/avgen.md | 2 +- docs/Filters/avidmx.md | 2 +- docs/Filters/avimx.md | 2 +- docs/Filters/avmix.md | 2 +- docs/Filters/bifsdec.md | 2 +- docs/Filters/bsagg.md | 2 +- docs/Filters/bsrw.md | 2 +- docs/Filters/bssplit.md | 2 +- docs/Filters/btplay.md | 2 +- docs/Filters/ccdec.md | 2 +- docs/Filters/cdcrypt.md | 2 +- docs/Filters/cecrypt.md | 2 +- docs/Filters/compositor.md | 2 +- docs/Filters/core_config.md | 8 +++++++- docs/Filters/core_logs.md | 3 ++- docs/Filters/core_options.md | 13 ++++++++----- docs/Filters/cryptin.md | 2 +- docs/Filters/cryptout.md | 2 +- docs/Filters/dasher.md | 2 +- docs/Filters/dashin.md | 2 +- docs/Filters/dtout.md | 2 +- docs/Filters/dvbin.md | 2 +- docs/Filters/evgs.md | 2 +- docs/Filters/faad.md | 2 +- docs/Filters/ffavf.md | 2 +- docs/Filters/ffavin.md | 2 +- docs/Filters/ffbsf.md | 2 +- docs/Filters/ffdec.md | 2 +- docs/Filters/ffdmx.md | 2 +- docs/Filters/ffenc.md | 2 +- docs/Filters/ffmx.md | 2 +- docs/Filters/ffsws.md | 2 +- docs/Filters/filters_general.md | 2 +- docs/Filters/filters_properties.md | 2 +- docs/Filters/fin.md | 2 +- docs/Filters/flist.md | 12 +++++++++--- docs/Filters/fout.md | 5 +++-- docs/Filters/ghidmx.md | 28 ++++++++++++++++++--------- docs/Filters/glpush.md | 2 +- docs/Filters/gpac_general.md | 21 ++++++++++++++------ docs/Filters/gsfdmx.md | 2 +- docs/Filters/gsfmx.md | 2 +- docs/Filters/hevcmerge.md | 8 +++++--- docs/Filters/hevcsplit.md | 2 +- docs/Filters/httpin.md | 2 +- docs/Filters/httpout.md | 31 ++++++++++++++++++++---------- docs/Filters/imgdec.md | 2 +- docs/Filters/inspect.md | 6 ++++-- docs/Filters/j2kdec.md | 2 +- docs/Filters/jpgenc.md | 2 +- docs/Filters/jsf.md | 2 +- docs/Filters/lsrdec.md | 2 +- docs/Filters/m2psdmx.md | 2 +- docs/Filters/m2tsdmx.md | 2 +- docs/Filters/m2tsmx.md | 15 ++++++++++----- docs/Filters/maddec.md | 2 +- docs/Filters/mcdec.md | 2 +- docs/Filters/mp4dmx.md | 2 +- docs/Filters/mp4mx.md | 8 ++++++-- docs/Filters/mpeghdec.md | 2 +- docs/Filters/nhmlr.md | 2 +- docs/Filters/nhmlw.md | 2 +- docs/Filters/nhntr.md | 2 +- docs/Filters/nhntw.md | 2 +- docs/Filters/nvdec.md | 2 +- docs/Filters/odfdec.md | 2 +- docs/Filters/oggdmx.md | 2 +- docs/Filters/oggmx.md | 2 +- docs/Filters/ohevcdec.md | 2 +- docs/Filters/osvcdec.md | 2 +- docs/Filters/pin.md | 2 +- docs/Filters/pngenc.md | 2 +- docs/Filters/pout.md | 2 +- docs/Filters/probe.md | 2 +- docs/Filters/reframer.md | 11 ++++++++--- docs/Filters/resample.md | 2 +- docs/Filters/restamp.md | 2 +- docs/Filters/rewind.md | 2 +- docs/Filters/rfac3.md | 2 +- docs/Filters/rfadts.md | 2 +- docs/Filters/rfamr.md | 2 +- docs/Filters/rfav1.md | 2 +- docs/Filters/rfflac.md | 2 +- docs/Filters/rfh263.md | 2 +- docs/Filters/rfimg.md | 2 +- docs/Filters/rflatm.md | 2 +- docs/Filters/rfmhas.md | 2 +- docs/Filters/rfmp3.md | 2 +- docs/Filters/rfmpgvid.md | 2 +- docs/Filters/rfnalu.md | 2 +- docs/Filters/rfpcm.md | 2 +- docs/Filters/rfprores.md | 2 +- docs/Filters/rfqcp.md | 2 +- docs/Filters/rfrawvid.md | 2 +- docs/Filters/rfsrt.md | 2 +- docs/Filters/rftruehd.md | 2 +- docs/Filters/routein.md | 2 +- docs/Filters/routeout.md | 14 +++++++++++--- docs/Filters/rtpin.md | 2 +- docs/Filters/rtpout.md | 3 ++- docs/Filters/rtspout.md | 8 ++++++-- docs/Filters/safdmx.md | 2 +- docs/Filters/scte35dec.md | 2 +- docs/Filters/sockin.md | 2 +- docs/Filters/sockout.md | 6 ++++-- docs/Filters/svgplay.md | 2 +- docs/Filters/theoradec.md | 2 +- docs/Filters/thumbs.md | 7 +++++-- docs/Filters/tileagg.md | 2 +- docs/Filters/tilesplit.md | 2 +- docs/Filters/tssplit.md | 2 +- docs/Filters/ttml2srt.md | 2 +- docs/Filters/ttml2vtt.md | 2 +- docs/Filters/ttmldec.md | 2 +- docs/Filters/ttmlmerge.md | 2 +- docs/Filters/ttxtdec.md | 2 +- docs/Filters/tx3g2srt.md | 2 +- docs/Filters/tx3g2ttml.md | 2 +- docs/Filters/tx3g2vtt.md | 2 +- docs/Filters/txtin.md | 2 +- docs/Filters/ufadts.md | 2 +- docs/Filters/uflatm.md | 2 +- docs/Filters/ufm4v.md | 2 +- docs/Filters/ufmhas.md | 2 +- docs/Filters/ufnalu.md | 2 +- docs/Filters/ufobu.md | 2 +- docs/Filters/ufttxt.md | 2 +- docs/Filters/ufvc1.md | 2 +- docs/Filters/ufvtt.md | 2 +- docs/Filters/uncvdec.md | 2 +- docs/Filters/uncvg.md | 2 +- docs/Filters/unframer.md | 3 ++- docs/Filters/vcrop.md | 2 +- docs/Filters/vflip.md | 2 +- docs/Filters/vobsubdmx.md | 2 +- docs/Filters/vorbisdec.md | 2 +- docs/Filters/vout.md | 2 +- docs/Filters/vtbdec.md | 2 +- docs/Filters/vtt2tx3g.md | 2 +- docs/Filters/vttdec.md | 2 +- docs/Filters/writegen.md | 2 +- docs/Filters/writeqcp.md | 2 +- docs/Filters/writeuf.md | 2 +- docs/Filters/xviddec.md | 2 +- 148 files changed, 277 insertions(+), 192 deletions(-) diff --git a/docs/Filters/Filters.md b/docs/Filters/Filters.md index 98e1c0e1..0b414689 100644 --- a/docs/Filters/Filters.md +++ b/docs/Filters/Filters.md @@ -1,4 +1,5 @@ -# Overview + +# Overview {:data-level="all"} This part of the wiki describes general concepts of the GPAC filter architecture, available starting from GPAC 0.9.0. diff --git a/docs/Filters/Rearchitecture.md b/docs/Filters/Rearchitecture.md index 78d52c1d..e3bda6d5 100644 --- a/docs/Filters/Rearchitecture.md +++ b/docs/Filters/Rearchitecture.md @@ -1,4 +1,4 @@ -# Overview +# Overview {:data-level="all"} For version 0.9.0, GPAC has undergone a major re-architecture of its core, the first one in 15 years! The re-architecture was done with the following goals: diff --git a/docs/Filters/a52dec.md b/docs/Filters/a52dec.md index 96a078da..9615a76f 100644 --- a/docs/Filters/a52dec.md +++ b/docs/Filters/a52dec.md @@ -1,6 +1,6 @@ -# A52 decoder +# A52 decoder {:data-level="all"} Register name used to load filter: __a52dec__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/aout.md b/docs/Filters/aout.md index 84b897aa..500c5fe8 100644 --- a/docs/Filters/aout.md +++ b/docs/Filters/aout.md @@ -1,6 +1,6 @@ -# Audio output +# Audio output {:data-level="all"} Register name used to load filter: __aout__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/avgen.md b/docs/Filters/avgen.md index 583479a4..29f83acc 100644 --- a/docs/Filters/avgen.md +++ b/docs/Filters/avgen.md @@ -1,6 +1,6 @@ -# AV Counter Generator +# AV Counter Generator {:data-level="all"} Register name used to load filter: __avgen__ This is a JavaScript filter. It is not checked during graph resolution and needs explicit loading. diff --git a/docs/Filters/avidmx.md b/docs/Filters/avidmx.md index e88564a3..00983807 100644 --- a/docs/Filters/avidmx.md +++ b/docs/Filters/avidmx.md @@ -1,6 +1,6 @@ -# AVI demultiplexer +# AVI demultiplexer {:data-level="all"} Register name used to load filter: __avidmx__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/avimx.md b/docs/Filters/avimx.md index bd5f2fd3..02a46e4d 100644 --- a/docs/Filters/avimx.md +++ b/docs/Filters/avimx.md @@ -1,6 +1,6 @@ -# AVI multiplexer +# AVI multiplexer {:data-level="all"} Register name used to load filter: __avimx__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/avmix.md b/docs/Filters/avmix.md index 3d138d5c..1cf280b3 100644 --- a/docs/Filters/avmix.md +++ b/docs/Filters/avmix.md @@ -1,6 +1,6 @@ -# Audio Video Mixer +# Audio Video Mixer {:data-level="all"} Register name used to load filter: __avmix__ This is a JavaScript filter. It is not checked during graph resolution and needs explicit loading. diff --git a/docs/Filters/bifsdec.md b/docs/Filters/bifsdec.md index 1ab46e2e..e6c88719 100644 --- a/docs/Filters/bifsdec.md +++ b/docs/Filters/bifsdec.md @@ -1,6 +1,6 @@ -# MPEG-4 BIFS decoder +# MPEG-4 BIFS decoder {:data-level="all"} Register name used to load filter: __bifsdec__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/bsagg.md b/docs/Filters/bsagg.md index 4d041e2d..c6ecae17 100644 --- a/docs/Filters/bsagg.md +++ b/docs/Filters/bsagg.md @@ -1,6 +1,6 @@ -# Compressed layered bitstream aggregator +# Compressed layered bitstream aggregator {:data-level="all"} Register name used to load filter: __bsagg__ This filter is not checked during graph resolution and needs explicit loading. diff --git a/docs/Filters/bsrw.md b/docs/Filters/bsrw.md index d4123115..583526d1 100644 --- a/docs/Filters/bsrw.md +++ b/docs/Filters/bsrw.md @@ -1,6 +1,6 @@ -# Compressed bitstream rewriter +# Compressed bitstream rewriter {:data-level="all"} Register name used to load filter: __bsrw__ This filter is not checked during graph resolution and needs explicit loading. diff --git a/docs/Filters/bssplit.md b/docs/Filters/bssplit.md index 0735456d..f27bd1f2 100644 --- a/docs/Filters/bssplit.md +++ b/docs/Filters/bssplit.md @@ -1,6 +1,6 @@ -# Compressed layered bitstream splitter +# Compressed layered bitstream splitter {:data-level="all"} Register name used to load filter: __bssplit__ This filter is not checked during graph resolution and needs explicit loading. diff --git a/docs/Filters/btplay.md b/docs/Filters/btplay.md index 8f53fc97..92d24014 100644 --- a/docs/Filters/btplay.md +++ b/docs/Filters/btplay.md @@ -1,6 +1,6 @@ -# BT/XMT/X3D loader +# BT/XMT/X3D loader {:data-level="all"} Register name used to load filter: __btplay__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/ccdec.md b/docs/Filters/ccdec.md index 61af34e2..f3d1fa0a 100644 --- a/docs/Filters/ccdec.md +++ b/docs/Filters/ccdec.md @@ -1,6 +1,6 @@ -# Closed-Caption decoder +# Closed-Caption decoder {:data-level="all"} Register name used to load filter: __ccdec__ This filter is not checked during graph resolution and needs explicit loading. diff --git a/docs/Filters/cdcrypt.md b/docs/Filters/cdcrypt.md index 61e81b97..e6eb50db 100644 --- a/docs/Filters/cdcrypt.md +++ b/docs/Filters/cdcrypt.md @@ -1,6 +1,6 @@ -# CENC decryptor +# CENC decryptor {:data-level="all"} Register name used to load filter: __cdcrypt__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/cecrypt.md b/docs/Filters/cecrypt.md index a78fe72b..0a633e97 100644 --- a/docs/Filters/cecrypt.md +++ b/docs/Filters/cecrypt.md @@ -1,6 +1,6 @@ -# CENC encryptor +# CENC encryptor {:data-level="all"} Register name used to load filter: __cecrypt__ This filter is not checked during graph resolution and needs explicit loading. diff --git a/docs/Filters/compositor.md b/docs/Filters/compositor.md index b45c69c9..aeb29227 100644 --- a/docs/Filters/compositor.md +++ b/docs/Filters/compositor.md @@ -1,6 +1,6 @@ -# Compositor +# Compositor {:data-level="all"} Register name used to load filter: __compositor__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/core_config.md b/docs/Filters/core_config.md index 342ee6be..094db8de 100644 --- a/docs/Filters/core_config.md +++ b/docs/Filters/core_config.md @@ -1,6 +1,6 @@ -# Configuration file +# Configuration file {:data-level="all"} GPAC uses a configuration file to modify default options of libgpac and filters. This file is called `GPAC.cfg` and is located: @@ -33,6 +33,7 @@ Example [core] threads=2 ``` + Setting this in the config file is equivalent to using `-threads=2`. The options specified at prompt overrides the value of the config file. @@ -44,6 +45,7 @@ Example [filter@rtpin] interleave=yes ``` + This will force the rtp input filter to always request RTP over RTSP by default. To generate a configuration file with all filters options serialized, use [-wf](gpac_general/#wf). @@ -55,11 +57,13 @@ Example ``` --buffer=100 -i file vout aout ``` + This is equivalent to specifying `vout:buffer=100 aout:buffer=100`. Example ``` --buffer=100 -i file vout aout:buffer=10 ``` + This is equivalent to specifying `vout:buffer=100 aout:buffer=10`. __Warning: This syntax only applies to regular filter options. It cannot be used with builtin shortcuts (gfreg, enc, ...).__ @@ -69,6 +73,7 @@ Example ``` --profile=Baseline -i file.cmp -o dump.264 ``` + This is equivalent to specifying `-o dump.264:profile=Baseline`. For both syntaxes, it is possible to specify the filter registry name of the option, using `--FNAME:OPTNAME=VAL` or `--FNAME@OPTNAME=VAL`. @@ -77,4 +82,5 @@ Example ``` --flist@timescale=100 -i plist1 -i plist2 -o live.mpd ``` + This will set the timescale option on the playlists filters but not on the dasher filter. diff --git a/docs/Filters/core_logs.md b/docs/Filters/core_logs.md index 3d5e9ed0..e1ff0ae4 100644 --- a/docs/Filters/core_logs.md +++ b/docs/Filters/core_logs.md @@ -1,7 +1,7 @@ # GPAC Log System -# libgpac logs options: +# libgpac logs options: {:data-level="all"}
    __-noprog__: disable progress messages __-quiet__: disable all messages, including errors @@ -58,6 +58,7 @@ Example ``` -logs=all@info:dash@debug:ncl ``` + This moves all log to info level, dash to debug level and disable color logs __-proglf__: use new line at each progress messages diff --git a/docs/Filters/core_options.md b/docs/Filters/core_options.md index ebfa074c..10ea9de9 100644 --- a/docs/Filters/core_options.md +++ b/docs/Filters/core_options.md @@ -1,7 +1,7 @@ -# GPAC Core Options +# GPAC Core Options -# libgpac core options: +# libgpac core options: {:data-level="all"} __-noprog__: disable progress messages __-quiet__: disable all messages, including errors @@ -65,19 +65,22 @@ Example ``` -netcap=dst=dump.gpc -``` +``` + This will record packets to dump.gpc Example ``` -netcap=src=dump.gpc,id=NC1 -i session1.sdp:NCID=NC1 -i session2.sdp -``` +``` + This will read packets from dump.gpc only for session1.sdp and let session2.sdp use regular sockets Example ``` -netcap=[p=1234,s=100,n=20][r=200,s=500,o=10,v=FE] -``` +``` + This will use regular network interface and drop packets 100 to 119 on port 1234 and patch one random packet every 200 starting from packet 500, setting byte 10 to FE __-cache__ (string): cache directory location diff --git a/docs/Filters/cryptin.md b/docs/Filters/cryptin.md index df8ef72b..5dda470c 100644 --- a/docs/Filters/cryptin.md +++ b/docs/Filters/cryptin.md @@ -1,6 +1,6 @@ -# CryptFile input +# CryptFile input {:data-level="all"} Register name used to load filter: __cryptin__ This filter is not checked during graph resolution and needs explicit loading. diff --git a/docs/Filters/cryptout.md b/docs/Filters/cryptout.md index 01a22fd2..f6ca07ff 100644 --- a/docs/Filters/cryptout.md +++ b/docs/Filters/cryptout.md @@ -1,6 +1,6 @@ -# CryptFile output +# CryptFile output {:data-level="all"} Register name used to load filter: __cryptout__ This filter is not checked during graph resolution and needs explicit loading. diff --git a/docs/Filters/dasher.md b/docs/Filters/dasher.md index aa60983a..8b354f87 100644 --- a/docs/Filters/dasher.md +++ b/docs/Filters/dasher.md @@ -1,6 +1,6 @@ -# DASH and HLS segmenter +# DASH and HLS segmenter {:data-level="all"} Register name used to load filter: __dasher__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/dashin.md b/docs/Filters/dashin.md index 2d36fc1a..52dc660f 100644 --- a/docs/Filters/dashin.md +++ b/docs/Filters/dashin.md @@ -1,6 +1,6 @@ -# MPEG-DASH and HLS client +# MPEG-DASH and HLS client {:data-level="all"} Register name used to load filter: __dashin__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/dtout.md b/docs/Filters/dtout.md index 69a60051..d73965aa 100644 --- a/docs/Filters/dtout.md +++ b/docs/Filters/dtout.md @@ -1,6 +1,6 @@ -# DekTec SDIOut +# DekTec SDIOut {:data-level="all"} Register name used to load filter: __dtout__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/dvbin.md b/docs/Filters/dvbin.md index 91493a96..5b7468a2 100644 --- a/docs/Filters/dvbin.md +++ b/docs/Filters/dvbin.md @@ -1,6 +1,6 @@ -# DVB for Linux +# DVB for Linux {:data-level="all"} Register name used to load filter: __dvbin__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/evgs.md b/docs/Filters/evgs.md index f0e92171..156ffa0a 100644 --- a/docs/Filters/evgs.md +++ b/docs/Filters/evgs.md @@ -1,6 +1,6 @@ -# EVG video rescaler +# EVG video rescaler {:data-level="all"} Register name used to load filter: __evgs__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/faad.md b/docs/Filters/faad.md index a2be3b59..01271771 100644 --- a/docs/Filters/faad.md +++ b/docs/Filters/faad.md @@ -1,6 +1,6 @@ -# FAAD decoder +# FAAD decoder {:data-level="all"} Register name used to load filter: __faad__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/ffavf.md b/docs/Filters/ffavf.md index db1c3f95..c53656f5 100644 --- a/docs/Filters/ffavf.md +++ b/docs/Filters/ffavf.md @@ -1,6 +1,6 @@ -# FFmpeg AVFilter +# FFmpeg AVFilter {:data-level="all"} Register name used to load filter: __ffavf__ This filter is not checked during graph resolution and needs explicit loading. diff --git a/docs/Filters/ffavin.md b/docs/Filters/ffavin.md index b142c45e..505a9c45 100644 --- a/docs/Filters/ffavin.md +++ b/docs/Filters/ffavin.md @@ -1,6 +1,6 @@ -# FFmpeg AV Capture +# FFmpeg AV Capture {:data-level="all"} Register name used to load filter: __ffavin__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/ffbsf.md b/docs/Filters/ffbsf.md index b15808b1..30e8ac37 100644 --- a/docs/Filters/ffbsf.md +++ b/docs/Filters/ffbsf.md @@ -1,6 +1,6 @@ -# FFmpeg BitStream filter +# FFmpeg BitStream filter {:data-level="all"} Register name used to load filter: __ffbsf__ This filter is not checked during graph resolution and needs explicit loading. diff --git a/docs/Filters/ffdec.md b/docs/Filters/ffdec.md index eb9a0c1e..49632554 100644 --- a/docs/Filters/ffdec.md +++ b/docs/Filters/ffdec.md @@ -1,6 +1,6 @@ -# FFmpeg decoder +# FFmpeg decoder {:data-level="all"} Register name used to load filter: __ffdec__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/ffdmx.md b/docs/Filters/ffdmx.md index fde5938a..a723256c 100644 --- a/docs/Filters/ffdmx.md +++ b/docs/Filters/ffdmx.md @@ -1,6 +1,6 @@ -# FFmpeg demultiplexer +# FFmpeg demultiplexer {:data-level="all"} Register name used to load filter: __ffdmx__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/ffenc.md b/docs/Filters/ffenc.md index f6d68d24..bdb2669b 100644 --- a/docs/Filters/ffenc.md +++ b/docs/Filters/ffenc.md @@ -1,6 +1,6 @@ -# FFmpeg encoder +# FFmpeg encoder {:data-level="all"} Register name used to load filter: __ffenc__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/ffmx.md b/docs/Filters/ffmx.md index d5c78101..4e261fea 100644 --- a/docs/Filters/ffmx.md +++ b/docs/Filters/ffmx.md @@ -1,6 +1,6 @@ -# FFmpeg multiplexer +# FFmpeg multiplexer {:data-level="all"} Register name used to load filter: __ffmx__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/ffsws.md b/docs/Filters/ffsws.md index fb23ec75..b458607d 100644 --- a/docs/Filters/ffsws.md +++ b/docs/Filters/ffsws.md @@ -1,6 +1,6 @@ -# FFmpeg video rescaler +# FFmpeg video rescaler {:data-level="all"} Register name used to load filter: __ffsws__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/filters_general.md b/docs/Filters/filters_general.md index 2ade42b4..9c2dc994 100644 --- a/docs/Filters/filters_general.md +++ b/docs/Filters/filters_general.md @@ -1,6 +1,6 @@ -# Overview +# Overview {:data-level="all"} Filters are configurable processing units consuming and producing data packets. These packets are carried between filters through a data channel called _PID_. A PID is in charge of allocating/tracking data packets, and passing the packets to the destination filter(s). A filter output PID may be connected to zero or more filters. This fan-out is handled internally by GPAC (no such thing as a tee filter in GPAC). _Note: When a PID cannot be connected to any filter, a warning is thrown and all packets dispatched on this PID will be destroyed. The session may however still run, unless [-full-link](core_options/#full-link) is set._ diff --git a/docs/Filters/filters_properties.md b/docs/Filters/filters_properties.md index 82137654..b8fc355e 100644 --- a/docs/Filters/filters_properties.md +++ b/docs/Filters/filters_properties.md @@ -1,6 +1,6 @@ -# GPAC Built-in properties +# GPAC Built-in properties {:data-level="all"} ## Built-in property types diff --git a/docs/Filters/fin.md b/docs/Filters/fin.md index 49590d9f..ed2aa5e6 100644 --- a/docs/Filters/fin.md +++ b/docs/Filters/fin.md @@ -1,6 +1,6 @@ -# File input +# File input {:data-level="all"} Register name used to load filter: __fin__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/flist.md b/docs/Filters/flist.md index 057675b4..c17d9dd2 100644 --- a/docs/Filters/flist.md +++ b/docs/Filters/flist.md @@ -1,6 +1,6 @@ -# Sources concatenator +# Sources concatenator {:data-level="all"} Register name used to load filter: __flist__ This filter may be automatically loaded during graph resolution. @@ -111,13 +111,15 @@ __Warning: There shall be a single character, with value space (' '), before and Example ``` src.mp4 @ reframer:rt=on -``` +``` + This will inject a reframer with real-time regulation between source and `flist` filter. Example ``` src.mp4 @ reframer:saps=1 @1 reframer:saps=0,2,3 src.mp4 @ reframer:saps=1 @-1 reframer:saps=0,2,3 ``` + This will inject a reframer filtering only SAP1 frames and a reframer filtering only non-SAP1 frames between source and `flist` filter Link options can be specified (see `gpac -h doc`). @@ -125,6 +127,7 @@ Example ``` src.mp4 @#video reframer:rt=on ``` + This will inject a reframer with real-time regulation between video PID of source and `flist` filter. When using filter chains, the `flist` filter will only accept PIDs from the last declared filter in the chain. @@ -133,13 +136,15 @@ Example ``` src.mp4 @#video reframer:rt=on @-1#audio ``` + This will inject a reframer with real-time regulation between video PID of source and `flist` filter, and will also allow audio PIDs from source to connect to `flist` filter. The empty link directive can also be used on the last declared filter Example ``` src.mp4 @ reframer:rt=on @#audio -``` +``` + This will inject a reframer with real-time regulation between source and `flist` filter and only connect audio PIDs to `flist` filter. ## Splicing @@ -191,6 +196,7 @@ Example #out=2 in=4 mark sprops=#xlink=http://foo.bar/ src:#Period=main ``` + This will inject property xlink on the output PIDs in the splice zone (corresponding to period `main_2`) but not in the rest of the main media. Directives `mark`, `keep` and `sprops` are reset at the end of the splice period. diff --git a/docs/Filters/fout.md b/docs/Filters/fout.md index efe8f266..006551db 100644 --- a/docs/Filters/fout.md +++ b/docs/Filters/fout.md @@ -1,6 +1,6 @@ -# File output +# File output {:data-level="all"} Register name used to load filter: __fout__ This filter may be automatically loaded during graph resolution. @@ -27,7 +27,8 @@ When recording a DASH or HLS session, the number of segments to keep per quality Example ``` gpac -i LIVE_MPD dashin:forward=file -o rec/$File$:max_cache_segs=3 -``` +``` + This will force keeping a maximum of 3 media segments while recording the DASH session. diff --git a/docs/Filters/ghidmx.md b/docs/Filters/ghidmx.md index ab5f3e46..d7272f3c 100644 --- a/docs/Filters/ghidmx.md +++ b/docs/Filters/ghidmx.md @@ -1,6 +1,6 @@ -# GHI demultiplexer +# GHI demultiplexer {:data-level="all"} Register name used to load filter: __ghidmx__ This filter may be automatically loaded during graph resolution. @@ -16,11 +16,13 @@ Example ``` gpac -i SRC [... -i SRCn] -o index.ghi:segdur=2 ``` + This constructs a binary index for the DASH session. Example ``` gpac -i SRC -o index.ghix:segdur=2 -``` +``` + This constructs an XML index for the DASH session. __Warning: XML indexes should only be used for debug purposes as they can take a significant amount of time to be parsed.__ @@ -33,13 +35,15 @@ The index can be used to generate manifest, child variants for HLS, init segment Example ``` gpac -i index.ghi:gm=all -o dash/vod.mpd -``` +``` + This generates manifest(s) and init segment(s). Example ``` gpac -i index.ghi:rep=FOO:sn=10 -o dash/vod.mpd -``` +``` + This generates the 10th segment of representation with ID `FOO`. _Note: The manifest file(s) and init segment(s) are not written when generating a segment. The manifest target (mpd or m3u8) is only used to setup the filter chain and target output path._ @@ -47,19 +51,22 @@ _Note: The manifest file(s) and init segment(s) are not written when generating Example ``` gpac -i index.ghi:gm=main -o dash/vod.m3u8 -``` +``` + This generates main manifest only (MPD or master HLS playlist). Example ``` gpac -i index.ghi:gm=child:rep=FOO:out=BAR -o dash/vod.m3u8 -``` +``` + This generates child manifest for representation `FOO` in file `BAR`. Example ``` gpac -i index.ghi:gm=init:rep=FOO:out=BAR2 -o dash/vod.m3u8 ``` + This generates init segment for representation `FOO` in file `BAR2`. The filter outputs are PIDs using framed packets marked with segment boundaries and can be chained to other filters before entering the dasher (e.g. for encryption, transcode...). @@ -76,19 +83,22 @@ The filter can be used to generate muxed representations, either at manifest gen Example ``` gpac -i index.ghi:mux=A@V1@V2 -o dash/vod.mpd -``` +``` + This will generate a manifest muxing representations `A` with representations `V1` and `V2`. Example ``` gpac -i index.ghi:mux=A@V1@V2,T@V1@V2 -o dash/vod.mpd -``` +``` + This will generate a manifest muxing representations `A` and `T` with representations `V1` and `V2`. Example ``` gpac -i index.ghi:rep=V2:sn=5:mux=A@V2 -o dash/vod.mpd -``` +``` + This will generate the 5th segment containing representations `A` and `V2`. The filter does not store any state, it is the user responsibility to use consistent information across calls: diff --git a/docs/Filters/glpush.md b/docs/Filters/glpush.md index 2e59203d..dcbcad0e 100644 --- a/docs/Filters/glpush.md +++ b/docs/Filters/glpush.md @@ -1,6 +1,6 @@ -# GPU texture uploader +# GPU texture uploader {:data-level="all"} Register name used to load filter: __glpush__ This is a JavaScript filter. It is not checked during graph resolution and needs explicit loading. diff --git a/docs/Filters/gpac_general.md b/docs/Filters/gpac_general.md index 964e0d6c..a25583d0 100644 --- a/docs/Filters/gpac_general.md +++ b/docs/Filters/gpac_general.md @@ -1,5 +1,5 @@ -# General Usage of gpac +# General Usage of gpac {:data-level="all"} Usage: gpac [options] FILTER [LINK] FILTER [...] gpac is GPAC's command line tool for setting up and running filter chains. @@ -162,7 +162,8 @@ When parsing arguments, the alias will be replace by its value. Example ``` gpac -alias="output aout vout" -``` +``` + This allows later audio and video playback using `gpac -i src.mp4 output` Aliases can use arguments from the command line. The allowed syntaxes are: @@ -188,22 +189,26 @@ Arguments not used by any aliases are kept on the command line, other ones are r Example ``` -alias="foo src=@{N} dst=test.mp4" -``` +``` + The command `gpac foo f1 f2` expands to `gpac src=f2 dst=test.mp4 f1` Example ``` -alias="list: inspect src=@{+:N}" ``` + The command `gpac list f1 f2 f3` expands to `gpac inspect src=f1 src=f2 src=f3` Example ``` -alias="list inspect src=@{+2:N}" ``` + The command `gpac list f1 f2 f3` expands to `gpac inspect src=f2 src=f3 f1` Example ``` -alias="plist aout vout flist:srcs=@{-,N}" ``` + The command `gpac plist f1 f2 f3` expands to `gpac aout vout flist:srcs="f1,f2,f3"` Alias documentation can be set using `gpac -aliasdoc="NAME VALUE"`, with `NAME` the alias name and `VALUE` the documentation. @@ -263,6 +268,7 @@ rg=bar [d1/d2] ru=foo ``` + With this configuration: - the directory `d1` will be readable by all members of group `bar` @@ -288,12 +294,14 @@ The last run can be omitted. Example ``` gpac -dl -np -i SRC reframer -g -rl -g inspect -g -rl -``` +``` + This will load SRC and reframer, print the graph (no connection), relink SRC, print the graph (connection to reframer), insert inspect, print the graph (no connection), relink reframer and run. No play event is sent here. Example ``` gpac -dl -np -i SRC reframer inspect:deep -g -rl=2 -g -rl -se -``` +``` + This will load SRC, reframer and inspect, print the graph (no connection), relink SRC, print the graph (connection to reframer), print the graph (no connection), relink reframer, send play and run. Linking can be done once filters are loaded, using the syntax `@F@SRC` or `@@F@SRC`: @@ -306,7 +314,8 @@ Sources MUST be set before relinking outputs using (-rl)[]. Example ``` gpac -dl -i SRC F1 F2 [...] @1@2 @0@2 -``` +``` + This will set SRC as source to F1 and SRC as source to F2 after loading all filters. The following options are used in defer mode: diff --git a/docs/Filters/gsfdmx.md b/docs/Filters/gsfdmx.md index fa3a6616..88a3408e 100644 --- a/docs/Filters/gsfdmx.md +++ b/docs/Filters/gsfdmx.md @@ -1,6 +1,6 @@ -# GSF demultiplexer +# GSF demultiplexer {:data-level="all"} Register name used to load filter: __gsfdmx__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/gsfmx.md b/docs/Filters/gsfmx.md index 038d4c5c..3a7520c1 100644 --- a/docs/Filters/gsfmx.md +++ b/docs/Filters/gsfmx.md @@ -1,6 +1,6 @@ -# GSF Multiplexer +# GSF Multiplexer {:data-level="all"} Register name used to load filter: __gsfmx__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/hevcmerge.md b/docs/Filters/hevcmerge.md index ed886194..9323549e 100644 --- a/docs/Filters/hevcmerge.md +++ b/docs/Filters/hevcmerge.md @@ -1,6 +1,6 @@ -# HEVC Tile merger +# HEVC Tile merger {:data-level="all"} Register name used to load filter: __hevcmerge__ This filter may be automatically loaded during graph resolution. @@ -44,7 +44,8 @@ This indicates that `src1` contains a video located at 0,0, with a size of 640x4 Example ``` src2:SRD=640x0x640x480:SRDRef=1280x720 -``` +``` + This indicates that `src1` contains a video located at 640,0, with a size of 640x480 pixels in a virtual source of 1280x720 pixels. Each merged input is described by 8 integers in the output `SRDMap`: @@ -64,7 +65,8 @@ Alternatively to using `SRD` and `SRDRef`, it is possible to specify `CropOrigin Example ``` src1:CropOrigin=0x0 src1:CropOrigin=640x0 -``` +``` + Assuming the two sources are encoded at 320x240 and merged as src1 above src2, the output will be a 320x480 video with a `SRDMap` of `{0,0,320,240,0,0,320,240,640,0,320,240,0,240,320,240}` diff --git a/docs/Filters/hevcsplit.md b/docs/Filters/hevcsplit.md index b494b5fc..62e84792 100644 --- a/docs/Filters/hevcsplit.md +++ b/docs/Filters/hevcsplit.md @@ -1,6 +1,6 @@ -# HEVC tile splitter +# HEVC tile splitter {:data-level="all"} Register name used to load filter: __hevcsplit__ This filter is not checked during graph resolution and needs explicit loading. diff --git a/docs/Filters/httpin.md b/docs/Filters/httpin.md index d28ae49a..5e44cb52 100644 --- a/docs/Filters/httpin.md +++ b/docs/Filters/httpin.md @@ -1,6 +1,6 @@ -# HTTP input +# HTTP input {:data-level="all"} Register name used to load filter: __httpin__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/httpout.md b/docs/Filters/httpout.md index 878a9a61..05825afc 100644 --- a/docs/Filters/httpout.md +++ b/docs/Filters/httpout.md @@ -1,6 +1,6 @@ -# HTTP Server +# HTTP Server {:data-level="all"} Register name used to load filter: __httpout__ This filter may be automatically loaded during graph resolution. @@ -39,7 +39,8 @@ Example ``` [foodir] name=bar -``` +``` + Content `RES` of this directory is exposed as `http://SERVER/bar/RES`. Listing can be enabled on server using [dlist](#dlist). @@ -59,18 +60,21 @@ Example ``` gpac httpout:rdirs=outcoming ``` + This sets up a read-only server. Example ``` gpac httpout:wdir=incoming -``` +``` + This sets up a write-only server. Example ``` gpac httpout:rdirs=outcoming:wdir=incoming:port=8080 -``` +``` + This sets up a read-write server running on [port](#port) 8080. @@ -82,13 +86,15 @@ This mode is mostly useful to setup live HTTP streaming of media sessions such a Example ``` gpac -i MP3_SOURCE -o http://localhost/live.mp3 --hold -``` +``` + In this example, the server waits for client requests on `/live.mp3` and will then push each input packet to all connected clients. If the source is not real-time, you can inject a reframer filter performing realtime regulation. Example ``` gpac -i MP3_SOURCE reframer:rt=on -o http://localhost/live.mp3 -``` +``` + In this example, the server will push each input packet to all connected clients, or trash the packet if no connected clients. In this mode, ICECast meta-data can be inserted using [ice](#ice). The default inserted values are `ice-audio-info`, `icy-br`, `icy-pub` (set to 1) and `icy-name` if input `ServiceName` property is set. @@ -96,7 +102,8 @@ The server will also look for any property called `ice-*` on the input PID and i Example ``` gpac -i source.mp3:#ice-Genre=CoolRock -o http://IP/live.mp3 --ice -``` +``` + This will inject the header `ice-Genre: CoolRock` in the response. Once one complete input file is sent, it is no longer available for download unless [reopen](#reopen) is set and input PID is not over. @@ -121,7 +128,8 @@ This mode is typically used for origin server in HAS sessions where clients may Example ``` gpac -i SOURCE reframer:rt=on -o http://localhost:8080/live.mpd --rdirs=temp --dmode=dynamic --cdur=0.1 -``` +``` + In this example, a real-time dynamic DASH session with chunks of 100ms is created, writing files to `temp`. A client connecting to the live edge will receive segments as they are produced using HTTP chunk transfer. The server can store incoming files to memory mode by setting the read directory to `gmem`. @@ -144,7 +152,8 @@ The filter uses no read or write directories in this mode. Example ``` gpac -i SOURCE -o http://targethost:8080/live.mpd:gpac:hmode=push -``` +``` + In this example, the filter will send PUT methods to the server running on [port](#port) 8080 at `targethost` location (IP address or name). @@ -156,7 +165,8 @@ The filter uses no read or write directories in this mode, and uploaded data is Example ``` gpac httpout:hmode=source vout aout -``` +``` + In this example, the filter will try to play uploaded files through video and audio output. @@ -179,6 +189,7 @@ Example ``` gpac -i dash.mpd dashin:forward=file:FID=D1 dashin:forward=segb:FID=D2 -o http://localhost:80/live.mpd:SID=D1:rdirs=dash -o http://localhost:80/live_rw.mpd:SID=D2:sigfrag ``` + This will: - load the HTTP server and forward (through `D1`) the dash session to this server using `live.mpd` as manifest name diff --git a/docs/Filters/imgdec.md b/docs/Filters/imgdec.md index ff117c49..ef9c91fe 100644 --- a/docs/Filters/imgdec.md +++ b/docs/Filters/imgdec.md @@ -1,6 +1,6 @@ -# PNG/JPG decoder +# PNG/JPG decoder {:data-level="all"} Register name used to load filter: __imgdec__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/inspect.md b/docs/Filters/inspect.md index 020e6f7a..5394ab87 100644 --- a/docs/Filters/inspect.md +++ b/docs/Filters/inspect.md @@ -1,6 +1,6 @@ -# Inspect packets +# Inspect packets {:data-level="all"} Register name used to load filter: __inspect__ This filter is not checked during graph resolution and needs explicit loading. @@ -68,7 +68,8 @@ When the option is not present, all properties are dumped. Otherwise, only prope Example ``` fmt="PID $pid.ID$ packet $pn$ DTS $dts$ CTS $cts$ $lf$" -``` +``` + This dumps packet number, cts and dts as follows: `PID 1 packet 10 DTS 100 CTS 108 \n` An unrecognized keyword or missing property will resolve to an empty string. @@ -85,6 +86,7 @@ Example ``` gpac -i SRC reframer:rt=on inspect:buffer=10000:rbuffer=1000:mbuffer=30000:speed=2 ``` + This will play the session at 2x speed, using 30s of maximum buffering, consuming packets after 10s of media are ready and rebuffering if less than 1s of media. diff --git a/docs/Filters/j2kdec.md b/docs/Filters/j2kdec.md index cfba259c..794d820e 100644 --- a/docs/Filters/j2kdec.md +++ b/docs/Filters/j2kdec.md @@ -1,6 +1,6 @@ -# OpenJPEG2000 decoder +# OpenJPEG2000 decoder {:data-level="all"} Register name used to load filter: __j2kdec__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/jpgenc.md b/docs/Filters/jpgenc.md index 9e9c3c6d..2143f58b 100644 --- a/docs/Filters/jpgenc.md +++ b/docs/Filters/jpgenc.md @@ -1,6 +1,6 @@ -# JPG encoder +# JPG encoder {:data-level="all"} Register name used to load filter: __jpgenc__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/jsf.md b/docs/Filters/jsf.md index 3802832f..7ce75d63 100644 --- a/docs/Filters/jsf.md +++ b/docs/Filters/jsf.md @@ -1,6 +1,6 @@ -# JavaScript filter +# JavaScript filter {:data-level="all"} Register name used to load filter: __jsf__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/lsrdec.md b/docs/Filters/lsrdec.md index b1003020..fd57a89f 100644 --- a/docs/Filters/lsrdec.md +++ b/docs/Filters/lsrdec.md @@ -1,6 +1,6 @@ -# MPEG-4 LASeR decoder +# MPEG-4 LASeR decoder {:data-level="all"} Register name used to load filter: __lsrdec__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/m2psdmx.md b/docs/Filters/m2psdmx.md index 5df6830e..9b64b735 100644 --- a/docs/Filters/m2psdmx.md +++ b/docs/Filters/m2psdmx.md @@ -1,6 +1,6 @@ -# MPEG PS demultiplexer +# MPEG PS demultiplexer {:data-level="all"} Register name used to load filter: __m2psdmx__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/m2tsdmx.md b/docs/Filters/m2tsdmx.md index 21cffbf5..f9d91a6e 100644 --- a/docs/Filters/m2tsdmx.md +++ b/docs/Filters/m2tsdmx.md @@ -1,6 +1,6 @@ -# MPEG-2 TS demultiplexer +# MPEG-2 TS demultiplexer {:data-level="all"} Register name used to load filter: __m2tsdmx__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/m2tsmx.md b/docs/Filters/m2tsmx.md index 1ac905f5..f06de81b 100644 --- a/docs/Filters/m2tsmx.md +++ b/docs/Filters/m2tsmx.md @@ -1,6 +1,6 @@ -# MPEG-2 TS multiplexer +# MPEG-2 TS multiplexer {:data-level="all"} Register name used to load filter: __m2tsmx__ This filter may be automatically loaded during graph resolution. @@ -52,27 +52,32 @@ Each TEMI description is formatted as ID_OR_URL or #OPT1[#OPT2]#ID_OR_URL. Optio Example ``` temi="url" -``` +``` + Inserts a TEMI URL+timecode in the each stream of each program. Example ``` temi="#P0#url,#P1#4" ``` + Inserts a TEMI URL+timecode in the first stream of all programs and an external TEMI with ID 4 in the second stream of all programs. Example ``` temi="#P0#2,#P0#url,#P1#4" -``` +``` + Inserts a TEMI with ID 2 and a TEMI URL+timecode in the first stream of all programs, and an external TEMI with ID 4 in the second stream of all programs. Example ``` temi="#S20#4,#S10#URL" -``` +``` + Inserts an external TEMI with ID 4 in the each stream of program with ServiceID 20 and a TEMI URL in each stream of program with ServiceID 10. Example ``` temi="#N#D500#PV#T30000#4" -``` +``` + Inserts an external TEMI with ID 4 and timescale 30000, NTP injection and carousel of 500 ms in the video stream of all programs. __Warning: multipliers (k,m,g) are not supported in TEMI options.__ diff --git a/docs/Filters/maddec.md b/docs/Filters/maddec.md index 65da8b7b..03a656c2 100644 --- a/docs/Filters/maddec.md +++ b/docs/Filters/maddec.md @@ -1,6 +1,6 @@ -# MAD decoder +# MAD decoder {:data-level="all"} Register name used to load filter: __maddec__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/mcdec.md b/docs/Filters/mcdec.md index bfd409e5..8ed51dd0 100644 --- a/docs/Filters/mcdec.md +++ b/docs/Filters/mcdec.md @@ -1,6 +1,6 @@ -# MediaCodec decoder +# MediaCodec decoder {:data-level="all"} Register name used to load filter: __mcdec__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/mp4dmx.md b/docs/Filters/mp4dmx.md index 3ef146c2..667c71e9 100644 --- a/docs/Filters/mp4dmx.md +++ b/docs/Filters/mp4dmx.md @@ -1,6 +1,6 @@ -# ISOBMFF/QT demultiplexer +# ISOBMFF/QT demultiplexer {:data-level="all"} Register name used to load filter: __mp4dmx__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/mp4mx.md b/docs/Filters/mp4mx.md index 6c4be772..fa25ec0e 100644 --- a/docs/Filters/mp4mx.md +++ b/docs/Filters/mp4mx.md @@ -1,6 +1,6 @@ -# ISOBMFF/QT multiplexer +# ISOBMFF/QT multiplexer {:data-level="all"} Register name used to load filter: __mp4mx__ This filter may be automatically loaded during graph resolution. @@ -45,7 +45,8 @@ Per PID box patch can be specified through the PID property `boxpatch`. Example ``` gpac -i source:#boxpatch=myfile.xml -o mux.mp4 -``` +``` + Per Item box patch can be specified through the PID property `boxpatch`. Example ``` @@ -113,6 +114,7 @@ Example ``` -i unkn.mkv:#ISOMSubtype=VIUK:#DSIWrap=cfgv -o t.mp4 ``` + This will wrap the unknown stream using `VIUK` code point in `stsd` and wrap any decoder configuration data in a `cfgv` box. If [pad_sparse](#pad_sparse) is set, the filter watches the property `Sparse` on incoming PID to decide whether empty packets should be injected to keep packet duration info. @@ -128,12 +130,14 @@ Example ``` -i src.srt:#StreamSubtype=sbtl [-i ...] -o test.mp4 ``` + This will force the text stream to use `sbtl` handler type instead of default `text` one. Subtitle streams may be used as chapters by setting the property `IsChap` on the desired PID. Example ``` -i src.srt:#IsChap [-i ...] -o test.mp4 ``` + This will force the text stream to be used as a QT chapter track. diff --git a/docs/Filters/mpeghdec.md b/docs/Filters/mpeghdec.md index a1b2f271..df6f580b 100644 --- a/docs/Filters/mpeghdec.md +++ b/docs/Filters/mpeghdec.md @@ -1,6 +1,6 @@ -# MPEG-H Audio decoder +# MPEG-H Audio decoder {:data-level="all"} Register name used to load filter: __mpeghdec__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/nhmlr.md b/docs/Filters/nhmlr.md index 35daf657..c33d166e 100644 --- a/docs/Filters/nhmlr.md +++ b/docs/Filters/nhmlr.md @@ -1,6 +1,6 @@ -# NHML reader +# NHML reader {:data-level="all"} Register name used to load filter: __nhmlr__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/nhmlw.md b/docs/Filters/nhmlw.md index a96ad15d..34895651 100644 --- a/docs/Filters/nhmlw.md +++ b/docs/Filters/nhmlw.md @@ -1,6 +1,6 @@ -# NHML writer +# NHML writer {:data-level="all"} Register name used to load filter: __nhmlw__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/nhntr.md b/docs/Filters/nhntr.md index 902258b5..25fc2eb4 100644 --- a/docs/Filters/nhntr.md +++ b/docs/Filters/nhntr.md @@ -1,6 +1,6 @@ -# NHNT reader +# NHNT reader {:data-level="all"} Register name used to load filter: __nhntr__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/nhntw.md b/docs/Filters/nhntw.md index dcc9c220..23503a96 100644 --- a/docs/Filters/nhntw.md +++ b/docs/Filters/nhntw.md @@ -1,6 +1,6 @@ -# NHNT writer +# NHNT writer {:data-level="all"} Register name used to load filter: __nhntw__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/nvdec.md b/docs/Filters/nvdec.md index 156d9eb1..e140a63e 100644 --- a/docs/Filters/nvdec.md +++ b/docs/Filters/nvdec.md @@ -1,6 +1,6 @@ -# NVidia decoder +# NVidia decoder {:data-level="all"} Register name used to load filter: __nvdec__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/odfdec.md b/docs/Filters/odfdec.md index d6072528..03d24248 100644 --- a/docs/Filters/odfdec.md +++ b/docs/Filters/odfdec.md @@ -1,6 +1,6 @@ -# MPEG-4 OD decoder +# MPEG-4 OD decoder {:data-level="all"} Register name used to load filter: __odfdec__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/oggdmx.md b/docs/Filters/oggdmx.md index 092e2621..cede61d9 100644 --- a/docs/Filters/oggdmx.md +++ b/docs/Filters/oggdmx.md @@ -1,6 +1,6 @@ -# OGG demultiplexer +# OGG demultiplexer {:data-level="all"} Register name used to load filter: __oggdmx__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/oggmx.md b/docs/Filters/oggmx.md index b547ee5a..ae0ece91 100644 --- a/docs/Filters/oggmx.md +++ b/docs/Filters/oggmx.md @@ -1,6 +1,6 @@ -# OGG multiplexer +# OGG multiplexer {:data-level="all"} Register name used to load filter: __oggmx__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/ohevcdec.md b/docs/Filters/ohevcdec.md index e941a773..85da0a28 100644 --- a/docs/Filters/ohevcdec.md +++ b/docs/Filters/ohevcdec.md @@ -1,6 +1,6 @@ -# OpenHEVC decoder +# OpenHEVC decoder {:data-level="all"} Register name used to load filter: __ohevcdec__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/osvcdec.md b/docs/Filters/osvcdec.md index 6cffa2f3..c7b26e2c 100644 --- a/docs/Filters/osvcdec.md +++ b/docs/Filters/osvcdec.md @@ -1,6 +1,6 @@ -# OpenSVC decoder +# OpenSVC decoder {:data-level="all"} Register name used to load filter: __osvcdec__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/pin.md b/docs/Filters/pin.md index b108c9d8..9f58ebc5 100644 --- a/docs/Filters/pin.md +++ b/docs/Filters/pin.md @@ -1,6 +1,6 @@ -# pipe input +# pipe input {:data-level="all"} Register name used to load filter: __pin__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/pngenc.md b/docs/Filters/pngenc.md index 58151bf3..c7396f23 100644 --- a/docs/Filters/pngenc.md +++ b/docs/Filters/pngenc.md @@ -1,6 +1,6 @@ -# PNG encoder +# PNG encoder {:data-level="all"} Register name used to load filter: __pngenc__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/pout.md b/docs/Filters/pout.md index aace8f97..e318a4ea 100644 --- a/docs/Filters/pout.md +++ b/docs/Filters/pout.md @@ -1,6 +1,6 @@ -# pipe output +# pipe output {:data-level="all"} Register name used to load filter: __pout__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/probe.md b/docs/Filters/probe.md index 91e28a96..463fe9cf 100644 --- a/docs/Filters/probe.md +++ b/docs/Filters/probe.md @@ -1,6 +1,6 @@ -# Probe source +# Probe source {:data-level="all"} Register name used to load filter: __probe__ This filter is not checked during graph resolution and needs explicit loading. diff --git a/docs/Filters/reframer.md b/docs/Filters/reframer.md index 82cc0a8d..4aac59df 100644 --- a/docs/Filters/reframer.md +++ b/docs/Filters/reframer.md @@ -1,6 +1,6 @@ -# Media Reframer +# Media Reframer {:data-level="all"} Register name used to load filter: __reframer__ This filter is not checked during graph resolution and needs explicit loading. @@ -69,6 +69,7 @@ Example ``` gpac -i m.mp4 reframer:xs=T00:00:10,T00:01:10,T00:02:00:xe=T00:00:20,T00:01:20 [dst] ``` + This will extract the time ranges [10s,20s], [1m10s,1m20s] and all media starting from 2m If no end range is found for a given start range: @@ -80,12 +81,14 @@ If no end range is found for a given start range: Example ``` gpac -i m.mp4 reframer:xs=0,10,25:xe=5,20 [dst] -``` +``` + This will extract the time ranges [0s,5s], [10s,20s] and all media starting from 25s Example ``` gpac -i m.mp4 reframer:xs=0,10,25 [dst] -``` +``` + This will extract the time ranges [0s,10s], [10s,25s] and all media starting from 25s It is possible to signal range boundaries in output packets using [splitrange](#splitrange). @@ -99,6 +102,7 @@ Example ``` gpac -i m.mp4 reframer:xs=T00:00:10,T00:01:10:xe=T00:00:20:splitrange -o dump_$FS$.264 [dst] ``` + This will create two output files dump_T00.00.10_T00.02.00.264 and dump_T00.01.10.264. _Note: The `:` and `/` characters are replaced by `.` in `FileSuffix` property._ @@ -110,6 +114,7 @@ Example ``` gpac -i m.mp4 reframer:xs=0,30::props=#Period=P1,#Period=P2:#foo=bar [dst] ``` + This will assign to output PIDs - during the range [0,30]: property `Period` to `P1` diff --git a/docs/Filters/resample.md b/docs/Filters/resample.md index ca1d2dfe..e5e3210a 100644 --- a/docs/Filters/resample.md +++ b/docs/Filters/resample.md @@ -1,6 +1,6 @@ -# Audio resampler +# Audio resampler {:data-level="all"} Register name used to load filter: __resample__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/restamp.md b/docs/Filters/restamp.md index 4f93b193..46ed0456 100644 --- a/docs/Filters/restamp.md +++ b/docs/Filters/restamp.md @@ -1,6 +1,6 @@ -# Packet timestamp rewriter +# Packet timestamp rewriter {:data-level="all"} Register name used to load filter: __restamp__ This filter is not checked during graph resolution and needs explicit loading. diff --git a/docs/Filters/rewind.md b/docs/Filters/rewind.md index 7ec1b63a..73f6ff9f 100644 --- a/docs/Filters/rewind.md +++ b/docs/Filters/rewind.md @@ -1,6 +1,6 @@ -# Audio/Video rewinder +# Audio/Video rewinder {:data-level="all"} Register name used to load filter: __rewind__ This filter is not checked during graph resolution and needs explicit loading. diff --git a/docs/Filters/rfac3.md b/docs/Filters/rfac3.md index 3ff4332a..5a25aa95 100644 --- a/docs/Filters/rfac3.md +++ b/docs/Filters/rfac3.md @@ -1,6 +1,6 @@ -# AC3 reframer +# AC3 reframer {:data-level="all"} Register name used to load filter: __rfac3__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/rfadts.md b/docs/Filters/rfadts.md index b3a8ebbc..be969fd6 100644 --- a/docs/Filters/rfadts.md +++ b/docs/Filters/rfadts.md @@ -1,6 +1,6 @@ -# ADTS reframer +# ADTS reframer {:data-level="all"} Register name used to load filter: __rfadts__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/rfamr.md b/docs/Filters/rfamr.md index 4bb6622e..607cf107 100644 --- a/docs/Filters/rfamr.md +++ b/docs/Filters/rfamr.md @@ -1,6 +1,6 @@ -# AMR/EVRC reframer +# AMR/EVRC reframer {:data-level="all"} Register name used to load filter: __rfamr__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/rfav1.md b/docs/Filters/rfav1.md index 06f35595..8d2b7fc9 100644 --- a/docs/Filters/rfav1.md +++ b/docs/Filters/rfav1.md @@ -1,6 +1,6 @@ -# AV1/IVF/VP9 reframer +# AV1/IVF/VP9 reframer {:data-level="all"} Register name used to load filter: __rfav1__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/rfflac.md b/docs/Filters/rfflac.md index 93e2d465..1f167471 100644 --- a/docs/Filters/rfflac.md +++ b/docs/Filters/rfflac.md @@ -1,6 +1,6 @@ -# FLAC reframer +# FLAC reframer {:data-level="all"} Register name used to load filter: __rfflac__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/rfh263.md b/docs/Filters/rfh263.md index 090bfad4..68a72dc6 100644 --- a/docs/Filters/rfh263.md +++ b/docs/Filters/rfh263.md @@ -1,6 +1,6 @@ -# H263 reframer +# H263 reframer {:data-level="all"} Register name used to load filter: __rfh263__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/rfimg.md b/docs/Filters/rfimg.md index 80dfc171..7c602601 100644 --- a/docs/Filters/rfimg.md +++ b/docs/Filters/rfimg.md @@ -1,6 +1,6 @@ -# JPG/J2K/PNG/BMP reframer +# JPG/J2K/PNG/BMP reframer {:data-level="all"} Register name used to load filter: __rfimg__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/rflatm.md b/docs/Filters/rflatm.md index fa59a0a0..a3ac8754 100644 --- a/docs/Filters/rflatm.md +++ b/docs/Filters/rflatm.md @@ -1,6 +1,6 @@ -# LATM reframer +# LATM reframer {:data-level="all"} Register name used to load filter: __rflatm__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/rfmhas.md b/docs/Filters/rfmhas.md index 3194593f..cf49afc2 100644 --- a/docs/Filters/rfmhas.md +++ b/docs/Filters/rfmhas.md @@ -1,6 +1,6 @@ -# MPEH-H Audio Stream reframer +# MPEH-H Audio Stream reframer {:data-level="all"} Register name used to load filter: __rfmhas__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/rfmp3.md b/docs/Filters/rfmp3.md index a8455510..8f21fc25 100644 --- a/docs/Filters/rfmp3.md +++ b/docs/Filters/rfmp3.md @@ -1,6 +1,6 @@ -# MP3 reframer +# MP3 reframer {:data-level="all"} Register name used to load filter: __rfmp3__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/rfmpgvid.md b/docs/Filters/rfmpgvid.md index 72aff953..7359d26b 100644 --- a/docs/Filters/rfmpgvid.md +++ b/docs/Filters/rfmpgvid.md @@ -1,6 +1,6 @@ -# M1V/M2V/M4V reframer +# M1V/M2V/M4V reframer {:data-level="all"} Register name used to load filter: __rfmpgvid__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/rfnalu.md b/docs/Filters/rfnalu.md index db9d43c9..4febe4c8 100644 --- a/docs/Filters/rfnalu.md +++ b/docs/Filters/rfnalu.md @@ -1,6 +1,6 @@ -# AVC/HEVC reframer +# AVC/HEVC reframer {:data-level="all"} Register name used to load filter: __rfnalu__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/rfpcm.md b/docs/Filters/rfpcm.md index 7d5d3b98..d8fe952b 100644 --- a/docs/Filters/rfpcm.md +++ b/docs/Filters/rfpcm.md @@ -1,6 +1,6 @@ -# PCM reframer +# PCM reframer {:data-level="all"} Register name used to load filter: __rfpcm__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/rfprores.md b/docs/Filters/rfprores.md index c0d985e2..c03e8366 100644 --- a/docs/Filters/rfprores.md +++ b/docs/Filters/rfprores.md @@ -1,6 +1,6 @@ -# ProRes reframer +# ProRes reframer {:data-level="all"} Register name used to load filter: __rfprores__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/rfqcp.md b/docs/Filters/rfqcp.md index 5da4e3f1..a36e713e 100644 --- a/docs/Filters/rfqcp.md +++ b/docs/Filters/rfqcp.md @@ -1,6 +1,6 @@ -# QCP reframer +# QCP reframer {:data-level="all"} Register name used to load filter: __rfqcp__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/rfrawvid.md b/docs/Filters/rfrawvid.md index b57c21d9..a74d8687 100644 --- a/docs/Filters/rfrawvid.md +++ b/docs/Filters/rfrawvid.md @@ -1,6 +1,6 @@ -# RAW video reframer +# RAW video reframer {:data-level="all"} Register name used to load filter: __rfrawvid__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/rfsrt.md b/docs/Filters/rfsrt.md index e486f993..b79d8eee 100644 --- a/docs/Filters/rfsrt.md +++ b/docs/Filters/rfsrt.md @@ -1,6 +1,6 @@ -# SRT reframer +# SRT reframer {:data-level="all"} Register name used to load filter: __rfsrt__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/rftruehd.md b/docs/Filters/rftruehd.md index a25072ca..a9f1f2d3 100644 --- a/docs/Filters/rftruehd.md +++ b/docs/Filters/rftruehd.md @@ -1,6 +1,6 @@ -# TrueHD reframer +# TrueHD reframer {:data-level="all"} Register name used to load filter: __rftruehd__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/routein.md b/docs/Filters/routein.md index 6d2b9c17..8c395b94 100644 --- a/docs/Filters/routein.md +++ b/docs/Filters/routein.md @@ -1,6 +1,6 @@ -# ROUTE input +# ROUTE input {:data-level="all"} Register name used to load filter: __routein__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/routeout.md b/docs/Filters/routeout.md index 57bb7871..917f00b1 100644 --- a/docs/Filters/routeout.md +++ b/docs/Filters/routeout.md @@ -1,6 +1,6 @@ -# ROUTE output +# ROUTE output {:data-level="all"} Register name used to load filter: __routeout__ This filter may be automatically loaded during graph resolution. @@ -40,6 +40,7 @@ Example ``` "atsc://:ext=mpd", "route://IP:PORT/manifest.mpd" ``` + If multiple services with different formats are needed, you will need to explicit your filters: Example ``` @@ -115,17 +116,20 @@ Multiplexing an existing DASH session in route: Example ``` gpac -i source.mpd dashin:forward=file -o route://225.1.1.0:6000/ -``` +``` + Multiplexing an existing DASH session in atsc: Example ``` gpac -i source.mpd dashin:forward=file -o atsc:// ``` + Dashing and multiplexing in route: Example ``` gpac -i source.mp4 dasher:profile=live -o route://225.1.1.0:6000/manifest.mpd -``` +``` + Dashing and multiplexing in route Low Latency: Example ``` @@ -143,17 +147,20 @@ Example ``` gpac -i source.mpd -o route://225.1.1.0:6000/ ``` + This will only send the manifest file as a regular object and will not load the dash session. Example ``` gpac -i source.mpd dashin:forward=file -o route://225.1.1.0:6000/manifest.mpd ``` + This will force the ROUTE multiplexer to only accept .mpd files, and will drop all segment files (same if [ext](#ext) is used). Example ``` gpac -i source.mpd dasher -o route://225.1.1.0:6000/ gpac -i source.mpd dasher -o route://225.1.1.0:6000/manifest.mpd ``` + These will demultiplex the input, re-dash it and send the output of the dasher to ROUTE # Error simulation @@ -163,6 +170,7 @@ Example ``` gpac -i source.mpd dasher -o route://225.1.1.0:6000/:errsim=1.0x98.0 ``` + for a 1.0 percent chance to transition to error (not sending data over the network) and 98.0 to transition from error back to OK. diff --git a/docs/Filters/rtpin.md b/docs/Filters/rtpin.md index 78ba81a4..6cdb4eef 100644 --- a/docs/Filters/rtpin.md +++ b/docs/Filters/rtpin.md @@ -1,6 +1,6 @@ -# RTP/RTSP/SDP input +# RTP/RTSP/SDP input {:data-level="all"} Register name used to load filter: __rtpin__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/rtpout.md b/docs/Filters/rtpout.md index 53279f02..4facd8ff 100644 --- a/docs/Filters/rtpout.md +++ b/docs/Filters/rtpout.md @@ -1,6 +1,6 @@ -# RTP Streamer +# RTP Streamer {:data-level="all"} Register name used to load filter: __rtpout__ This filter may be automatically loaded during graph resolution. @@ -19,6 +19,7 @@ Example ``` gpac -i src -o rtp://localhost:1234/:ext=ts ``` + This will indicate that the RTP streamer expects a MPEG-2 TS mux as an input. # RTP Packets diff --git a/docs/Filters/rtspout.md b/docs/Filters/rtspout.md index 84cc7fe3..3259390d 100644 --- a/docs/Filters/rtspout.md +++ b/docs/Filters/rtspout.md @@ -1,6 +1,6 @@ -# RTSP Server +# RTSP Server {:data-level="all"} Register name used to load filter: __rtspout__ This filter may be automatically loaded during graph resolution. @@ -24,6 +24,7 @@ Example gpac -i source -o rtsp://myip/sessionname gpac -i source -o rtsp://myip/sessionname ``` + In this mode, only one session is possible. It is possible to [loop](#loop) the input source(s). # Server mode @@ -32,7 +33,8 @@ The filter can work as a regular RTSP server by specifying the [mounts](#mounts) Example ``` gpac rtspout:mounts=mydir1,mydir2 -``` +``` + In this case, content `RES` from any of the specified directory is exposed as `rtsp://SERVER/RES` The [mounts](#mounts) option can also specify access rule file(s), see `gpac -h creds`. When rules are used: @@ -46,6 +48,7 @@ Example [foodir] name=bar ``` + Content `RES` of this directory is exposed as `rtsp://SERVER/bar/RES`. @@ -56,6 +59,7 @@ Example ``` gpac -i rtsp://localhost/?pipe://mynamepipe&myfile.mp4 [dst filters] ``` + The server will resolve this URL in a new session containing streams from `myfile.mp4` and streams from pipe `mynamepipe`. When setting [runfor](#runfor) in server mode, the server will exit at the end of the last session being closed. diff --git a/docs/Filters/safdmx.md b/docs/Filters/safdmx.md index 0c44ef6f..5a337b2a 100644 --- a/docs/Filters/safdmx.md +++ b/docs/Filters/safdmx.md @@ -1,6 +1,6 @@ -# SAF demultiplexer +# SAF demultiplexer {:data-level="all"} Register name used to load filter: __safdmx__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/scte35dec.md b/docs/Filters/scte35dec.md index 7b9103c2..abc3b4ba 100644 --- a/docs/Filters/scte35dec.md +++ b/docs/Filters/scte35dec.md @@ -1,6 +1,6 @@ -# SCTE35 decoder +# SCTE35 decoder {:data-level="all"} Register name used to load filter: __scte35dec__ This filter is not checked during graph resolution and needs explicit loading. diff --git a/docs/Filters/sockin.md b/docs/Filters/sockin.md index 98b13f04..701444a8 100644 --- a/docs/Filters/sockin.md +++ b/docs/Filters/sockin.md @@ -1,6 +1,6 @@ -# UDP/TCP input +# UDP/TCP input {:data-level="all"} Register name used to load filter: __sockin__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/sockout.md b/docs/Filters/sockout.md index 38e3af5b..a31356de 100644 --- a/docs/Filters/sockout.md +++ b/docs/Filters/sockout.md @@ -1,6 +1,6 @@ -# UDP/TCP output +# UDP/TCP output {:data-level="all"} Register name used to load filter: __sockout__ This filter may be automatically loaded during graph resolution. @@ -30,12 +30,14 @@ If the numerator is 0, a packet is randomly chosen in that window. Example ``` :pckd=4/10 -``` +``` + This drops every 4th packet of each 10 packet window. Example ``` :pckr=0/100 ``` + This reverts the send order of one random packet in each 100 packet window. diff --git a/docs/Filters/svgplay.md b/docs/Filters/svgplay.md index 261b50aa..b5c5098b 100644 --- a/docs/Filters/svgplay.md +++ b/docs/Filters/svgplay.md @@ -1,6 +1,6 @@ -# SVG loader +# SVG loader {:data-level="all"} Register name used to load filter: __svgplay__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/theoradec.md b/docs/Filters/theoradec.md index 64fa9f2f..de053436 100644 --- a/docs/Filters/theoradec.md +++ b/docs/Filters/theoradec.md @@ -1,6 +1,6 @@ -# Theora decoder +# Theora decoder {:data-level="all"} Register name used to load filter: __theoradec__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/thumbs.md b/docs/Filters/thumbs.md index 10c2f0e1..46894f30 100644 --- a/docs/Filters/thumbs.md +++ b/docs/Filters/thumbs.md @@ -1,6 +1,6 @@ -# Thumbnail collection generator +# Thumbnail collection generator {:data-level="all"} Register name used to load filter: __thumbs__ This is a JavaScript filter. It is not checked during graph resolution and needs explicit loading. @@ -37,13 +37,15 @@ A single line of text can be inserted over each frame. Predefined keywords can b Example ``` gpac -i src reframer:saps=1 thumbs:snap=30:grid=6x30 -o dump/$num$.png -``` +``` + This will generate images from key-frames only, inserting one image every 30 seconds. Using key-frame filtering is much faster but may give unexpected results if there are not enough key-frames in the source. Example ``` gpac -i src thumbs:snap=0:grid=5x5 -o dump/$num$.png ``` + This will generate one image containing 25 frames every second at 25 fps. If a single image per output frame is used and the scaling factor is 1, the input packet is reused as input with text and graphics overlaid. @@ -52,6 +54,7 @@ Example ``` gpac -i src thumbs:grid=1x1:txt='Frame $time$' -o dump/$num$.png ``` + This will inject text over each frame and keep timing and other packet properties. A json output can be specified in input [list](#list) to let applications retrieve frame position in output image from its timing. diff --git a/docs/Filters/tileagg.md b/docs/Filters/tileagg.md index 9f2cb7dc..1d8c07ed 100644 --- a/docs/Filters/tileagg.md +++ b/docs/Filters/tileagg.md @@ -1,6 +1,6 @@ -# HEVC tile aggregator +# HEVC tile aggregator {:data-level="all"} Register name used to load filter: __tileagg__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/tilesplit.md b/docs/Filters/tilesplit.md index 96131460..ef4e934c 100644 --- a/docs/Filters/tilesplit.md +++ b/docs/Filters/tilesplit.md @@ -1,6 +1,6 @@ -# HEVC tile bitstream splitter +# HEVC tile bitstream splitter {:data-level="all"} Register name used to load filter: __tilesplit__ This filter is not checked during graph resolution and needs explicit loading. diff --git a/docs/Filters/tssplit.md b/docs/Filters/tssplit.md index 21cd73d0..adc3da25 100644 --- a/docs/Filters/tssplit.md +++ b/docs/Filters/tssplit.md @@ -1,6 +1,6 @@ -# MPEG Transport Stream splitter +# MPEG Transport Stream splitter {:data-level="all"} Register name used to load filter: __tssplit__ This filter is not checked during graph resolution and needs explicit loading. diff --git a/docs/Filters/ttml2srt.md b/docs/Filters/ttml2srt.md index 41d05316..a20fbf15 100644 --- a/docs/Filters/ttml2srt.md +++ b/docs/Filters/ttml2srt.md @@ -1,6 +1,6 @@ -# TTML to SRT +# TTML to SRT {:data-level="all"} Register name used to load filter: __ttml2srt__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/ttml2vtt.md b/docs/Filters/ttml2vtt.md index e8cff790..49d5db9b 100644 --- a/docs/Filters/ttml2vtt.md +++ b/docs/Filters/ttml2vtt.md @@ -1,6 +1,6 @@ -# TTML to WebVTT +# TTML to WebVTT {:data-level="all"} Register name used to load filter: __ttml2vtt__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/ttmldec.md b/docs/Filters/ttmldec.md index e79ad11b..88cb1b85 100644 --- a/docs/Filters/ttmldec.md +++ b/docs/Filters/ttmldec.md @@ -1,6 +1,6 @@ -# TTML decoder +# TTML decoder {:data-level="all"} Register name used to load filter: __ttmldec__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/ttmlmerge.md b/docs/Filters/ttmlmerge.md index 7686aefe..5d0341ac 100644 --- a/docs/Filters/ttmlmerge.md +++ b/docs/Filters/ttmlmerge.md @@ -1,6 +1,6 @@ -# TTML sample merger +# TTML sample merger {:data-level="all"} Register name used to load filter: __ttmlmerge__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/ttxtdec.md b/docs/Filters/ttxtdec.md index 5b37f61d..04e090a4 100644 --- a/docs/Filters/ttxtdec.md +++ b/docs/Filters/ttxtdec.md @@ -1,6 +1,6 @@ -# TTXT/TX3G decoder +# TTXT/TX3G decoder {:data-level="all"} Register name used to load filter: __ttxtdec__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/tx3g2srt.md b/docs/Filters/tx3g2srt.md index a35e783b..33c5b5e2 100644 --- a/docs/Filters/tx3g2srt.md +++ b/docs/Filters/tx3g2srt.md @@ -1,6 +1,6 @@ -# TX3G to SRT +# TX3G to SRT {:data-level="all"} Register name used to load filter: __tx3g2srt__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/tx3g2ttml.md b/docs/Filters/tx3g2ttml.md index 696245bb..d0cc1676 100644 --- a/docs/Filters/tx3g2ttml.md +++ b/docs/Filters/tx3g2ttml.md @@ -1,6 +1,6 @@ -# TX3G to TTML +# TX3G to TTML {:data-level="all"} Register name used to load filter: __tx3g2ttml__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/tx3g2vtt.md b/docs/Filters/tx3g2vtt.md index 68a491da..0843b721 100644 --- a/docs/Filters/tx3g2vtt.md +++ b/docs/Filters/tx3g2vtt.md @@ -1,6 +1,6 @@ -# TX3G to WebVTT +# TX3G to WebVTT {:data-level="all"} Register name used to load filter: __tx3g2vtt__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/txtin.md b/docs/Filters/txtin.md index 9bb51b75..950cc1b2 100644 --- a/docs/Filters/txtin.md +++ b/docs/Filters/txtin.md @@ -1,6 +1,6 @@ -# Subtitle loader +# Subtitle loader {:data-level="all"} Register name used to load filter: __txtin__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/ufadts.md b/docs/Filters/ufadts.md index e732744c..c69bcb9a 100644 --- a/docs/Filters/ufadts.md +++ b/docs/Filters/ufadts.md @@ -1,6 +1,6 @@ -# ADTS writer +# ADTS writer {:data-level="all"} Register name used to load filter: __ufadts__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/uflatm.md b/docs/Filters/uflatm.md index 6be7b12a..2d1d34ee 100644 --- a/docs/Filters/uflatm.md +++ b/docs/Filters/uflatm.md @@ -1,6 +1,6 @@ -# Raw AAC to LATM writer +# Raw AAC to LATM writer {:data-level="all"} Register name used to load filter: __uflatm__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/ufm4v.md b/docs/Filters/ufm4v.md index df33dac6..d8bdc91b 100644 --- a/docs/Filters/ufm4v.md +++ b/docs/Filters/ufm4v.md @@ -1,6 +1,6 @@ -# M4V writer +# M4V writer {:data-level="all"} Register name used to load filter: __ufm4v__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/ufmhas.md b/docs/Filters/ufmhas.md index 43e51d81..31be89e7 100644 --- a/docs/Filters/ufmhas.md +++ b/docs/Filters/ufmhas.md @@ -1,6 +1,6 @@ -# MHAS writer +# MHAS writer {:data-level="all"} Register name used to load filter: __ufmhas__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/ufnalu.md b/docs/Filters/ufnalu.md index 5943b372..329afa92 100644 --- a/docs/Filters/ufnalu.md +++ b/docs/Filters/ufnalu.md @@ -1,6 +1,6 @@ -# AVC/HEVC to AnnexB writer +# AVC/HEVC to AnnexB writer {:data-level="all"} Register name used to load filter: __ufnalu__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/ufobu.md b/docs/Filters/ufobu.md index 1eae0026..4d3fa92c 100644 --- a/docs/Filters/ufobu.md +++ b/docs/Filters/ufobu.md @@ -1,6 +1,6 @@ -# IVF/OBU/annexB writer +# IVF/OBU/annexB writer {:data-level="all"} Register name used to load filter: __ufobu__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/ufttxt.md b/docs/Filters/ufttxt.md index 6d2b582c..55933cd3 100644 --- a/docs/Filters/ufttxt.md +++ b/docs/Filters/ufttxt.md @@ -1,6 +1,6 @@ -# TX3G unframer +# TX3G unframer {:data-level="all"} Register name used to load filter: __ufttxt__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/ufvc1.md b/docs/Filters/ufvc1.md index c997362c..477bacbc 100644 --- a/docs/Filters/ufvc1.md +++ b/docs/Filters/ufvc1.md @@ -1,6 +1,6 @@ -# VC1 writer +# VC1 writer {:data-level="all"} Register name used to load filter: __ufvc1__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/ufvtt.md b/docs/Filters/ufvtt.md index 8b2bd664..5629ae0d 100644 --- a/docs/Filters/ufvtt.md +++ b/docs/Filters/ufvtt.md @@ -1,6 +1,6 @@ -# WebVTT unframer +# WebVTT unframer {:data-level="all"} Register name used to load filter: __ufvtt__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/uncvdec.md b/docs/Filters/uncvdec.md index 06e98b3a..ddcfa587 100644 --- a/docs/Filters/uncvdec.md +++ b/docs/Filters/uncvdec.md @@ -1,6 +1,6 @@ -# UNCV decoder +# UNCV decoder {:data-level="all"} Register name used to load filter: __uncvdec__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/uncvg.md b/docs/Filters/uncvg.md index 949c06a3..329ef99a 100644 --- a/docs/Filters/uncvg.md +++ b/docs/Filters/uncvg.md @@ -1,6 +1,6 @@ -# Uncompressed Video File Format Generator Utility +# Uncompressed Video File Format Generator Utility {:data-level="all"} Register name used to load filter: __uncvg__ This is a JavaScript filter. It is not checked during graph resolution and needs explicit loading. diff --git a/docs/Filters/unframer.md b/docs/Filters/unframer.md index 7d4e0b4e..a9ae1a21 100644 --- a/docs/Filters/unframer.md +++ b/docs/Filters/unframer.md @@ -1,6 +1,6 @@ -# Stream unframer +# Stream unframer {:data-level="all"} Register name used to load filter: __unframer__ This filter is not checked during graph resolution and needs explicit loading. @@ -10,6 +10,7 @@ Example ``` gpac -i src.mp4 unframer -o dst.mp4 ``` + This will: - force input PIDs of unframer to be in serialized form (AnnexB, ADTS, ...) diff --git a/docs/Filters/vcrop.md b/docs/Filters/vcrop.md index 2a1ba655..bf4b5558 100644 --- a/docs/Filters/vcrop.md +++ b/docs/Filters/vcrop.md @@ -1,6 +1,6 @@ -# Video crop +# Video crop {:data-level="all"} Register name used to load filter: __vcrop__ This filter is not checked during graph resolution and needs explicit loading. diff --git a/docs/Filters/vflip.md b/docs/Filters/vflip.md index d9615df9..7de80868 100644 --- a/docs/Filters/vflip.md +++ b/docs/Filters/vflip.md @@ -1,6 +1,6 @@ -# Video flip +# Video flip {:data-level="all"} Register name used to load filter: __vflip__ This filter is not checked during graph resolution and needs explicit loading. diff --git a/docs/Filters/vobsubdmx.md b/docs/Filters/vobsubdmx.md index 1442f6f1..a8ab9547 100644 --- a/docs/Filters/vobsubdmx.md +++ b/docs/Filters/vobsubdmx.md @@ -1,6 +1,6 @@ -# VobSub parser +# VobSub parser {:data-level="all"} Register name used to load filter: __vobsubdmx__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/vorbisdec.md b/docs/Filters/vorbisdec.md index 8546f8ad..9e5412cf 100644 --- a/docs/Filters/vorbisdec.md +++ b/docs/Filters/vorbisdec.md @@ -1,6 +1,6 @@ -# Vorbis decoder +# Vorbis decoder {:data-level="all"} Register name used to load filter: __vorbisdec__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/vout.md b/docs/Filters/vout.md index 23cab6b6..fca13d87 100644 --- a/docs/Filters/vout.md +++ b/docs/Filters/vout.md @@ -1,6 +1,6 @@ -# Video output +# Video output {:data-level="all"} Register name used to load filter: __vout__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/vtbdec.md b/docs/Filters/vtbdec.md index 45adde1e..0c73ff6a 100644 --- a/docs/Filters/vtbdec.md +++ b/docs/Filters/vtbdec.md @@ -1,6 +1,6 @@ -# VideoToolBox decoder +# VideoToolBox decoder {:data-level="all"} Register name used to load filter: __vtbdec__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/vtt2tx3g.md b/docs/Filters/vtt2tx3g.md index 91064c10..bb9e704b 100644 --- a/docs/Filters/vtt2tx3g.md +++ b/docs/Filters/vtt2tx3g.md @@ -1,6 +1,6 @@ -# WebVTT to TX3G +# WebVTT to TX3G {:data-level="all"} Register name used to load filter: __vtt2tx3g__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/vttdec.md b/docs/Filters/vttdec.md index 229d8529..974d5ee3 100644 --- a/docs/Filters/vttdec.md +++ b/docs/Filters/vttdec.md @@ -1,6 +1,6 @@ -# WebVTT decoder +# WebVTT decoder {:data-level="all"} Register name used to load filter: __vttdec__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/writegen.md b/docs/Filters/writegen.md index 9e21a8af..77b9c1df 100644 --- a/docs/Filters/writegen.md +++ b/docs/Filters/writegen.md @@ -1,6 +1,6 @@ -# Stream to file +# Stream to file {:data-level="all"} Register name used to load filter: __writegen__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/writeqcp.md b/docs/Filters/writeqcp.md index 822ce2fa..c88b4837 100644 --- a/docs/Filters/writeqcp.md +++ b/docs/Filters/writeqcp.md @@ -1,6 +1,6 @@ -# QCP writer +# QCP writer {:data-level="all"} Register name used to load filter: __writeqcp__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/writeuf.md b/docs/Filters/writeuf.md index 0aea63b5..98eedd6d 100644 --- a/docs/Filters/writeuf.md +++ b/docs/Filters/writeuf.md @@ -1,6 +1,6 @@ -# Stream to unframed format +# Stream to unframed format {:data-level="all"} Register name used to load filter: __writeuf__ This filter may be automatically loaded during graph resolution. diff --git a/docs/Filters/xviddec.md b/docs/Filters/xviddec.md index f8606e0a..58aa0472 100644 --- a/docs/Filters/xviddec.md +++ b/docs/Filters/xviddec.md @@ -1,6 +1,6 @@ -# XVid decoder +# XVid decoder {:data-level="all"} Register name used to load filter: __xviddec__ This filter may be automatically loaded during graph resolution. From d0d29c383162d11202b06cf50cb47445609690c0 Mon Sep 17 00:00:00 2001 From: PierreDillard <7gaspard77@gmail.com> Date: Thu, 5 Sep 2024 11:22:12 +0200 Subject: [PATCH 13/28] Fix Format error .MD and collapse in Build,XML,Player sections --- .../GPAC-build-MP4Box-only-all-platforms.md | 6 +-- docs/Build/Upgrading.md | 2 +- .../build/GPAC-Build-Guide-for-Android.md | 12 +++--- .../Build/build/GPAC-Build-Guide-for-Linux.md | 7 +++- .../build/GPAC-Build-Guide-for-MSYS-MinGW.md | 4 +- docs/Build/build/GPAC-Build-Guide-for-OSX.md | 4 +- docs/Build/build/GPAC-Build-Guide-for-WASM.md | 8 ++-- .../build/GPAC-Build-Guide-for-Windows.md | 21 ++++++---- docs/Build/build/GPAC-Build-Guide-for-iOS.md | 6 +-- docs/Build/build/GPAC-Build-with-Docker.md | 12 +++--- ...ows-x86-using-free-Microsoft-Visual-C++.md | 12 +++--- docs/Build/old/Compiling-GPAC-for-MacOS-X.md | 22 ++++++++-- ...PAC-on-Debian-and-Ubuntu-and-other-Unix.md | 2 +- docs/Howtos/dash/DASH-Support-in-MP4Box.md | 6 +-- ...egmentation,-splitting-and-interleaving.md | 2 +- docs/Player/Player-Features.md | 41 ++++++++++--------- docs/Player/Player.md | 2 +- ...-with-multiview-rendering-on-3D-screens.md | 3 +- docs/Player/filters-playback.md | 2 +- docs/Player/olay-composition.md | 2 +- docs/xmlformats/BoxPatch.md | 2 +- docs/xmlformats/Common-Encryption.md | 14 +++---- docs/xmlformats/ISMACryp.md | 2 +- docs/xmlformats/NHNT-Format.md | 2 +- docs/xmlformats/OMA-DRM.md | 2 +- docs/xmlformats/TTXT-Format-Documentation.md | 2 +- overrides/base.html | 4 +- 27 files changed, 118 insertions(+), 86 deletions(-) diff --git a/docs/Build/GPAC-build-MP4Box-only-all-platforms.md b/docs/Build/GPAC-build-MP4Box-only-all-platforms.md index 0a5f6603..e4b7273a 100644 --- a/docs/Build/GPAC-build-MP4Box-only-all-platforms.md +++ b/docs/Build/GPAC-build-MP4Box-only-all-platforms.md @@ -1,5 +1,5 @@ -## Build MP4Box with Visual Studio +# Build MP4Box with Visual Studio {: data-level="all"} We published a minimal solution to build MP4Box.exe only, with zero dependency. No DLL to distribute. @@ -7,7 +7,7 @@ You need Visual Studio 2010 or more recent (a pop-up will ask you to upgrade you That's all! -## Build MP4Box with Make +# Build MP4Box with Make On your system, you need the following tools installed on your system: @@ -103,7 +103,7 @@ make -j4 You can find more examples in [our TravisCI script](https://github.com/gpac/gpac/blob/master/.travis.yml). -## Next Step +# Next Step We have started [a multimedia component-level build system called Zenbuild](https://github.com/gpac/zenbuild). Zenbuild builds FFmpeg/libav, VLC or GPAC with most of their features enabled (librtmp, jack, openHEVC, etc.). You can start using Zenbuild, it is fully operational! diff --git a/docs/Build/Upgrading.md b/docs/Build/Upgrading.md index 9916708c..22e4dce3 100644 --- a/docs/Build/Upgrading.md +++ b/docs/Build/Upgrading.md @@ -1,4 +1,4 @@ -# In source tree building +# In source tree building {: data-level="all" } If you build GPAC directly in the source tree (i.e., running `./configure && make` in the same directory as the configure script), the following steps must be done when upgrading your code base to a new version of GPAC, or when switching branches: - uninstall any previous version of GPAC (optional, the build system as of 1.0 is independent from the presence of any other version of libgpac headers on the system) diff --git a/docs/Build/build/GPAC-Build-Guide-for-Android.md b/docs/Build/build/GPAC-Build-Guide-for-Android.md index 0da9dd95..5919267a 100644 --- a/docs/Build/build/GPAC-Build-Guide-for-Android.md +++ b/docs/Build/build/GPAC-Build-Guide-for-Android.md @@ -1,12 +1,14 @@ -The Android build is a bit complicated. The method described here is the one used for the official Android builds. It is fairly rigid, with some hard-coded paths and versions. It should not be too hard to adapt it to one's own set up, but there is no guarantee that it will work as is on (for example) more recent versions of the ndk/sdk. +!!! note + The Android build is a bit complicated. The method described here is the one used for the official Android builds. It is fairly rigid, with some hard-coded paths and versions. It should not be too hard to adapt it to one's own setup, but there is no guarantee that it will work as is on (for example) more recent versions of the ndk/sdk. -It was tested on Ubuntu 14 to 18. + It was tested on Ubuntu 14 to 18. -The process has three main steps: set up the build environment, cross-compile the dependencies, build the GPAC apk. + The process has three main steps: set up the build environment, cross-compile the dependencies, build the GPAC apk. -In the following, we'll call the main working directory ``. + In the following, we'll call the main working directory ``. -# Set up the build toolchain + +# Set up the build toolchain {: data-level="all"} ## JVM and tools diff --git a/docs/Build/build/GPAC-Build-Guide-for-Linux.md b/docs/Build/build/GPAC-Build-Guide-for-Linux.md index a1f03c91..91454252 100644 --- a/docs/Build/build/GPAC-Build-Guide-for-Linux.md +++ b/docs/Build/build/GPAC-Build-Guide-for-Linux.md @@ -1,4 +1,6 @@ -_Preliminary notes: the following instructions will be based on Ubuntu and Debian. It should be easily applicable to other distributions, the only changes should be name of the packages to be installed, and the package manager used._ +!!! note + Preliminary notes: the following instructions will be based on Ubuntu and Debian. It should be easily applicable to other distributions, the only changes should be the name of the packages to be installed, and the package manager used. + GPAC is a modular piece of software which depends on third-party libraries. During the build process it will try to detect and leverage the installed third-party libraries on your system. Here are the instructions to: @@ -6,7 +8,7 @@ GPAC is a modular piece of software which depends on third-party libraries. Duri * build a minimal 'MP4Box' and 'gpac' (only contains GPAC core features like muxing and streaming), * build a complete GPAC by rebuilding all the dependencies manually. -# General case for all builds +# General case for all builds {: data-level="all" } ## Development tools @@ -185,6 +187,7 @@ If you already have a gpac version installed, or if you have already built gpac /gpac_public$ make uninstall /gpac_public$ make distclean ``` + **before** running `./configure`. ### Install for developers diff --git a/docs/Build/build/GPAC-Build-Guide-for-MSYS-MinGW.md b/docs/Build/build/GPAC-Build-Guide-for-MSYS-MinGW.md index 33a88ee6..552426be 100644 --- a/docs/Build/build/GPAC-Build-Guide-for-MSYS-MinGW.md +++ b/docs/Build/build/GPAC-Build-Guide-for-MSYS-MinGW.md @@ -1,6 +1,6 @@ -The following is the guide to build GPAC on Windows using MSYS2 and the MinGW gcc toolchain. It is a bit clunky and will probably only work on x64 and without cross-compilation. +_The following is the guide to build GPAC on Windows using MSYS2 and the MinGW gcc toolchain. It is a bit clunky and will probably only work on x64 and without cross-compilation._ -# Setting up +# Setting up {: data-level="all"} ## MSYS diff --git a/docs/Build/build/GPAC-Build-Guide-for-OSX.md b/docs/Build/build/GPAC-Build-Guide-for-OSX.md index 78fbd2dd..921ecdf2 100644 --- a/docs/Build/build/GPAC-Build-Guide-for-OSX.md +++ b/docs/Build/build/GPAC-Build-Guide-for-OSX.md @@ -1,7 +1,7 @@ -Since the OSX build is essentially the same as the Linux build, this doc will be succinct. Please refer to the [Linux build guide](GPAC-Build-Guide-for-Linux) for further details. +_Since the OSX build is essentially the same as the Linux build, this doc will be succinct. Please refer to the [Linux build guide](GPAC-Build-Guide-for-Linux) for further details._ -# MP4Box only +# MP4Box only {: data-level="all"} Same as Linux: diff --git a/docs/Build/build/GPAC-Build-Guide-for-WASM.md b/docs/Build/build/GPAC-Build-Guide-for-WASM.md index 00975559..a3f9044b 100644 --- a/docs/Build/build/GPAC-Build-Guide-for-WASM.md +++ b/docs/Build/build/GPAC-Build-Guide-for-WASM.md @@ -1,10 +1,12 @@ [**HOME**](Home) » [**Build**](Build-Introduction) » WASM -This page describes how to build GPAC for WASM/Emscripten. +_This page describes how to build GPAC for WASM/Emscripten._ -A live demo can be seen at [https://wasm.gpac.io](https://wasm.gpac.io) +!!! note + You can check a live demo at [https://wasm.gpac.io](https://wasm.gpac.io). -# (Recommended) Use Docker + +# (Recommended) Use Docker {: data-level="all"} The easiest way to build GPAC for WASM is to use the provided Dockerfile at `build/docker/wasm.Dockerfile` in the GPAC repository. diff --git a/docs/Build/build/GPAC-Build-Guide-for-Windows.md b/docs/Build/build/GPAC-Build-Guide-for-Windows.md index 27a88ac6..ba997e5d 100644 --- a/docs/Build/build/GPAC-Build-Guide-for-Windows.md +++ b/docs/Build/build/GPAC-Build-Guide-for-Windows.md @@ -1,13 +1,16 @@ -To build on Windows, you'll need: - * [Git](https://git-scm.com/download/win) - * [Visual Studio](https://visualstudio.microsoft.com/vs/community/) (at least VS2015 is recommended) +!!! note + To build on Windows, you'll need: + * [Git](https://git-scm.com/download/win) + * [Visual Studio](https://visualstudio.microsoft.com/vs/community/) (at least VS2015 is recommended) - For a full build, other tools might be required, they will be mentioned at the time. + For a full build, other tools might be required, they will be mentioned at the time. + + +# NOTE FOR Windows XP Users {: data-level="all" } -# NOTE FOR Windows XP Users Windows XP is no longer supported (by GPAC nor Microsoft) in our regular build system. See [this discussion](https://github.com/gpac/gpac/issues/1490#issuecomment-649519836) for further details. - # Build MP4Box only +# Build MP4Box only To build only the MP4Box command line utility, you will need to: @@ -31,7 +34,8 @@ It might be called something else depending on your version of Visual Studio. Th You can adjust the parameters: - `/p:Platform=x64`: Change to `/p:Platform=Win32` to get a 32 bits build - - `/p:PlatformToolset=v140` and `/p:WindowsTargetPlatformVersion=8.1`: Change this depending on your Visual Studio version and the Windows SDK version you have installed. (e.g.: `/p:PlatformToolset=v143 /p:WindowsTargetPlatformVersion=10.0` for VS 2022 on Windows 10) + - `/p:PlatformToolset=v140` and `/p:WindowsTargetPlatformVersion=8.1`: Change this depending on your Visual Studio version and the Windows SDK version you have installed. + - (e.g.: `/p:PlatformToolset=v143 /p:WindowsTargetPlatformVersion=10.0` for VS 2022 on Windows 10) You can find out what versions you have by opening the gpac.sln solution in Visual Studio, opening the property page of one the project, and checking the "Platform Toolset" and "Windows SDK Version" fields. @@ -42,7 +46,7 @@ You can adjust the parameters: You can add this directory to your `PATH` environment variable. Or move the binary to a destination in your `PATH`. Or just use it locally. - # Full GPAC build +# Full GPAC build To get a full build of the whole GPAC project, you will first need to build some dependencies, copy them over to the main gpac repository, and build it. @@ -101,6 +105,7 @@ The binaries will be in ```batch \gpac_public\bin\\ ``` + (e.g. `\gpac_public\bin\x64\Release`) You can add this to your `PATH` to run it from anywhere. diff --git a/docs/Build/build/GPAC-Build-Guide-for-iOS.md b/docs/Build/build/GPAC-Build-Guide-for-iOS.md index a634835e..9a32f532 100644 --- a/docs/Build/build/GPAC-Build-Guide-for-iOS.md +++ b/docs/Build/build/GPAC-Build-Guide-for-iOS.md @@ -1,9 +1,9 @@ -To build GPAC for iOS, we'll first need to cross-compile some dependencies, before building the project itself. +_To build GPAC for iOS, we'll first need to cross-compile some dependencies, before building the project itself._ -For the following, we'll call the working directory ``. +_For the following, we'll call the working directory ``._ -# Building dependencies +# Building dependencies {: data-level="all"} ## Get the code diff --git a/docs/Build/build/GPAC-Build-with-Docker.md b/docs/Build/build/GPAC-Build-with-Docker.md index a8c0c650..f2f03a04 100644 --- a/docs/Build/build/GPAC-Build-with-Docker.md +++ b/docs/Build/build/GPAC-Build-with-Docker.md @@ -1,13 +1,13 @@ [**HOME**](Home) » [**Build**](Build-Introduction) » Docker -This page contains instructions on how to build and use gpac with Docker. +_This page contains instructions on how to build and use gpac with Docker._ -For now, only linux containers are available. +_For now, only linux containers are available._ -Pre-requisite: a working [Docker](https://www.docker.com/) installation. +_Pre-requisite: a working [Docker](https://www.docker.com/) installation._ -# Linux +# Linux {: data-level="all"} We provide two types of resources depending on your use case. @@ -16,8 +16,10 @@ We provide two types of resources depending on your use case. ## Build your own image (optional) -```bash +``` bash + # clone gpac repo + git clone https://github.com/gpac/gpac.git cd gpac diff --git a/docs/Build/old/Command-line-GPAC-compiling-on-Windows-x86-using-free-Microsoft-Visual-C++.md b/docs/Build/old/Command-line-GPAC-compiling-on-Windows-x86-using-free-Microsoft-Visual-C++.md index fa7dbf85..a3d9d895 100644 --- a/docs/Build/old/Command-line-GPAC-compiling-on-Windows-x86-using-free-Microsoft-Visual-C++.md +++ b/docs/Build/old/Command-line-GPAC-compiling-on-Windows-x86-using-free-Microsoft-Visual-C++.md @@ -1,4 +1,4 @@ -## Overview +# Overview {: data-level="all"} Windows users often ask about how to compile GPAC and generate an installer. The main problem on platforms, like Windows, that don't rely on a modern package system is to get your dependencies with the correct version. Fortunately, we release a package containing most of the extra-libs we use. @@ -6,7 +6,7 @@ The first reaction of unix users when compiling GPAC is to use an existing gnu/g **This article was made using a clean install within a virtualized Windows XP. It focuses on command-lines but converting and compiling projects from the GUI works.** -## Getting the tools +# Getting the tools * [Download Visual C++ Express](http://www.microsoft.com/express/Downloads/) on the web (2010 has been used for this article). * Ensure you have access to msbuild.exe which is provided with [the .NET framework 3.5](http://www.microsoft.com/downloads/en/details.aspx?FamilyId=333325fd-ae52-4e35-b531-508d977d32a6&displaylang=en) (it should be installed on any up-to-date Windows): `%WINDIR%\Microsoft.NET\Framework\v3.5\MSBuild.exe` @@ -21,7 +21,7 @@ Rem: some users told us June 2010 DirectX SDK got rid of some needed components. Note: if you don't have the MFC/AFX headers, GPAC still provides you with SDL as an audio/video output. -## Getting the source code (or organizing it) +# Getting the source code (or organizing it) Make sure you have git and [subversion installed on your system](http://subversion.apache.org/), and **add the subversion binaries to your path**. @@ -35,7 +35,7 @@ Download the file at @@ -19,7 +19,7 @@ Just like any XML file, the file must begin with the usual xml header. The file ``` -## GPACDRM Element Semantics +# GPACDRM Element Semantics * `type` : is the default protection scheme type used for all tracks in the source media file. The possible values are (full name or 4 char code): * **ISMA** or **iAEC**: ISMA E&A (ISMACryp) Scheme @@ -52,7 +52,7 @@ The payload of a DRMInfo describing a PSSH blob can be encrypted using AES-128 C The following example shows a simple GPAC DRM system info: - ### Simple GPAC DRM system info +### Simple GPAC DRM system info ```xml diff --git a/docs/xmlformats/ISMACryp.md b/docs/xmlformats/ISMACryp.md index 76dd825f..f1c9cc1d 100644 --- a/docs/xmlformats/ISMACryp.md +++ b/docs/xmlformats/ISMACryp.md @@ -1,4 +1,4 @@ -# ISMACryp authoring +# ISMACryp authoring {: data-level="all"} In order to encrypt an MP4 file, MP4Box will need a specific file containing all cryptographic information, usually referred to as `drm_file` in MP4Box documentation. The command line is as follows: diff --git a/docs/xmlformats/NHNT-Format.md b/docs/xmlformats/NHNT-Format.md index bf4524e6..da569833 100644 --- a/docs/xmlformats/NHNT-Format.md +++ b/docs/xmlformats/NHNT-Format.md @@ -1,4 +1,4 @@ -# NHNT Overview +# NHNT Overview {: data-level="all"} The NHNT format has been developed during the MPEG-4 Systems implementation phase, as a way to easily mux unknown media formats to an MP4 file or an MPEG-4 multiplex. The goal was to have the media encoder produce a description of the media time fragmentation (access units and timestamps) that could be reused by a media-unaware MPEG-4 multiplexer. A NHNT source is composed of 2 or 3 parts: diff --git a/docs/xmlformats/OMA-DRM.md b/docs/xmlformats/OMA-DRM.md index 2133de7f..3ec7b3e2 100644 --- a/docs/xmlformats/OMA-DRM.md +++ b/docs/xmlformats/OMA-DRM.md @@ -1,4 +1,4 @@ -# OMA DRM authoring +# OMA DRM authoring {: data-level="all"} In order to encrypt a 3GP/MP4 file into a PDCF file, MP4Box uses the same process as CENC or ISMA encryption, only the drm file syntax changes. diff --git a/docs/xmlformats/TTXT-Format-Documentation.md b/docs/xmlformats/TTXT-Format-Documentation.md index 7d796b4a..f7eecdd4 100644 --- a/docs/xmlformats/TTXT-Format-Documentation.md +++ b/docs/xmlformats/TTXT-Format-Documentation.md @@ -1,4 +1,4 @@ -# Foreword +# Foreword {: data-level="all"} The 3GPP consortium has defined a standard for text streaming, independent of any scene description such as SMIL, SVG or BIFS: 3GPP Timed Text. MP4Box supports this standard and uses its own textual format to describe a streaming text session. diff --git a/overrides/base.html b/overrides/base.html index 167fd5e7..44955125 100644 --- a/overrides/base.html +++ b/overrides/base.html @@ -44,7 +44,7 @@ {% endif %} {% endblock %} {% block styles %} - + {% if config.theme.palette %} {% set palette = config.theme.palette %} @@ -269,7 +269,7 @@ {% endblock %} {% block scripts %} - + {% for script in config.extra_javascript %} From cfe2868fedeecd2b2cbc12c71bfadd57bcdd5a3a Mon Sep 17 00:00:00 2001 From: PierreDillard <7gaspard77@gmail.com> Date: Thu, 5 Sep 2024 16:33:04 +0200 Subject: [PATCH 14/28] add Glossary/sink, Glossary/source --- docs/Build/build/GPAC-Build-Guide-for-OSX.md | 2 +- docs/Build/build/GPAC-Build-Guide-for-iOS.md | 20 ++++++---- docs/{glossary.md => Glossary}/encode.md | 12 +++++- docs/{glossary.md => Glossary}/encrypt.md | 5 ++- docs/Glossary/sink.md | 40 +++++++++++++++++++ docs/Glossary/source.md | 41 ++++++++++++++++++++ docs/index.md | 2 + docs/javascripts/domManipulation.js | 2 +- docs/javascripts/main.js | 2 +- docs/javascripts/modalFunctions.js | 20 +++++----- docs/javascripts/search.js | 14 +++---- docs/stylesheets/modal.css | 2 +- mkdocs.yml | 10 +++-- 13 files changed, 138 insertions(+), 34 deletions(-) rename docs/{glossary.md => Glossary}/encode.md (86%) rename docs/{glossary.md => Glossary}/encrypt.md (89%) create mode 100644 docs/Glossary/sink.md create mode 100644 docs/Glossary/source.md diff --git a/docs/Build/build/GPAC-Build-Guide-for-OSX.md b/docs/Build/build/GPAC-Build-Guide-for-OSX.md index 921ecdf2..01dd302f 100644 --- a/docs/Build/build/GPAC-Build-Guide-for-OSX.md +++ b/docs/Build/build/GPAC-Build-Guide-for-OSX.md @@ -38,7 +38,7 @@ brew install freetype jpeg libpng openjpeg mad faad2 libogg libvorbis theora a52 ```bash # install build tools -sudo port -N install cmake scons coreutils gettext yasm git wget pkgconfig +sudo port -N install cmake scons coreutils gtime gettext yasm wget pkgconfig # install dependencies sudo port -N install freetype jpeg libpng openjpeg libmad faad2 libogg libvorbis libtheora a52dec ffmpeg6 x264 aom xvid openssl libsdl2 diff --git a/docs/Build/build/GPAC-Build-Guide-for-iOS.md b/docs/Build/build/GPAC-Build-Guide-for-iOS.md index 9a32f532..afabe6f1 100644 --- a/docs/Build/build/GPAC-Build-Guide-for-iOS.md +++ b/docs/Build/build/GPAC-Build-Guide-for-iOS.md @@ -9,12 +9,12 @@ _For the following, we'll call the working directory ``._ ```bash ##get src -$ git clone https://github.com/gpac/gpac.git gpac_public +git clone https://github.com/gpac/gpac.git gpac_public ##get deps -$ git clone https://github.com/gpac/deps_ios -$ cd deps_ios -/deps_ios$ git submodule update --init --recursive --force --checkout +git clone https://github.com/gpac/deps_ios +cd deps_ios +git submodule update --init --recursive --force --checkout ``` We need to keep the `gpac_public` for the main repository to have the scripts run smoothly. @@ -22,16 +22,20 @@ We need to keep the `gpac_public` for the main repository to have the scripts ru ## Build dependencies +From the `deps_ios` folder: + ```bash -/deps_ios/SDL_iOS$ cd ../build/xcode_ios -/deps_ios/build/xcode_ios$ ./generate_extra_libs.sh +cd build/xcode_ios +./generate_extra_libs.sh ``` ## Copy dependencies +Then continuing from the `build/xcode_ios` folder: + ```bash -/deps_ios/build/xcode_ios$ cd ../.. -/deps_ios$ ./CopyLibs2Public4iOS.sh +cd ../.. +./CopyLibs2Public4iOS.sh ``` If all went well, the extra_lib directory of the main repository should now look something like this: diff --git a/docs/glossary.md/encode.md b/docs/Glossary/encode.md similarity index 86% rename from docs/glossary.md/encode.md rename to docs/Glossary/encode.md index a7c3f45d..9738efde 100644 --- a/docs/glossary.md/encode.md +++ b/docs/Glossary/encode.md @@ -1,3 +1,7 @@ +--- +hide: + - toc +--- `encode` is a function that allows you to encode multimedia files into various formats using specified codecs. @@ -32,4 +36,10 @@ encode("input.mp4", "output.mp4", "libx264") - **input_file**: Path to the multimedia file to be encoded. - **output_file**: Path where the encoded file will be saved. -- **codec**: Codec to be used for encoding (e.g., libx264 for H.264 encoding). \ No newline at end of file +- **codec**: Codec to be used for encoding (e.g., libx264 for H.264 encoding). + +## See Also: +- [Codec](link-to-codec.md) +- [Bitrate](link-to-bitrate.md) +- [Transcode](link-to-transcode.md) + diff --git a/docs/glossary.md/encrypt.md b/docs/Glossary/encrypt.md similarity index 89% rename from docs/glossary.md/encrypt.md rename to docs/Glossary/encrypt.md index 69cf5a7b..60782f18 100644 --- a/docs/glossary.md/encrypt.md +++ b/docs/Glossary/encrypt.md @@ -36,4 +36,7 @@ encrypt("input.mp4", "output.mp4", "keyfile.xml") - **input_file**: Path to the multimedia file to be encrypted. - **output_file**: Path where the encrypted file will be saved. -- **key_file**: Path to the key file used for encryption. \ No newline at end of file +- **key_file**: Path to the key file used for encryption. + +## See Also: +- [Decoder](link-to-decoder.md) diff --git a/docs/Glossary/sink.md b/docs/Glossary/sink.md new file mode 100644 index 00000000..01cf3bac --- /dev/null +++ b/docs/Glossary/sink.md @@ -0,0 +1,40 @@ +--- +hide: + - toc +--- + +**SINK** in GPAC refers to an output element that receives processed media data. It is usually the endpoint of a media processing pipeline. + +**Reference:** + +`SINK` + +**Usage:** + +- Finalizing the media processing chain by writing the output data. +- Exporting media streams to various file formats (e.g., MP4, AVI). +- Displaying processed media in a player or exporting to a network stream. + +**Troubleshooting:** + +- **Output file not created:** + Check the file path permissions and ensure the output format is supported. +- **Corrupted output data:** + Confirm that the input data is correctly processed and compatible with the specified output format. + +**Example:** + +```plaintext +gpac -i input.mp4 -o output.avi +``` + +**Parameters:** + +- **output_file**: Path where the processed media data will be saved. +- **options**: Additional parameters to control the output settings (e.g., format, quality). + +## See Also: +- [Output](link-to-output.md) +- [Codec](link-to-codec.md) +- [Transcode](link-to-transcode.md) + diff --git a/docs/Glossary/source.md b/docs/Glossary/source.md new file mode 100644 index 00000000..4e39efd3 --- /dev/null +++ b/docs/Glossary/source.md @@ -0,0 +1,41 @@ +--- +hide: + - navigation +--- + + +**SOURCE** is a keyword in GPAC representing an input element that provides media data to be processed. It is typically the starting point of a media processing pipeline. + +**Reference:** + +`SOURCE` + +**Usage:** + +- Serving as the entry point in a media processing chain. +- Loading various media types (e.g., video, audio) for further processing. +- Configuring the initial properties of media streams before processing. + +**Troubleshooting:** + +- **No input data available:** + Ensure the correct path or media source is specified and accessible. +- **Invalid media format:** + Verify that the source format is supported by GPAC and correctly configured. + +**Example:** + +```plaintext +gpac -i input.mp4 -o output.mp4 +``` + +**Parameters:** + +- **input_file**: Path to the multimedia file to be loaded as a source. +- **options**: Additional parameters to control the behavior of the source filter (e.g., loop, start time). + +## See Also: +- [Input](link-to-input.md) +- [Filter](link-to-filter.md) +- [Decode](link-to-decode.md) + diff --git a/docs/index.md b/docs/index.md index 2bb3909a..c8e78dc5 100644 --- a/docs/index.md +++ b/docs/index.md @@ -32,3 +32,5 @@ hide: The documentation assumes GPAC 2.0 or above. Some informations relative to MP4Box may still be useful for older versions. . + + diff --git a/docs/javascripts/domManipulation.js b/docs/javascripts/domManipulation.js index 09fe8ff1..556d5ba5 100644 --- a/docs/javascripts/domManipulation.js +++ b/docs/javascripts/domManipulation.js @@ -9,7 +9,7 @@ document.addEventListener("DOMContentLoaded", function () { if (toggleButton) { - console.log("Creating tippy instance for toggle button"); + tippy(toggleButton, { content: 'Toggle Nav/Toc', placement: 'left', diff --git a/docs/javascripts/main.js b/docs/javascripts/main.js index acd73b71..af809cee 100644 --- a/docs/javascripts/main.js +++ b/docs/javascripts/main.js @@ -16,7 +16,7 @@ document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function() { initializeLevelManagement(); - initializeTOCClickHandler(); + /* initializeTOCClickHandler(); */ let cachedKeywords = getCache('keywordsCache'); let cachedDefinitions = getCache('definitionsCache'); diff --git a/docs/javascripts/modalFunctions.js b/docs/javascripts/modalFunctions.js index d3751cc4..9a15618e 100644 --- a/docs/javascripts/modalFunctions.js +++ b/docs/javascripts/modalFunctions.js @@ -4,11 +4,11 @@ function openModal(keyword, definition) { const modal = document.getElementById("modal"); const modalTitle = document.getElementById("modal-title"); const modalDefinition = document.getElementById("modal-definition"); - // TODO: navigation to the keyword page is under development + const modalLink = document.getElementById("modal-link"); + - /* const modalLink = document.getElementById("modal-link"); */ - - if (modalTitle && modalDefinition /* && modalLink */) { + + if (modalTitle && modalDefinition && modalLink) { let descriptionText; if (typeof definition === 'string') { descriptionText = definition; @@ -17,16 +17,18 @@ function openModal(keyword, definition) { } else { descriptionText = 'Definition not available'; } - /* const glossaryPageUrl = `${window.location.origin}/glossary/${keyword.toLowerCase()}/`; */ + const glossaryPageUrl = `${ + window.location.origin + }/Glossary/${keyword.toLowerCase()}/`; - /* Removed the redirection logic */ modalTitle.textContent = keyword; modalDefinition.textContent = descriptionText; - /* modalLink.href = `${window.location.origin}/glossary/${keyword.toLowerCase()}/`; */ + modalLink.href = `${window.location.origin}/Glossary/${keyword.toLowerCase()}/`; modal.classList.remove("hidden"); modal.style.display = "block"; - /* modalLink.classList.remove("hidden"); */ - modalLink.href = `${window.location.origin}/glossary/${keyword.toLowerCase()}/`; + modalLink.classList.remove("hidden"); + modalLink.href = `${window.location.origin}/Glossary/${keyword.toLowerCase()}/`; + modalLink.href = glossaryPageUrl; modal.classList.remove("hidden"); modal.style.display = "block"; modalLink.classList.remove("hidden"); diff --git a/docs/javascripts/search.js b/docs/javascripts/search.js index 6bcdd380..4f282aa3 100644 --- a/docs/javascripts/search.js +++ b/docs/javascripts/search.js @@ -13,9 +13,9 @@ document.addEventListener('DOMContentLoaded', function() { if (isBeginnerMode) { - console.log("Beginner mode: creating/updating tooltip"); + if (!tippyInstance && searchInput) { - console.log("Creating new tippy instance"); + tippyInstance = tippy(searchInput, { content: '⚠️ For best search results, switch to expert mode', placement: 'left', @@ -24,13 +24,13 @@ document.addEventListener('DOMContentLoaded', function() { hideOnClick: true }); } else if (tippyInstance) { - console.log("Updating existing tippy instance"); + tippyInstance.setProps({ theme: isDarkMode ? 'dark' : 'light' }); } } else { - console.log("Expert mode: destroying tooltip if it exists"); + if (tippyInstance) { tippyInstance.destroy(); tippyInstance = null; @@ -39,16 +39,16 @@ document.addEventListener('DOMContentLoaded', function() { } if (searchInput) { - console.log("Search input found, setting up tooltip"); + setTimeout(() => { - console.log("Initializing tooltip after delay"); + updateTooltip(); }, 100); document.addEventListener('levelChanged', function() { - console.log("Level changed event detected"); + updateTooltip(); }); diff --git a/docs/stylesheets/modal.css b/docs/stylesheets/modal.css index 3b59ed64..983cb629 100644 --- a/docs/stylesheets/modal.css +++ b/docs/stylesheets/modal.css @@ -67,7 +67,7 @@ display: inline-block; padding: 6px 12px; background-color: #d94412; - color: #ffffff; + color: #ffffff!important; text-decoration: none; border-radius: 4px; font-size: 14px; diff --git a/mkdocs.yml b/mkdocs.yml index 25621e0c..63cd1ac6 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -389,7 +389,9 @@ nav: - TTXT Format: xmlformats/TTXT-Format-Documentation.md - NHML format: xmlformats/NHML-Format.md - NHNT format: xmlformats/NHNT-Format.md - # TODO: implement keyword pages - # - Glossary: - # - glossary/encode.md - # - glossary/encrypt.md \ No newline at end of file + + - Glossary: + - Glossary/encode.md + - Glossary/encrypt.md + - Glossary/sink.md + - Glossary/source.md \ No newline at end of file From d236b644897422ab07b85acacb25b802e3857fab Mon Sep 17 00:00:00 2001 From: PierreDillard <7gaspard77@gmail.com> Date: Fri, 6 Sep 2024 13:08:13 +0200 Subject: [PATCH 15/28] add custom 404 --- docs/{Glossary => glossary}/encode.md | 0 docs/{Glossary => glossary}/encrypt.md | 0 docs/{Glossary => glossary}/sink.md | 2 +- docs/{Glossary => glossary}/source.md | 0 docs/javascripts/modalFunctions.js | 6 +- docs/stylesheets/keyword-cloud.css | 3 +- mkdocs.yml | 9 +- overrides/404.html | 159 +++++++++++++++++++++++++ overrides/base.html | 6 +- 9 files changed, 172 insertions(+), 13 deletions(-) rename docs/{Glossary => glossary}/encode.md (100%) rename docs/{Glossary => glossary}/encrypt.md (100%) rename docs/{Glossary => glossary}/sink.md (86%) rename docs/{Glossary => glossary}/source.md (100%) create mode 100644 overrides/404.html diff --git a/docs/Glossary/encode.md b/docs/glossary/encode.md similarity index 100% rename from docs/Glossary/encode.md rename to docs/glossary/encode.md diff --git a/docs/Glossary/encrypt.md b/docs/glossary/encrypt.md similarity index 100% rename from docs/Glossary/encrypt.md rename to docs/glossary/encrypt.md diff --git a/docs/Glossary/sink.md b/docs/glossary/sink.md similarity index 86% rename from docs/Glossary/sink.md rename to docs/glossary/sink.md index 01cf3bac..3c2ee1d0 100644 --- a/docs/Glossary/sink.md +++ b/docs/glossary/sink.md @@ -3,7 +3,7 @@ hide: - toc --- -**SINK** in GPAC refers to an output element that receives processed media data. It is usually the endpoint of a media processing pipeline. +**SINK** refers to an output element that receives processed media data. It is usually the endpoint of a media processing pipeline. **Reference:** diff --git a/docs/Glossary/source.md b/docs/glossary/source.md similarity index 100% rename from docs/Glossary/source.md rename to docs/glossary/source.md diff --git a/docs/javascripts/modalFunctions.js b/docs/javascripts/modalFunctions.js index 9a15618e..3c158205 100644 --- a/docs/javascripts/modalFunctions.js +++ b/docs/javascripts/modalFunctions.js @@ -19,15 +19,15 @@ function openModal(keyword, definition) { } const glossaryPageUrl = `${ window.location.origin - }/Glossary/${keyword.toLowerCase()}/`; + }/glossary/${keyword.toLowerCase()}/`; modalTitle.textContent = keyword; modalDefinition.textContent = descriptionText; - modalLink.href = `${window.location.origin}/Glossary/${keyword.toLowerCase()}/`; + modalLink.href = `${window.location.origin}/glossary/${keyword.toLowerCase()}/`; modal.classList.remove("hidden"); modal.style.display = "block"; modalLink.classList.remove("hidden"); - modalLink.href = `${window.location.origin}/Glossary/${keyword.toLowerCase()}/`; + modalLink.href = `${window.location.origin}/glossary/${keyword.toLowerCase()}/`; modalLink.href = glossaryPageUrl; modal.classList.remove("hidden"); modal.style.display = "block"; diff --git a/docs/stylesheets/keyword-cloud.css b/docs/stylesheets/keyword-cloud.css index 3a3fa2b9..9bbe5786 100644 --- a/docs/stylesheets/keyword-cloud.css +++ b/docs/stylesheets/keyword-cloud.css @@ -20,8 +20,7 @@ margin-right: 1rem; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); transition: background-color 0.3s ease-in-out; - border-radius: 8px; - margin-right: 1rem; + } diff --git a/mkdocs.yml b/mkdocs.yml index 63cd1ac6..8de7a5e8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -2,6 +2,7 @@ site_name: GPAC wiki site_url: https://wiki.gpac.io/ site_author: GPAC contributors extra: + keywords_file: 'data/keywords.json' extra_javascript: @@ -391,7 +392,7 @@ nav: - NHNT format: xmlformats/NHNT-Format.md - Glossary: - - Glossary/encode.md - - Glossary/encrypt.md - - Glossary/sink.md - - Glossary/source.md \ No newline at end of file + - glossary/encode.md + - glossary/encrypt.md + - glossary/sink.md + - glossary/source.md \ No newline at end of file diff --git a/overrides/404.html b/overrides/404.html new file mode 100644 index 00000000..017ed5f8 --- /dev/null +++ b/overrides/404.html @@ -0,0 +1,159 @@ +{#- This file was automatically generated - do not edit -#} +{% extends "main.html" %} +{% block content %} +
    + GPAC Logo +

    404 - Page Not Found

    +
    + + +
    +

    Do you know this term and wish to contribute to the page?

    + Contribute to GPAC Wiki +
    +
    + + + + +{% endblock %} \ No newline at end of file diff --git a/overrides/base.html b/overrides/base.html index 44955125..1cb3f397 100644 --- a/overrides/base.html +++ b/overrides/base.html @@ -44,7 +44,7 @@ {% endif %} {% endblock %} {% block styles %} - + {% if config.theme.palette %} {% set palette = config.theme.palette %} @@ -238,7 +238,7 @@ "base": base_url, "features": features, "translations": {}, - "search": "assets/javascripts/workers/search.b8dbb3d2.min.js" | url + "search": "assets/javascripts/workers/search.07f07601.min.js" | url } -%} {%- if config.extra.version -%} {%- set mike = config.plugins.get("mike") -%} @@ -269,7 +269,7 @@ {% endblock %} {% block scripts %} - + {% for script in config.extra_javascript %} From 910e5b62eff77c7a898f6e67ae5756dff6502237 Mon Sep 17 00:00:00 2001 From: PierreDillard <7gaspard77@gmail.com> Date: Fri, 6 Sep 2024 13:53:26 +0200 Subject: [PATCH 16/28] fix 404 template --- overrides/404.html | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/overrides/404.html b/overrides/404.html index 017ed5f8..ee547f2e 100644 --- a/overrides/404.html +++ b/overrides/404.html @@ -6,13 +6,21 @@

    404 - Page Not Found

    -
    - View full glossary - Return to homepage + -
    -

    Do you know this term and wish to contribute to the page?

    - Contribute to GPAC Wiki + +
    @@ -122,7 +130,7 @@

    404 - Page Not Found

    } .contribution-button { - background-color: var(--md-accent-fg-color); + background-color: #333!important; color: white!important; } @@ -132,7 +140,8 @@

    404 - Page Not Found

    {% endblock %} \ No newline at end of file From 9315dea3c133b941b028f615e2f5cef9df086860 Mon Sep 17 00:00:00 2001 From: PierreDillard <7gaspard77@gmail.com> Date: Fri, 6 Sep 2024 15:21:22 +0200 Subject: [PATCH 17/28] add codec, bitrate & transcode in /glossary --- docs/glossary/bitrate.md | 40 +++++++++++++++++++++++++++++++++ docs/glossary/codec.md | 42 +++++++++++++++++++++++++++++++++++ docs/glossary/encode.md | 6 ++--- docs/glossary/encrypt.md | 28 +++++++++++++----------- docs/glossary/sink.md | 29 ++++++++++++------------ docs/glossary/source.md | 31 +++++++++++++------------- docs/glossary/transcode.md | 45 ++++++++++++++++++++++++++++++++++++++ mkdocs.yml | 5 ++++- 8 files changed, 180 insertions(+), 46 deletions(-) create mode 100644 docs/glossary/bitrate.md create mode 100644 docs/glossary/codec.md create mode 100644 docs/glossary/transcode.md diff --git a/docs/glossary/bitrate.md b/docs/glossary/bitrate.md new file mode 100644 index 00000000..c9c1a4d3 --- /dev/null +++ b/docs/glossary/bitrate.md @@ -0,0 +1,40 @@ + + +`bitrate` refers to the amount of data processed per unit of time during encoding or streaming. It directly impacts the quality and size of multimedia files. + +## Reference + +### +```bash +bitrate(input_file, output_file, bitrate_value) +``` + +## Usage + +- **Setting specific bitrates for encoding to optimize for quality or file size** +- **Adjusting audio/video bitrates to control bandwidth usage for streaming** + +## Troubleshooting + +### File size too large +- Reduce the bitrate for a smaller file size. + +### Quality is poor +- Increase the bitrate for better quality. + +## Example + +```bash +bitrate("input.mp4", "output.mp4", "1000k") +``` + +## Parameters + +- **input_file**: Path to the file. +- **output_file**: Output path. +- **bitrate_value**: Desired bitrate (e.g., `1000k` for 1000 kbps). + +## See Also: +- [Codec](codec.md) +- [Transcode](transcode.md) + diff --git a/docs/glossary/codec.md b/docs/glossary/codec.md new file mode 100644 index 00000000..683096e4 --- /dev/null +++ b/docs/glossary/codec.md @@ -0,0 +1,42 @@ +--- +hide: + - toc +--- +`codec` refers to a compression algorithm used to encode and decode multimedia data (such as video, audio, etc.) into different formats for transmission and storage. + +## Reference + +### +```bash +codec(input_file, output_file, codec_name) +``` +## Usage + +- **Compressing video or audio using different codecs** +- **Optimizing multimedia files for storage or streaming** +- **Converting between different codecs for compatibility** + +## Troubleshooting + +### File not playing correctly +- Ensure the codec used is compatible with the media player. + +### Poor quality +- Verify that the encoding settings for the codec are optimized, such as using higher bitrates. + +## Example + +```bash +codec("input.mp4", "output.mp4", "libx265") +``` + +## Parameters + +- **input_file**: Path to the multimedia file. +- **output_file**: Path where the output file will be saved. +- **codec_name**: The codec to be used for encoding, e.g., `libx265` for HEVC. + +## See Also +- [Bitrate](bitrate.md) +- [Transcode](transcode.md) + diff --git a/docs/glossary/encode.md b/docs/glossary/encode.md index 9738efde..d7754b7e 100644 --- a/docs/glossary/encode.md +++ b/docs/glossary/encode.md @@ -39,7 +39,7 @@ encode("input.mp4", "output.mp4", "libx264") - **codec**: Codec to be used for encoding (e.g., libx264 for H.264 encoding). ## See Also: -- [Codec](link-to-codec.md) -- [Bitrate](link-to-bitrate.md) -- [Transcode](link-to-transcode.md) +- [Codec](codec) +- [Bitrate](bitrate) +- [Transcode](transcode) diff --git a/docs/glossary/encrypt.md b/docs/glossary/encrypt.md index 60782f18..928a8100 100644 --- a/docs/glossary/encrypt.md +++ b/docs/glossary/encrypt.md @@ -1,5 +1,7 @@ - - +--- +hide: + - toc +--- `encrypt` is a function that allows you to encrypt multimedia files using specified encryption keys. @@ -7,36 +9,36 @@ encrypt(input_file, output_file, key_file) ``` -#### Reference +## Reference ```bash encrypt("input.mp4", "output.mp4", "keyfile.xml") ``` -#### Usage +## Usage - Encrypting video files - Encrypting audio files - Encrypting live multimedia streams - Managing encryption keys -#### Troubleshooting +## Troubleshooting -- **My output file is corrupted after encryption** - - Verify that the key file is correct and compatible with the input multimedia file. -- **Permission error when accessing the key file** - - Ensure you have the necessary permissions to read the key file. +### My output file is corrupted after encryption +- Verify that the key file is correct and compatible with the input multimedia file. -#### Example +### Permission error when accessing the key file +- Ensure you have the necessary permissions to read the key file. +## Example ```bash encrypt("input.mp4", "output.mp4", "keyfile.xml") ``` -#### Parameters +## Parameters - **input_file**: Path to the multimedia file to be encrypted. - **output_file**: Path where the encrypted file will be saved. - **key_file**: Path to the key file used for encryption. -## See Also: -- [Decoder](link-to-decoder.md) +## See Also +- [Decoder](decoder) diff --git a/docs/glossary/sink.md b/docs/glossary/sink.md index 3c2ee1d0..a400b2f2 100644 --- a/docs/glossary/sink.md +++ b/docs/glossary/sink.md @@ -3,37 +3,38 @@ hide: - toc --- -**SINK** refers to an output element that receives processed media data. It is usually the endpoint of a media processing pipeline. +`sink` refers to an output element that receives processed media data. It is usually the endpoint of a media processing pipeline. -**Reference:** +## Reference `SINK` -**Usage:** +## Usage -- Finalizing the media processing chain by writing the output data. -- Exporting media streams to various file formats (e.g., MP4, AVI). -- Displaying processed media in a player or exporting to a network stream. +- **Finalizing the media processing chain by writing the output data.** +- **Exporting media streams to various file formats (e.g., MP4, AVI).** +- **Displaying processed media in a player or exporting to a network stream.** -**Troubleshooting:** +## Troubleshooting -- **Output file not created:** - Check the file path permissions and ensure the output format is supported. -- **Corrupted output data:** - Confirm that the input data is correctly processed and compatible with the specified output format. +### Output file not created +- Check the file path permissions and ensure the output format is supported. -**Example:** +### Corrupted output data +- Confirm that the input data is correctly processed and compatible with the specified output format. + +## Example ```plaintext gpac -i input.mp4 -o output.avi ``` -**Parameters:** +## Parameters - **output_file**: Path where the processed media data will be saved. - **options**: Additional parameters to control the output settings (e.g., format, quality). -## See Also: +## See Also - [Output](link-to-output.md) - [Codec](link-to-codec.md) - [Transcode](link-to-transcode.md) diff --git a/docs/glossary/source.md b/docs/glossary/source.md index 4e39efd3..a8ca8872 100644 --- a/docs/glossary/source.md +++ b/docs/glossary/source.md @@ -1,41 +1,42 @@ --- hide: - - navigation + - toc --- -**SOURCE** is a keyword in GPAC representing an input element that provides media data to be processed. It is typically the starting point of a media processing pipeline. +`source` is a keyword in GPAC representing an input element that provides media data to be processed. It is typically the starting point of a media processing pipeline. -**Reference:** +## Reference `SOURCE` -**Usage:** +## Usage - Serving as the entry point in a media processing chain. - Loading various media types (e.g., video, audio) for further processing. - Configuring the initial properties of media streams before processing. -**Troubleshooting:** +## Troubleshooting -- **No input data available:** - Ensure the correct path or media source is specified and accessible. -- **Invalid media format:** - Verify that the source format is supported by GPAC and correctly configured. +### No input data available +- Ensure the correct path or media source is specified and accessible. -**Example:** +### Invalid media format +- Verify that the source format is supported by GPAC and correctly configured. + +## Example ```plaintext gpac -i input.mp4 -o output.mp4 ``` -**Parameters:** +## Parameters - **input_file**: Path to the multimedia file to be loaded as a source. - **options**: Additional parameters to control the behavior of the source filter (e.g., loop, start time). -## See Also: -- [Input](link-to-input.md) -- [Filter](link-to-filter.md) -- [Decode](link-to-decode.md) +## See Also +- [Input](input) +- [Filter](filter) +- [Decode](decode) diff --git a/docs/glossary/transcode.md b/docs/glossary/transcode.md new file mode 100644 index 00000000..cf661173 --- /dev/null +++ b/docs/glossary/transcode.md @@ -0,0 +1,45 @@ +--- +hide: + - toc +--- + +`transcode` is the process of converting a multimedia file from one format to another by decoding and re-encoding the file. + +## Reference + +### +```bash +transcode(input_file, output_file, codec, bitrate) +``` + +## Usage + +- **Converting between formats for different devices** +- **Changing video resolution or quality while preserving content** +- **Adjusting parameters like codec and bitrate for various outputs** + +## Troubleshooting + +### File does not play after transcoding +- Ensure you used the correct codec and bitrate for the target platform. + +### File quality is too low +- Check your codec and bitrate settings for proper optimization. + +## Example + +```bash +transcode("input.mp4", "output.mp4", "libvpx", "500k") +``` + +## Parameters + +- **input_file**: File to be transcoded. +- **output_file**: Output file path. +- **codec**: Codec to be used (e.g., `libvpx` for VP8). +- **bitrate**: Desired bitrate for the output file. + +## See Also: +- [Codec](codec.md) +- [Bitrate](bitrate.md) + diff --git a/mkdocs.yml b/mkdocs.yml index 8de7a5e8..81eb2480 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -395,4 +395,7 @@ nav: - glossary/encode.md - glossary/encrypt.md - glossary/sink.md - - glossary/source.md \ No newline at end of file + - glossary/source.md + - glossary/transcode.md + - glossary/bitrate.md + - glossary/codec.md \ No newline at end of file From 1b55eb4b8f418468a26529b3acc11bdae680cf48 Mon Sep 17 00:00:00 2001 From: PierreDillard <7gaspard77@gmail.com> Date: Fri, 6 Sep 2024 16:34:16 +0200 Subject: [PATCH 18/28] improve md format in encode.md, sink.md --- docs/glossary/encode.md | 6 +++--- docs/glossary/sink.md | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/glossary/encode.md b/docs/glossary/encode.md index d7754b7e..a1f710b2 100644 --- a/docs/glossary/encode.md +++ b/docs/glossary/encode.md @@ -39,7 +39,7 @@ encode("input.mp4", "output.mp4", "libx264") - **codec**: Codec to be used for encoding (e.g., libx264 for H.264 encoding). ## See Also: -- [Codec](codec) -- [Bitrate](bitrate) -- [Transcode](transcode) +- [Codec](codec.md) +- [Bitrate](bitrate.md) +- [Transcode](transcode.md) diff --git a/docs/glossary/sink.md b/docs/glossary/sink.md index a400b2f2..8eea32fa 100644 --- a/docs/glossary/sink.md +++ b/docs/glossary/sink.md @@ -35,7 +35,7 @@ gpac -i input.mp4 -o output.avi - **options**: Additional parameters to control the output settings (e.g., format, quality). ## See Also -- [Output](link-to-output.md) -- [Codec](link-to-codec.md) -- [Transcode](link-to-transcode.md) +- [Output](output) +- [Codec](codec.md) +- [Transcode](transcode.md) From 8600e73bba14b3baaa33f0893c9388e578c55361 Mon Sep 17 00:00:00 2001 From: PierreDillard <7gaspard77@gmail.com> Date: Mon, 9 Sep 2024 13:41:35 +0200 Subject: [PATCH 19/28] improve bitrate, codec, sink && source --- changements_diff_origin.txt | 15446 ++++++++++++++++++++++++++++++++++ docs/glossary/bitrate.md | 5 +- docs/glossary/codec.md | 8 + docs/glossary/decoder.md | 46 + docs/glossary/output.md | 42 + docs/glossary/sink.md | 2 +- docs/glossary/source.md | 4 +- mkdocs.yml | 15 +- overrides/base.html | 6 +- 9 files changed, 15564 insertions(+), 10 deletions(-) create mode 100644 changements_diff_origin.txt create mode 100644 docs/glossary/decoder.md create mode 100644 docs/glossary/output.md diff --git a/changements_diff_origin.txt b/changements_diff_origin.txt new file mode 100644 index 00000000..38616311 --- /dev/null +++ b/changements_diff_origin.txt @@ -0,0 +1,15446 @@ +diff --git a/.gitignore b/.gitignore +index 8f997d18..ecb5b0f8 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -1,6 +1,8 @@ + .ignore/ + site/ + .venv/ ++venv/ + __pycache__/ + site.* + .DS_Store ++node_modules/ +diff --git a/content-tagging-system.md b/content-tagging-system.md +new file mode 100644 +index 00000000..38130513 +--- /dev/null ++++ b/content-tagging-system.md +@@ -0,0 +1,136 @@ ++ ++ ++# GPAC Wiki Content Tagging and Level Switch System ++ ++## Overview ++ ++The GPAC wiki implements a content tagging and level switch system to provide a customized reading experience for users with different levels of expertise. This system allows users to toggle between "Beginner" and "Expert" modes, affecting the visibility of content sections and keywords displayed. ++ ++## Purpose ++ ++The main goals of this system are: ++1. To help users new to GPAC access simpler, more approachable content. ++2. To provide more in-depth, technical content for experienced users. ++3. To create a dynamic reading experience that adapts to the user's knowledge level. ++ ++## Implementation ++ ++### Content Tagging ++ ++Content tagging is implemented in Markdown files using the `attr_list` extension. Tags are applied to H1 headers (single `#` in Markdown) using the following syntax: ++ ++```markdown ++# Section Title {: data-level="beginner" } ++``` ++ ++Available levels: ++- `beginner`: Content suitable for newcomers to GPAC. ++- `expert`: More advanced content (default if not specified). ++- `all`: Content that appears in both beginner and expert modes. ++ ++### Level Switch ++ ++The level switch is a toggle located in the top-left corner of the user interface. It allows users to switch between "Beginner" and "Expert" modes. ++ ++Default setting: The default level is set to "Expert", displaying all documentation. ++ ++User preference storage: The selected level is stored in the browser's local storage, persisting between sessions. ++ ++## Functionality ++ ++### Content Visibility ++ ++When switching between levels: ++ ++1. Expert mode: ++ - All sections are visible. ++ - The full Table of Contents (TOC) is displayed, including all sections and subsections. ++ ++2. Beginner mode: ++ - Only sections tagged as "beginner" or "all" are visible. ++ - Sections not explicitly tagged may disappear. ++ - The TOC displays only visible sections without subsections. ++ ++### Keywords Cloud ++ ++The keywords cloud is dynamic and changes based on the selected level: ++ ++- In Expert mode: All keywords are displayed. ++- In Beginner mode: Only keywords tagged as "beginner" or "all" are shown. ++ ++Keywords are defined in the `data/keywords.json` file with the following structure: ++ ++```json ++{ ++ "KEYWORD": { ++ "description": "Keyword description", ++ "level": "beginner" ++ } ++} ++``` ++ ++When a user clicks on a keyword, a modal appears with the keyword's definition. ++ ++## Implementation Details ++ ++### Key Files ++ ++- `javascripts/levels.js`: Main logic for level switching and content filtering. ++- `javascripts/domManipulation.js`: Handles DOM manipulation for showing/hiding content. ++ ++### Howtos Section ++ ++The level switching functionality is currently implemented only in the "Howtos" section of the documentation. ++ ++ ++## Developer Guidelines ++ ++1. Tagging new content: ++ - Always tag H1 headers in Markdown files. ++ - Use the format: `# Title {: data-level="level" }` where `level` is "beginner", "expert", or "all". ++ - The "all" sections are visible to beginners and experts. ++ Any section tagged with "all" will have its collapse menu unfolded. ++ They are often used for "Overview" sections. ++ - Untagged sections are considered "expert" by default. ++ ++2. Adding new keywords: ++ - Add new keywords to the `data/keywords.json` file. ++ - Include a description and appropriate level tag. ++ ++3. Testing: ++ - Test new content in both Beginner and Expert modes to ensure proper visibility. ++ - Verify that the TOC updates correctly when switching levels. ++ - Check that the keywords cloud updates appropriately. ++ ++## Best Practices for Content Formatting ++ ++When adding or editing content in the GPAC wiki, it's crucial to follow certain formatting practices to ensure readability and proper rendering of the documentation. One key aspect to pay attention to is the spacing between code blocks and other elements. ++ ++### Spacing Between Code Blocks ++ ++Always add a blank line between code blocks and surrounding text. This practice prevents rendering issues and improves readability. ++ ++#### Good Practice: ++ ++Example: ++ ++![Code block formatting issue](docs/images/good_format.png) ++ ++#### Bad Practice: ++ ++Avoid this: ++Example: ++ ++![Code block formatting issue](docs/images/bad_format.png) ++ ++ ++## Known Limitations ++ ++1. The level switching feature is currently only available in the "Howtos" section. ++2. The visibility of untagged sections in Beginner mode may not be consistent across all pages. ++ ++## Future Enhancements ++ ++1. Implement direct linking to keyword pages (e.g., `/glossary/${keyword}`). ++2. Extend the level switching functionality to other sections of the documentation. ++3. Improve the visibility and user experience of the level switch toggle. +diff --git a/docs/Build/build/GPAC-Build-Guide-for-Linux.md b/docs/Build/build/GPAC-Build-Guide-for-Linux.md +index bc277d85..a1f03c91 100644 +--- a/docs/Build/build/GPAC-Build-Guide-for-Linux.md ++++ b/docs/Build/build/GPAC-Build-Guide-for-Linux.md +@@ -58,7 +58,7 @@ You can either: + Install the development packages for the third-party libraries GPAC is able to leverage: + + ```bash +-sudo apt install zlib1g-dev libfreetype6-dev libjpeg62-dev libpng-dev libmad0-dev libfaad-dev libogg-dev libvorbis-dev libtheora-dev liba52-0.7.4-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavdevice-dev libnghttp2-dev libopenjp2-7-dev libcaca-dev libxv-dev x11proto-video-dev libgl1-mesa-dev libglu1-mesa-dev x11proto-gl-dev libxvidcore-dev libssl-dev libjack-jackd2-dev libasound2-dev libpulse-dev libsdl2-dev dvb-apps mesa-utils ++sudo apt install zlib1g-dev libfreetype6-dev libjpeg62-dev libpng-dev libmad0-dev libfaad-dev libogg-dev libvorbis-dev libtheora-dev liba52-0.7.4-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavdevice-dev libnghttp2-dev libopenjp2-7-dev libcaca-dev libxv-dev x11proto-video-dev libgl1-mesa-dev libglu1-mesa-dev x11proto-gl-dev libxvidcore-dev libssl-dev libjack-jackd2-dev libasound2-dev libpulse-dev libsdl2-dev dvb-apps mesa-utils libcurl4-openssl-dev + ``` + + This list should work on Ubuntu from 14.04 (`trusty`) to at least 22.04 (`jammy`). +diff --git a/docs/Build/build/GPAC-Build-Guide-for-OSX.md b/docs/Build/build/GPAC-Build-Guide-for-OSX.md +index 0ed7d555..78fbd2dd 100644 +--- a/docs/Build/build/GPAC-Build-Guide-for-OSX.md ++++ b/docs/Build/build/GPAC-Build-Guide-for-OSX.md +@@ -38,10 +38,10 @@ brew install freetype jpeg libpng openjpeg mad faad2 libogg libvorbis theora a52 + + ```bash + # install build tools +-port install cmake scons coreutils gettext yasm git wget pkgconfig ++sudo port -N install cmake scons coreutils gettext yasm git wget pkgconfig + + # install dependencies +-port install freetype jpeg libpng openjpeg libmad faad2 libogg libvorbis libtheora a52dec ffmpeg x264 aom xvid openssl libsdl2 ++sudo port -N install freetype jpeg libpng openjpeg libmad faad2 libogg libvorbis libtheora a52dec ffmpeg6 x264 aom xvid openssl libsdl2 + ``` + +
    +diff --git a/docs/Build/build/GPAC-Build-with-Docker.md b/docs/Build/build/GPAC-Build-with-Docker.md +index 90b369e0..a8c0c650 100644 +--- a/docs/Build/build/GPAC-Build-with-Docker.md ++++ b/docs/Build/build/GPAC-Build-with-Docker.md +@@ -25,10 +25,10 @@ cd gpac + ... + + # doubly optional: you can change and rebuild the base system containing the dependencies for gpac with +-docker build -t gpac/ubuntu-deps -f build/docker/ubuntu-deps.DockerFile . ++docker build -t gpac/ubuntu-deps -f build/docker/ubuntu-deps.Dockerfile . + + # build the docker image +-docker build -t myimages/gpac -f build/docker/ubuntu.DockerFile . ++docker build -t myimages/gpac -f build/docker/ubuntu.Dockerfile . + ``` + + ## Use an image +diff --git a/docs/Build/old/Compiling-GPAC-for-MacOS-X.md b/docs/Build/old/Compiling-GPAC-for-MacOS-X.md +index 51586109..df4b01e7 100644 +--- a/docs/Build/old/Compiling-GPAC-for-MacOS-X.md ++++ b/docs/Build/old/Compiling-GPAC-for-MacOS-X.md +@@ -13,9 +13,10 @@ git clone https://github.com/gpac/gpac.git + ## (Re-)Installing + + If macports is installed, [uninstall it](http://guide.macports.org/chunked/installing.macports.uninstalling.html): +-``` +-sudo port -f uninstall installed +-sudo rm -rf /opt/local /Applications/DarwinPorts /Applications/MacPorts /Library/LaunchDaemons/org.macports.* /Library/Receipts/DarwinPorts*.pkg /Library/Receipts/MacPorts*.pkg /Library/StartupItems/DarwinPortsStartup /Library/Tcl/darwinports1.0 /Library/Tcl/macports1.0 ~/.macports ++ ++ ``` ++ sudo port -f uninstall installed ++sudo rm -rf /opt/local /Applications/DarwinPorts /Applications/MacPorts /Library/LaunchDaemons/org.macports.* /Library/Receipts/DarwinPorts*.pkg /Library/Receipts/MacPorts*.pkg Library/StartupItems/DarwinPortsStartup /Library/Tcl/darwinports1.0 /Library/Tcl/macports1.0 ~/.macports + ``` + + [Install macport](http://distfiles.macports.org/MacPorts/). You MUST install a version >=1.9.x +diff --git a/docs/Developers/index.md b/docs/Developers/index.md +index b8c7e6df..002fd12b 100644 +--- a/docs/Developers/index.md ++++ b/docs/Developers/index.md +@@ -1,8 +1,49 @@ ++--- ++title: GPAC developer guide ++--- + +-## Developer resources ++## Contributing ++ ++A complex project like GPAC wouldn’t exist and persist without the support of its community. Please contribute: a nice message, supporting us in our communication, reporting issues when you see them … any gesture, even the smallest ones, counts. ++ ++## Getting support and reporting issues ++ ++Please use [github issues](https://github.com/gpac/gpac/issues) for feature requests and bug reports. When filing a request there, please tag it as feature-request. ++ ++ ++## API documentation ++ ++The [API documentation](https://doxygen.gpac.io/modules.html) provides information on the GPAC Filter API. ++ ++GPAC's core is writen in C, but it can be easily extended using [Javascipt Filters](/Developers/javascript), used in a [Python](/Howtos/python) or [NodeJS application](/Developers/javascript). ++ ++ ++## Tutorials ++ ++- [Intro to Filter Session](/Developers/tutorials/filter-session-intro) ++- [Writing a custom Filter](/Developers/tutorials/custom-filter) ++ ++ ++## Building ++ ++Detailed build [Build](/Build/Build-Introduction) instructions for MP4Box and GPAC on all supported platforms. ++ ++ ++## Testing ++ ++Learn how to [build and run GPAC's test suite](/Build/tests/GPAC_tests). ++ ++The testsuite scripts is always a good place to understand GPAC tools usage. ++ ++ ++## Continuous integration ++ ++GPAC is continuously built and tested through a buildbot server: ++ ++* Build status of GPAC: buildbot.gpac.io ++* Tests status of GPAC: tests.gpac.io ++ ++ ++## Archives + +-* current build status of GPAC: buildbot.gpac.io +-* current test suite status of GPAC: tests.gpac.io +-* C API documentation: doxygen.gpac.io +-* the testsuite scripts might give you additional info on how to use GPAC tools + * tips and tricks in our [github discussions](https://github.com/gpac/gpac/issues?utf8=%E2%9C%93&q=) and our old [sourceforge forums](https://sourceforge.net/p/gpac/discussion/) +diff --git a/docs/Developers/javascript.md b/docs/Developers/javascript.md +new file mode 100644 +index 00000000..21050662 +--- /dev/null ++++ b/docs/Developers/javascript.md +@@ -0,0 +1,16 @@ ++ ++Javascript developers have two options to work with GPAC, the most appropriate solution depends on the project's goal: ++ ++## Javascript Filters ++ ++The JS filter API makes it easy to **extend gpac** using the internal QuickJS runtime, giving access to the Filter API for frame and packet processing, but also APIs for adaptative streaming, compositing, storage, ... ++ ++Some of the gpac built-in filters - eg. [avgen](Filters/avgen), [avmix](Filters/avmix) - are actualy implemented as custom javascript filters. Their source code can be found under the[`share/scripts/jsf`](https://github.com/gpac/gpac/tree/master/share/scripts/jsf) directory. ++ ++[JSF documentation](/Howtos/jsf/jsfilter){ .md-button } ++ ++## NodeJS ++ ++GPAC's NodeJS bindings allow **writing custom NodeJS applications**. It differs slightly from the Javascript Filters API available in the QuickJS runtime. ++ ++[NodeJS documentation](/Howtos/nodejs){ .md-button } +diff --git a/docs/Developers/tutorials/GPAC-concepts.md b/docs/Developers/tutorials/GPAC-concepts.md +new file mode 100644 +index 00000000..5b9a0b9a +--- /dev/null ++++ b/docs/Developers/tutorials/GPAC-concepts.md +@@ -0,0 +1,40 @@ ++ ++## What is GPAC ? ++ ++The **GPAC Filter API** is at the core of the [MP4Box and GPAC](Howtos/gpac-mp4box) applications. ++ ++The `gpac` application allows building media pipelines by conveniently [combining and configuring Filters](Filters/filters_general) from the command line. ++ ++Filters are configurable processing units consuming and producing data packets. ++ ++GPAC provides a wide range of Filters supporting advanced media protocols, formats, codecs, for input, output and processing tasks. ++ ++It defines the infrastructure and helper classes to develop applications with advanced media capabilities. ++ ++ ++### Concepts ++ ++**Filter Session** ++ ++- [API documentation](https://doxygen.gpac.io/group__fs__grp.html#details) ++- [tutorial](https://git.gpac-licensing.com/slarbi/API_FIlters_tutos/src/branch/master/T0_Filters_session/simple%20gpac%20session.md) ++ ++**Filter** ++- [API documentation](https://doxygen.gpac.io/group__fs__filter.html#details) ++ ++**Filter Properties** ++- [API documentation](https://doxygen.gpac.io/group__fs__props.html#details) ++- [Built in Properties](https://wiki.gpac.io/Filters/filters_properties/?h=properties) ++ ++**Filter Events** ++- [API documentation](https://doxygen.gpac.io/group__fs__evt.html#details) ++ ++**Filter PIDs & Capabilities** ++- [API documentation](https://doxygen.gpac.io/group__fs__pid.html) ++ ++**Filter Packet** ++- [API documentation](https://doxygen.gpac.io/group__fs__pck.html#details) ++ ++**Custom Filter** ++- [doxygen](https://doxygen.gpac.io/group__filters____cust__grp.html#details) ++- [writing a custom Filter]() +diff --git a/docs/Developers/tutorials/custom-filter.md b/docs/Developers/tutorials/custom-filter.md +new file mode 100644 +index 00000000..7881d592 +--- /dev/null ++++ b/docs/Developers/tutorials/custom-filter.md +@@ -0,0 +1,383 @@ ++## Creating a custom GPAC filter ++ ++Custom filters are filters created by the app with no associated registry. Therefore there is no internal representation for the custom filter (No filter registry). So capabilities and different behaviors of the custom filter must be specified by the app with the helper callback functions (listed below). ++ ++The app is responsible for assigning capabilities to the filter, and setting callback functions. Each callback is optional, but a custom filter should at least have a process callback, and a configure_pid callback if not a source filter. ++ ++ ++ ++**Custom filter limitations:** ++ ++* Custom filters do not have any arguments exposed. ++* The filter cannot be used as source of filters loading a source filter graph dynamically, such as the dashin filter. ++* The filter cannot be used as destination of filters loading a destination filter graph dynamically, such as the dasher filter. ++* The filter cannot be cloned. ++ ++ ++## Callback functions ++ ++### [gf_fs_new_filter()](https://doxygen.gpac.io/group__filters____cust__grp.html#ga45d6cd5535614e43d6ebb77d8cb1a4bc) ++ ++This function is responsible for loading a custom filter into the specified filter session. It allows the application to create filters without associated registries, providing the flexibility to assign capabilities and set callback functions. The function parameters include the filter session, the name of the filter (optional), flags for filter registry, and an optional error code parameter. ++ ++### [gf_filter_push_caps()](https://doxygen.gpac.io/group__filters____cust__grp.html#ga878516ab46d5bfc96692a91f3ba6b124) ++ ++This function facilitates the addition of new capabilities to a custom filter. Parameters such as capability code, value, name, flags, and priority are specified to define the capabilities. This function returns an error if any issues occur during the process. ++ ++An alternative way is to push the caps to a specific PID within the filter callbacks function, typically inside the process callback , or the configure callback for non source filters. ++ ++### [gf_filter_set_process_ckb()](https://doxygen.gpac.io/group__filters____cust__grp.html#gad1f20440c6de9b009a4fde85d429ad39) ++ ++This function is employed to set the process callback function for a custom filter. Typically callback defines the processing logic of the filter. The function takes the target filter and the process callback as parameters. ++ ++This one callback is mandatory in order to use the custom filter. ++ ++### [gf_filter_set_configure_ckb()](https://doxygen.gpac.io/group__filters____cust__grp.html#gab21f4169d1de1a0876f8b6856301ff28) ++ ++For custom filters that are not source filters, the **gf_filter_set_configure_ckb()** function is utilized to set the **PID** configuration callback. The assigned function enables and manages the configuration of the PID(s) for the filter. ++ ++### [gf_filter_set_process_event_ckb()](https://doxygen.gpac.io/group__filters____cust__grp.html#gadefa24ff7508d8838334d381f7c9004c) ++ ++Set the process event callback for a custom filter. This callback handles events related to the filter. for example specifying what happens if a Play or a Stop event is received. ++ ++### [gf_filter_set_reconfigure_output_ckb()](https://doxygen.gpac.io/group__filters____cust__grp.html#gae22ecc90654524434483f204713989fb) ++ ++The reconfigure_output callback may be needed to reconfigure the output PID(s) of a filter during the execution of a filters session. ++ ++### [gf_filter_set_probe_data_cbk()](%28https://doxygen.gpac.io/group__filters____cust__grp.html#ga3b067ec9d2067d683ea5d3fdb4d32833%29) ++ ++Set the data prober function for a custom filter. ++ ++ ++## Pushing application data to a GPAC filter session using a custom filter ++ ++We saw previously that source filters generaly are file (or pipe/sockets/memory files) access objects. ++ ++But sometimes when integrating gpac with other pieces of software, the data may be available directly in the memory. so it can be beneficial to access data from memory within a gpac filters session. ++ ++The following is an example of a custom filter named “**mem_in**” which is source filter that provides a way to create a PID where the raw data is located in memory (ex audio/video frames located in memory). Allowing for the creation of a filter chain to process this data ( feeding the PID to other filters). ++ ++ ++### Defining the process callback function ++ ++We start by defining a process callback, which the main logic will execute whenever our custom filter is called for a process execution by the the filters session: ++ ++```C ++GF_Err mem_in_process_ckb(GF_Filter *filter) { ++ GF_Err gf_err = GF_OK; ++ GF_FilterPid *opid = NULL; ++ ++ opid = gf_filter_get_opid(filter, 0); ++ if (!opid) { ++ opid = gf_filter_pid_new(filter); ++ Properties properties[] = { ++ {.prop_4cc = GF_PROP_PID_CODECID, ++ .val = {.type = GF_PROP_UINT, .value.uint = GF_CODECID_AVC}, ++ .flags = GF_CAPS_INPUT}, ++ {0}, ++ }; ++ push_props(opid, properties); ++ } ++ ++ const u8 *data = NULL; ++ u32 data_size = 0; ++ u64 dts = 0, pts = 0; ++ MemInCtx *ctx = (MemInCtx *)gf_filter_get_rt_udta(filter); ++ if (!ctx) ++ return GF_BAD_PARAM; ++ ++ ctx->parent = (void *)ctx; ++ ctx->getData = &inputGetData; ++ if (!ctx->getData(ctx->parent, &data, &data_size, &dts, &pts)){ ++ gf_filter_pid_set_eos(opid); ++ return GF_EOS; ++ } ++ ++ if (!data) { ++ gf_filter_ask_rt_reschedule(filter, 1); ++ return GF_OK; ++ } ++ ++ GF_FilterPacket *pck =gf_filter_pck_new_shared(opid, data, data_size, mem_in_pck_destructor); ++ if (!pck) { ++ gf_err = GF_OUT_OF_MEM; ++ goto exit; ++ } ++ ++ gf_filter_pck_set_dts(pck, dts); ++ gf_filter_pck_set_cts(pck, pts); ++ gf_filter_pck_set_duration(pck, 1); ++ gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1); ++ gf_filter_pck_set_framing(pck, GF_TRUE, GF_TRUE); ++ gf_filter_pck_send(pck); ++ ++ exit: ++ return gf_err; ++} ++``` ++ ++**Code explanation** ++ ++First thing we declare a PID that will serve us as an output PID, than we add the properties to this PID. Here for example we are adding the property **GF_PROP_PID_CODECID** with the value **GF_CODECID_AVC** to indicate that w are sending an AVC frame through this PID. ++ ++```C ++opid = gf_filter_get_opid(filter, 0); ++if (!opid) { ++ opid = gf_filter_pid_new(filter); ++ Properties properties[] = { ++ {.prop_4cc = GF_PROP_PID_CODECID, ++ .val = {.type = GF_PROP_UINT, .value.uint = GF_CODECID_AVC}, ++ .flags = GF_CAPS_INPUT}, ++ {0}, ++}; ++ ++ push_props(opid, properties); ++} ++``` ++ ++The push props function is defined below: ++ ++```C ++static GF_Err push_props(GF_FilterPid *PID, Properties pid_props[]) ++{ ++GF_Err gf_err = GF_OK; ++int i = 0; ++while(pid_props[i].prop_4cc) { ++ if(pid_props[i].flags == GF_CAPS_INPUT) { ++ gf_filter_pid_set_property(PID, pid_props[i].prop_4cc, &pid_props[i].val); ++ } ++ i++; ++}} ++``` ++ ++We loop through the props that we want to add (defined here through a specific struct). And add them through the [gf_filter_pid_set_property](https://doxygen.gpac.io/group__fs__pid.html#gaa9d532d9ca4c10a19973bcbd5e8af4fd) function. ++ ++An alternative way is to use the [gf_filter_push_caps()](https://doxygen.gpac.io/group__filters____cust__grp.html#ga878516ab46d5bfc96692a91f3ba6b124) function. ++ ++Our output PID and its properties are now configured, Next order of business is to fetch the data from memory. We keep a reference to the internal data of the filter through the following struct. This allows us to pass references to the getData() and freeData() functions from the main program to the filters session internals, we also keep some metadata like the current decoding / presentation timestamps and the number of max frames we will process. ++ ++```C ++typedef struct { ++ void *parent; ++ Bool (*getData)(void *parent, const u8 **data, u32 *data_size, u64 *dts, ++ u64 *pts); ++ void (*freeData)(void *parent, const u8 *data); ++ int max_frames; ++ u64 dts; ++ u64 pts; ++} MemInCtx; ++``` ++ ++With that in mind , lets return to our mem_in process callback: ++ ++```C ++const u8 *data = NULL; ++u32 data_size = 0; ++u64 dts = 0, pts = 0; ++MemInCtx *ctx = (MemInCtx *)gf_filter_get_rt_udta(filter); ++if (!ctx) ++ return GF_BAD_PARAM; ++ctx->parent = (void *)ctx; ++ctx->getData = &inputGetData; ++if (!ctx->getData(ctx->parent, &data, &data_size, &dts, &pts)){ ++ gf_filter_pid_set_eos(opid); ++ return GF_EOS; ++} ++if (!data) { ++ gf_filter_ask_rt_reschedule(filter, 1); ++ return GF_OK; ++} ++``` ++ ++After initialisation of local variables, we use the [gf_filter_get_rt_udta()](https://doxygen.gpac.io/group__fs__filter.html#ga47b46ae728e700f983f53fdc069032f3) function to retrieve the user data that we set from the main function(see code bellow). This function is typically used by bindings and custom filters to share runtime data. ++ ++Now we can access the data in memory using the getData(), in case the function is not available we send an End Of Stream signal through our output pid. ++ ++If there is no data available we ask for rescheduling with [gf_filter_ask_rt_reschedule()](https://doxygen.gpac.io/group__fs__filter.html#ga36bb988aa964b3c6220aae11773d7c9e). ++ ++ ++Otherwise, we create a new packet to be shared, we set some packets properties and we send the packet upstream. ++ ++```C ++GF_FilterPacket *pck = gf_filter_pck_new_shared(opid, data, data_size, mem_in_pck_destructor); ++if(!pck) { gf_err = GF_OUT_OF_MEM; goto exit; } ++gf_filter_pck_set_dts(pck, dts); ++gf_filter_pck_set_cts(pck, pts); ++gf_filter_pck_set_duration(pck, 1); ++gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1); ++gf_filter_pck_set_framing(pck, GF_TRUE, GF_TRUE); ++gf_filter_pck_send(pck); ++``` ++ ++### Instantiation of the process callback ++ ++Once we defined our process callback we need to instantiate it and assign it to our filter. so the logic of the filter will be executed each time the filter process is called by the gpac filters session. ++ ++```C ++// create a new GF_filter pointer called src_filter ++GF_Filter *src_filter = gf_fs_new_filter(session, "mem_in", 0, &gf_err); ++// declare the mem_in process callback function if not defined in the same file ++GF_Err mem_in_process_ckb(GF_Filter * filter); ++// assign the callback to the filter ++gf_filter_set_process_ckb(src_filter, mem_in_process_ckb); ++``` ++ ++Our source filter is ready to be used by the filter session. ++ ++ ++## Example 2 - Getting data from a custom filter to an application ++ ++ ++Alternatively to using custom filters we can create a filter class and add it to the session using [gf_fs_add_filter_register()](https://doxygen.gpac.io/group__fs__grp.html#ga4ae302f59379de2544c17b374eae3516) ++The following is the definition of the mem_out filter register with simply two functions process() and configure_pid(). (This very minimalistic more options are possible ) ++ ++```C ++ GF_FilterRegister memOutRegister = { ++ .name = "mem_out", ++ .private_size = sizeof(MemOutCtx), ++ .process = mem_out_process, ++ .configure_pid = mem_out_configure_pid, ++ }; ++``` ++ ++The mem_out_process logic is also straight forward. ++ ++```C ++MemOutCtx *ctx = (MemOutCtx *)gf_filter_get_udta(filter); ++static GF_Err mem_out_process(GF_Filter *filter) { ++ if (!ctx) ++ return GF_BAD_PARAM; ++ ctx->pushData = &outputPushData; ++ ctx->pushDsi = &outputPushMetadata; ++ ++ GF_FilterPid *ipid = gf_filter_get_ipid(filter, 0); ++ ++ const GF_PropertyValue *prop = ++ gf_filter_pid_get_property(ipid, GF_PROP_PID_DECODER_CONFIG); ++ ++ if (prop && prop->value.data.ptr && prop->value.data.size) { ++ ctx->pushDsi(ctx->parent, prop->value.data.ptr, prop->value.data.size); ++ } ++ ++ GF_FilterPacket *pck = gf_filter_pid_get_packet(ipid); ++ if (pck) { ++ u32 data_size = 0; ++ u64 dts = gf_filter_pck_get_dts(pck); ++ u64 pts = gf_filter_pck_get_cts(pck); ++ const u8 *data = gf_filter_pck_get_data(pck, &data_size); ++ ++ ctx->pushData(ctx->parent, data, data_size, dts, pts); ++ gf_filter_pid_drop_packet(ipid); ++ } ++ ++ return GF_OK; ++} ++``` ++ ++we note here: ++ ++* The use of [gf_filter_get_ipid()](https://doxygen.gpac.io/group__fs__filter.html#ga03a9a73e8d044d7737b4dd9d74e23a79) to retrieve the input pid. ++* The use of [gf_filter_pid_get_property()](https://doxygen.gpac.io/group__fs__pid.html#ga1e28b43fba75976755ef3004edfe2a2a) to get the properties of the input pid. ++* The use of [gf_filter_pid_get_packet()](https://doxygen.gpac.io/group__fs__pid.html#gaf373afc8a944b4a5b20952e8a3121d2f) to retrieve the packet from the input pid. ++* The use of [gf_filter_pck_get_data()](https://doxygen.gpac.io/group__fs__pck.html#ga6727b4c6fa4a4366ccc23244c6191570) to retrieve the data from the packet of the input pid. ++ ++ ++### Registry and loading of the filter to the session ++ ++```C ++// register and load destination filter ++GF_Filter *dst_filter = NULL; ++const GF_FilterRegister *mem_out_register(GF_FilterSession *); ++gf_fs_add_filter_register(session, mem_out_register(session)); ++dst_filter = gf_fs_load_filter(session, "mem_out", &gf_err); ++``` ++ ++The memory output filter is now ready to be used inside the filters session. ++ ++## Building the filter session graph using the reframer filter ++ ++Here is an example of a filters session using both filters examples provided before that we build using two different approaches ( custom filter & class filter). sandwiching the reframer as an example of a filters chain. ++ ++```C ++int main() { ++ ++ GF_Err gf_err = GF_OK; ++ ++ GF_FilterSession *session = gf_fs_new_defaults(0u); ++ if (session == NULL) { ++ fprintf(stderr, "Failed to create GPAC session\n"); ++ goto exit; ++ } ++ ++ // adding custom input filter ++ GF_Filter *src_filter = gf_fs_new_filter(session, "mem_in", 0, &gf_err); ++ MemInCtx *ctxIn = gf_malloc(sizeof(MemInCtx)); ++ ctxIn->dts = 0; ++ ctxIn->pts = 0; ++ ctxIn->max_frames = 3; ++ gf_err = gf_filter_set_rt_udta(src_filter, (void *)ctxIn); ++ gf_err = gf_filter_set_process_ckb(src_filter, mem_in_process_ckb); ++ GF_Filter *reframer_filter = gf_fs_load_filter(session, "reframer", &gf_err); ++ if (gf_err != GF_OK) { ++ fprintf(stderr, "Failed to load filter reframer: %s \n", ++ gf_error_to_string(gf_err)); ++ goto exit; ++ } ++ gf_filter_set_source(reframer_filter, src_filter, NULL); ++ ++ // register and load destination filter ++ GF_Filter *dst_filter = NULL; ++ const GF_FilterRegister *mem_out_register(GF_FilterSession *); ++ gf_fs_add_filter_register(session, mem_out_register(session)); ++ dst_filter = gf_fs_load_filter(session, "mem_out", &gf_err); ++ if (gf_err != GF_OK) { ++ fprintf(stderr, "Failed to load filter mem_out: %s \n", ++ gf_error_to_string(gf_err)); ++ goto exit; ++ } ++ ++ // finalize graph connections ++ gf_filter_set_source(dst_filter, reframer_filter, NULL); ++ ++ // run ++ gf_filter_post_process_task(src_filter); ++ gf_fs_run(session); ++ ++ // error handling ++ if (gf_err >= GF_OK) { ++ gf_err = gf_fs_get_last_connect_error(session); ++ if (gf_err >= GF_OK) ++ gf_err = gf_fs_get_last_process_error(session); ++ } ++ ++ // print connections ++ gf_fs_print_debug_info(session, 0); ++ gf_fs_print_connections(session); ++ gf_fs_print_stats(session); ++ exit: ++ gf_fs_del(session); ++ session = NULL ++ ++ return gf_err == GF_OK ? EXIT_SUCCESS : EXIT_FAILURE; ++} ++``` ++ ++Here we note: ++ ++* The use of [gf_filter_set_rt_udta()](https://doxygen.gpac.io/group__fs__filter.html#gac2f040600796f000ac4189fda1c76bc0) to set our runtime data of the mem_in filter. ++* We post the mem_in process task to the session using the [gf_filter_post_process_task(src_filter)](https://doxygen.gpac.io/group__fs__filter.html#ga5806cdb70097f7d5d181884928870842) function. ++ ++## Execution report ++ ++If we define a static h264 frame to use it as a memory input ++ ++```C ++static const uint8_t h264_grey_frame_dsi[] = { ++ 0x01, 0x4d, 0x40, 0x0a, 0xff, 0xe1, 0x00, 0x15, 0x67, 0x4d, ++ 0x40, 0x0a, 0xe8, 0x8f, 0x42, 0x00, 0x00, 0x03, 0x00, 0x02, ++ 0x00, 0x00, 0x03, 0x00, 0x64, 0x1e, 0x24, 0x4a, 0x24, 0x01, ++ 0x00, 0x05, 0x68, 0xeb, 0xc3, 0xcb, 0x20 }; ++``` ++and we run our filters session that will result in the following filters graph being executed. ++ ++![custom filter execution](images/T1_img0.png) +diff --git a/docs/Developers/tutorials/filter-session-intro.md b/docs/Developers/tutorials/filter-session-intro.md +new file mode 100644 +index 00000000..2dd48c7c +--- /dev/null ++++ b/docs/Developers/tutorials/filter-session-intro.md +@@ -0,0 +1,229 @@ ++## About the gpac filters API ++ ++The API enables you to use GPAC capabilities in your own personal code. By calling any filter in gpac’s set of built-in filters, or even create your own personalized custom filters to interact within the media pipelines. This can be useful to interface with other pieces of software or create faster ways of executing some media processing workflows. ++ ++To learn more about general concepts refer to the general wiki page of [gpac](/Filters/filters_general). ++ ++ ++## Build and install the gpac library ++ ++Refer to the gpac [build documentation](/Build/Build-Introduction) provides detailed instructions for compiling GPAC on all supported platforms. ++ ++For linux systems, following the general build, you can use ++ `$ make install-lib` to install the necessary libraries and header files. It will also install a gpac.pc file for pkg-config. With it you can easily build projects that use the gpac library with something like: ++ `$ gcc -o example $(pkg-config --cflags gpac) example.c $(pkg-config --libs gpac)` ++ ++**_NOTE:_**: pkg-config needs to be installed on your machine. ++ ++## Creating a filter session ++ ++The GPAC filter session object allows building media pipelines using multiple sources and destinations and arbitrary filter chains. ++ ++The simplest way to create a session object is to use the gf_fs_new_defaults() function. ++ ++```C ++GF_FilterSession *session = gf_fs_new_defaults(0u); ++if (session == NULL) { ++ fprintf(stderr, "Failed to create GPAC session\n"); ++} ++``` ++ ++This function will create a new filter session, loading parameters from [gpac config](/Filters/core_config). This will also load all available filter registers not blacklisted. ++ ++More information on this function and alternatives can be found on the doxygen [libgpac documentation page](https://doxygen.gpac.io/group__fs__grp.html#gaa7570001b4d4c07ef8883b17d7ed12ca). ++ ++## Loading filters ++ ++### Loading a source filter ++ ++Filters can be processing block ex: (de-)multiplexers, de/encoders, media segmenters (for HTTP Adaptive Streaming), RTSP server. But also they can be file access objects either as a source filter or a destination filter, (eventually pipe and sockets too): ++ ++```C ++GF_Err gf_err = GF_OK; ++GF_Filter *src_filter = gf_fs_load_source(session, "logo.png", NULL, NULL, &gf_err); ++if (gf_err != GF_OK) ++{ ++ fprintf(stderr, "Failed to load filter: %s", gf_error_to_string(gf_err)); ++} ++``` ++ ++Alternatively to [gf_fs_load_source](https://doxygen.gpac.io/group__fs__grp.html#gafce8e6e28696bc68e863bd4153282f80) function we can use the more generic [gf_fs_load_filter](https://doxygen.gpac.io/group__fs__grp.html#ga962fa3063a69ef02e43f06abe14cfe65) and use the [Fin](/Filters/fin) filter (followed by its options with the syntax :opt=val) as follows: ++ ++```C ++GF_Filter *src_filter = gf_fs_load_filter(session, "fin:src=logo.png", &gf_err); ++``` ++ ++### Loading a filter ++ ++Filters are described through a [__gf_filter_register](https://doxygen.gpac.io/struct____gf__filter__register.html) structure. A set of built-in filters are available, and user-defined filters can be added or removed at runtime. ++ ++ ++ ++The filter session keeps an internal graph representation of all available filters and their possible input connections, which is used when resolving connections between filters. ++ ++ ++ ++The following code snippet provides an example to load the [reframer](/Filters/reframer) filter. ++ ++ ++```C ++GF_Filter *reframer_filter = gf_fs_load_filter(session, "reframer", &gf_err); ++if (gf_err != GF_OK) ++{ ++ fprintf(stderr, "Failed to load filter: %s", gf_error_to_string(gf_err)); ++} ++``` ++ ++options can be specified the same way as in the CLI of gpac, as stated with ‘fin’ above. Here is another example: ++ ++```C ++GF_Filter *reframer_filter = gf_fs_load_filter(session, "reframer:rt=on", &gf_err); ++``` ++ ++### Loading a destination filter ++ ++Loading a destination filter, exactly like loading a source filter mentioned above can be done in two different ways: ++ ++ ++ ++by using [gf_fs_load_destination()](https://doxygen.gpac.io/group__fs__grp.html#ga2fd8f1f59622bc781cc81aafee99ee7d) function : ++ ++ ++```C ++GF_Err gf_err = GF_OK; ++GF Filter *src_filter = gf_fs_load_destination(session, "logo_result.png", NULL, NULL, &gf_err); ++if (gf_err != GF_OK) ++{ ++ fprintf(stderr, "Failed to load filter: %s", gf_error_to_string(gf_err)); ++} ++``` ++ ++Or by using the gf_fs_load_filter and use the Fout filter (or any alternative output destinations pipes, sockets.. ) as follows: ++ ++```C ++//load destination filter ++GF_Filter *dst_filter = gf_fs_load_filter(session, "fout:dst=logo_result.png", &gf_err); ++if (gf_err != GF_OK) ++{ ++ fprintf(stderr, "Failed to load filter: %s", gf_error_to_string(gf_err)); ++} ++``` ++ ++## Connecting filters and creating a filter chain ++ ++The function [gf_filter_set_source()](https://doxygen.gpac.io/group__fs__filter.html#ga9d8a5da25284b1325d0210cc04a6a302) serves the purpose of explicitly assigning a source ID to a designated filter. Therefore it serves as an indicator for the graph resolver to link two filters. (it does not do the actual linking). ++ ++It is crucial to invoke this function before establishing a connection from the specified source filter. If the linked filter lacks an assigned ID, the function automatically generates a dynamic one in the format %08X, utilizing the memory address of the filter. ++ ++It is important to note that in multithreaded sessions, proper session locking is essential before the filter creation step and must be unlocked after calling this function. Failure to do so may result in graph resolution occurring before the gf_filter_set_source() is invoked. ++ ++The function accepts three parameters, including the target filter, the filter to link from (source filter), and any link extensions allowed in link syntax, providing flexibility in defining link properties. ( Pid name, properties, types of properties…) ++ ++ ++```C ++gf_filter_set_source(destination_filter, source_filter, NULL); ++``` ++ ++ ++## Disabling the graph resolver and connecting filters manually ++ ++The helper function [gf_fs_set_max_resolution_chain_length()](https://doxygen.gpac.io/group__fs__grp.html#gaef500f3cb6589e05c161d8a50ab20f12) sets the maximum length of a filter chain dynamically loaded to solve connection between two filters. Setting the value to 0 disables dynamic link resolution. You will have to specify the entire chain manually. ( using the above gf_filterset_source() function). ++ ++Note: this will not guarantee the linking, matching of capabilities between the two filters will still be evaluated. This can be very helpful in a testing/development scenario where one may need to disable the dynamic linking to solve compatibility issues between two filters. ++ ++```C ++gf_fs_set_max_resolution_chain_length(session, 0); ++``` ++ ++## Running the session and displaying stats ++ ++By default, the gpac filters session operates in a semi-blocking state. meaning whenever output PID buffers on a filter are all full, the filter is marked as blocked and not scheduled for processing. And whenever one output PID buffer is not full, the filter unblocks. ++ ++The function [gf_fs_run](https://doxygen.gpac.io/group__fs__grp.html#gafdef85e209aef33193e02f83ff5fcbab)() allows for executing the filter session. When the session is non-blocking, it processes tasks of the oldest scheduled filter, manages pending PID connections, and then returns. In the case of a blocking session, gf_fs_run() continues to run until the session concludes or is aborted. The function returns an error if any issues arise during execution, and the last errors can be retrieved using [gf_fs_get_last_connect_error](https://doxygen.gpac.io/group__fs__grp.html#ga026f96a009dd073700b7339fb3ade492) and [gf_fs_get_last_process_error](https://doxygen.gpac.io/group__fs__grp.html#ga2a217d0b7f3f44050f9f78cab10e577d). ++ ++ ++```C ++gf_err = gf_fs_run(session); ++ ++if (gf_err>=GF_OK) { ++ gf_err = gf_fs_get_last_connect_error(session); ++if (gf_err>=GF_OK) ++ gf_err = gf_fs_get_last_process_error(session); ++} ++ ++//print connections ++gf_fs_print_connections(session); ++gf_fs_print_stats(session); ++ ++gf_fs_del(session); ++session = NULL; ++``` ++ ++## Sample code ++ ++In the following example we reproduce a [testsuite example](https://github.com/gpac/testsuite/blob/master/scripts/reframers.sh) that takes a png image as input and calls the reframer filter on the png and writes a new image using writegen and fout filters. ++ ++[ (image file png) fin -> ] -> reframe -> writegen -> [ -> fout (image result file) ] ++ ++**_NOTE:_**: the reframer filter has no functionnnal use in this particular example. the example is just an illustartion of a filters chain. ++ ++```C ++int main(int argc, char *argv[]) ++{ ++ GF_Err gf_err = GF_OK; ++ ++ // session scheme for testing reframer with fin filter ++ GF_FilterSession *session = gf_fs_new_defaults(0u); ++ if (session == NULL) { ++ fprintf(stderr, "Failed to create GPAC session\n"); ++ return EXIT_FAILURE; ++ } ++ ++ // load source filter ++ GF_Filter * src_filter = gf_fs_load_filter(session, "fin:src=logo.png", &gf_err); ++ if (gf_err != GF_OK) ++ { ++ fprintf(stderr, "Failed to load filter: %s", gf_error_to_string(gf_err)); ++ } ++ //load reframer filter ++ GF_Filter *reframer_filter = gf_fs_load_filter(session, "reframer", &gf_err); ++ if (gf_err != GF_OK) ++ { ++ fprintf(stderr, "Failed to load filter: %s", gf_error_to_string(gf_err)); ++ } ++ //load writergen filter ++ GF_Filter *writegen_filter = gf_fs_load_filter(session, "writegen", &gf_err); ++ if (gf_err != GF_OK) ++ { ++ fprintf(stderr, "Failed to load filter: %s", gf_error_to_string(gf_err)); ++ } ++ ++ //load destination filter ++ GF_Filter *dst_filter = gf_fs_load_filter(session, "fout:dst=logo_result.png", &gf_err); ++ if (gf_err != GF_OK) ++ { ++ fprintf(stderr, "Failed to load filter: %s", gf_error_to_string(gf_err)); ++ } ++ ++ gf_filter_set_source(reframer_filter, src_filter, NULL); ++ gf_filter_set_source(writegen_filter,reframer_filter,NULL); ++ gf_filter_set_source(dst_filter ,writegen_filter,NULL); ++ ++ gf_err = gf_fs_run(session); ++ ++ if (gf_err>=GF_OK) ++ { ++ gf_err = gf_fs_get_last_connect_error(session); ++ if (gf_err>=GF_OK) ++ gf_err = gf_fs_get_last_process_error(session); ++ } ++ ++ //print connections ++ gf_fs_print_connections(session); ++ gf_fs_print_stats(session); ++ ++ gf_fs_del(session); ++ session = NULL; ++ return EXIT_SUCCESS; ++} ++``` +diff --git a/docs/Developers/tutorials/images/T1_img0.png b/docs/Developers/tutorials/images/T1_img0.png +new file mode 100644 +index 00000000..edbacfd0 +Binary files /dev/null and b/docs/Developers/tutorials/images/T1_img0.png differ +diff --git a/docs/Filters/Filters.md b/docs/Filters/Filters.md +index 98e1c0e1..0b414689 100644 +--- a/docs/Filters/Filters.md ++++ b/docs/Filters/Filters.md +@@ -1,4 +1,5 @@ +-# Overview ++ ++# Overview {:data-level="all"} + + This part of the wiki describes general concepts of the GPAC filter architecture, available starting from GPAC 0.9.0. + +diff --git a/docs/Filters/Rearchitecture.md b/docs/Filters/Rearchitecture.md +index 78d52c1d..e3bda6d5 100644 +--- a/docs/Filters/Rearchitecture.md ++++ b/docs/Filters/Rearchitecture.md +@@ -1,4 +1,4 @@ +-# Overview ++# Overview {:data-level="all"} + + For version 0.9.0, GPAC has undergone a major re-architecture of its core, the first one in 15 years! + The re-architecture was done with the following goals: +diff --git a/docs/Filters/a52dec.md b/docs/Filters/a52dec.md +index 96a078da..9615a76f 100644 +--- a/docs/Filters/a52dec.md ++++ b/docs/Filters/a52dec.md +@@ -1,6 +1,6 @@ + + +-# A52 decoder ++# A52 decoder {:data-level="all"} + + Register name used to load filter: __a52dec__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/aout.md b/docs/Filters/aout.md +index 3e27748f..500c5fe8 100644 +--- a/docs/Filters/aout.md ++++ b/docs/Filters/aout.md +@@ -1,11 +1,11 @@ + + +-# Audio output ++# Audio output {:data-level="all"} + + Register name used to load filter: __aout__ + This filter may be automatically loaded during graph resolution. + +-This filter writes a single uncompressed audio input PID to a sound card or other audio output device. ++This filter writes a single PCM (uncompressed) audio input PID to a sound card or other audio output device. + + The longer the audio buffering [bdur](#bdur) is, the longer the audio latency will be (pause/resume). The quality of fast forward audio playback will also be degraded when using large audio buffers. + +diff --git a/docs/Filters/avgen.md b/docs/Filters/avgen.md +index ef512eba..29f83acc 100644 +--- a/docs/Filters/avgen.md ++++ b/docs/Filters/avgen.md +@@ -1,9 +1,9 @@ + + +-# AV Counter Generator ++# AV Counter Generator {:data-level="all"} + + Register name used to load filter: __avgen__ +-This is a JavaScript filter, not checked during graph resolution and needs explicit loading. ++This is a JavaScript filter. It is not checked during graph resolution and needs explicit loading. + Author: GPAC Team + + This filter generates AV streams representing a counter. Streams can be enabled or disabled using [type](#type). +@@ -11,7 +11,7 @@ The filter is software-based and does not use GPU. + + When [adjust](#adjust) is set, the first video frame is adjusted such that a full circle happens at each exact second according to the system UTC clock. + By default, video UTC and date are computed at each frame generation from current clock and not from frame number. +-This will result in broken timing when playing at speeds other than 1.0. ++This will result in broken UTC timing text when playing at speeds other than 1.0. + This can be changed using [lock](#lock). + + Audio beep is generated every second, with octave (2xfreq) of even beep used every 10 seconds. +@@ -47,9 +47,10 @@ If multiple [views](#views) are generated, they are assigned the names `videoN_v + # Options + + __type__ (enum, default: _av_): output selection +-* a: audio only +-* v: video only +-* av: audio and video ++ ++- a: audio only ++- v: video only ++- av: audio and video + + __freq__ (uint, default: _440_): frequency of beep + __freq2__ (uint, default: _659_): frequency of odd beep +@@ -68,9 +69,10 @@ If multiple [views](#views) are generated, they are assigned the names `videoN_v + __dur__ (frac, default: _0/0_): run for the given time in second + __adjust__ (bool, default: _true_): adjust start time to synchronize counter and UTC + __pack__ (enum, default: _no_): packing mode for stereo views +- * no: no packing +- * ss: side by side packing, forces [views](#views) to 2 +- * tb: top-bottom packing, forces [views](#views) to 2 ++ ++- no: no packing ++- ss: side by side packing, forces [views](#views) to 2 ++- tb: top-bottom packing, forces [views](#views) to 2 + + __disparity__ (uint, default: _20_): disparity in pixels between left-most and right-most views + __views__ (uint, default: _1_): number of views +diff --git a/docs/Filters/avidmx.md b/docs/Filters/avidmx.md +index e88564a3..00983807 100644 +--- a/docs/Filters/avidmx.md ++++ b/docs/Filters/avidmx.md +@@ -1,6 +1,6 @@ + + +-# AVI demultiplexer ++# AVI demultiplexer {:data-level="all"} + + Register name used to load filter: __avidmx__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/avimx.md b/docs/Filters/avimx.md +index bd5f2fd3..02a46e4d 100644 +--- a/docs/Filters/avimx.md ++++ b/docs/Filters/avimx.md +@@ -1,6 +1,6 @@ + + +-# AVI multiplexer ++# AVI multiplexer {:data-level="all"} + + Register name used to load filter: __avimx__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/avmix.md b/docs/Filters/avmix.md +index cd373293..1cf280b3 100644 +--- a/docs/Filters/avmix.md ++++ b/docs/Filters/avmix.md +@@ -1,21 +1,25 @@ + + +-# Audio Video Mixer ++# Audio Video Mixer {:data-level="all"} + + Register name used to load filter: __avmix__ +-This is a JavaScript filter, not checked during graph resolution and needs explicit loading. ++This is a JavaScript filter. It is not checked during graph resolution and needs explicit loading. + Author: GPAC team + + AVMix is an audio video mixer controlled by an updatable JSON playlist format. The filter can be used to: ++ + - schedule video sequence(s) over time + - mix videos together + - layout of multiple videos + - overlay images, text and graphics over source videos ++ + + All input streams are decoded prior to entering the mixer. ++ + - audio streams are mixed in software + - video streams are composed according to the `gpu` option + - other stream types are not yet supported ++ + + OpenGL hardware acceleration can be used, but the supported feature set is currently not the same with or without GPU. + +@@ -31,30 +35,36 @@ This implies that there shall always be a media to compose, i.e. no "holes" in t + _Note: The playlist is still refreshed in offline mode._ + + When operating live, the mixer will initially wait for video frames to be ready for `lwait` seconds. After this initial timeout, the output frames will indicate: ++ + - 'No signal' if no input is available (no source frames) or no scene is defined + - 'Signal lost' if no new input data has been received for `lwait` on a source ++ + + # Playlist Format + + ## Overview + The main components in a playlist are: +-* Media sources and sequences: each source is described by one or more URL to the media data, and each sequence is a set of sources to be played continuously +-* Transitions: sources in a sequence can be combined using transitions +-* Scenes: a scene describes one graphical object to put on screen and if and how input video are mapped on objects +-* Groups: a group is a hierarchy of scenes and groups with positioning properties, and can also be used to create offscreen images reused by other elements +-* Timers: a timer can be used to animate scene parameters in various fashions ++ ++- Media sources and sequences: each source is described by one or more URL to the media data, and each sequence is a set of sources to be played continuously ++- Transitions: sources in a sequence can be combined using transitions ++- Scenes: a scene describes one graphical object to put on screen and if and how input video are mapped on objects ++- Groups: a group is a hierarchy of scenes and groups with positioning properties, and can also be used to create offscreen images reused by other elements ++- Timers: a timer can be used to animate scene parameters in various fashions ++ + + The playlist content shall be either a single JSON object or an array of JSON objects, hereafter called root objects. + Root objects types can be indicated through a `type` property: +-* seq: a `sequence` object +-* url: a `source` object (if used as root, a default `sequence` object will be created) +-* scene: a `scene` object +-* group: a `group` object +-* timer: a `timer` object +-* script: a `script` object +-* config: a `config` object +-* watch: a `watcher` object +-* style: a `style` object ++ ++- seq: a `sequence` object ++- url: a `source` object (if used as root, a default `sequence` object will be created) ++- scene: a `scene` object ++- group: a `group` object ++- timer: a `timer` object ++- script: a `script` object ++- config: a `config` object ++- watch: a `watcher` object ++- style: a `style` object ++ + + Except for `style`, the `type` property of root objects is usually not needed as the parser guesses the object types from its properties. + +@@ -65,11 +75,13 @@ Any unrecognized property not starting with `_` will be reported as warning. + + ## Colors + Colors are handled as strings, formatted as: ++ + - the DOM color name (see `gpac -h colors`) + - HTML codes `$RRGGBB` or `#RRGGBB` + - RGB hex vales `0xRRGGBB` + - RGBA hex values `0xAARRGGBB` + - the color `none` is `0x00000000`, its signification depends on the object using it. ++ + + If JS code needs to manipulate colors, use sys.color_lerp and sys.color_component functions. + +@@ -82,37 +94,43 @@ The `JSFun` arguments and return value are dependent on the parent object type. + The parent object is exposed as `this` in `JSFun` and can be used to store context information for the JS code. + + The code can use the global functions and modules defined, especially: +-* sys: GPAC system module +-* evg: GPAC EVG module +-* os: QuickJS OS module +-* video_playing: video playing state +-* audio_playing: audio playing state +-* video_time: output video time +-* video_timescale: output video timescale +-* video_width: output video width +-* video_height: output video height +-* audio_time: output audio time +-* audio_timescale: output audio timescale +-* samplerate: output audio samplerate +-* channels: output audio channels +-* current_utc_clock: current UTC clock in ms +-* get_media_time: gets media time of output (no argument) or of source with id matching the first argument. Return +- * -4: not found +- * -3: not playing +- * -2: in prefetch +- * -1: timing not yet known +- * value: media time in seconds (float) +-* resolve_url: resolves URL given in first argument against media playlist URL and returns the resolved url (string) +-* get_scene(id): gets scene with given ID +-* get_group(id): gets group with given ID +-* mouse_over(evt): returns scene under mouse described by a GPAC event, or null if no scene (picking for scenes with perspective projection is not supported) +-* mouse_over(x, y): returns scene under coordinates {x, y} in pixels, {0,0} representing the center of the frame, x axis oriented towards the right and y axis oriented towards the top ++ ++- sys: GPAC system module ++- evg: GPAC EVG module ++- os: QuickJS OS module ++- video_playing: video playing state ++- audio_playing: audio playing state ++- video_time: output video time ++- video_timescale: output video timescale ++- video_width: output video width ++- video_height: output video height ++- audio_time: output audio time ++- audio_timescale: output audio timescale ++- samplerate: output audio samplerate ++- channels: output audio channels ++- current_utc_clock: current UTC clock in ms ++- get_media_time: gets media time of output (no argument) or of source with id matching the first argument. Return ++ ++ - -4: not found ++ - -3: not playing ++ - -2: in prefetch ++ - -1: timing not yet known ++ - value: media time in seconds (float) ++ ++- resolve_url: resolves URL given in first argument against media playlist URL and returns the resolved url (string) ++- get_scene(id): gets scene with given ID ++- get_group(id): gets group with given ID ++- mouse_over(evt): returns scene under mouse described by a GPAC event, or null if no scene (picking for scenes with perspective projection is not supported) ++- mouse_over(x, y): returns scene under coordinates {x, y} in pixels, {0,0} representing the center of the frame, x axis oriented towards the right and y axis oriented towards the top ++ + + Scene and group options must be accessed through getters and setters: +-* scene.get(prop_name): gets the scene option +-* scene.set(prop_name, value): sets the scene option +-* group.get(prop_name): gets the group option +-* group.set(prop_name, value): sets the group option ++ ++- scene.get(prop_name): gets the scene option ++- scene.set(prop_name, value): sets the scene option ++- group.get(prop_name): gets the group option ++- group.set(prop_name, value): sets the group option ++ + + __Warning: Results are undefined if JS code modifies the scene/group objects in any other way.__ + +@@ -121,23 +139,27 @@ Other playlist objects (as well as scene and group objects) can be queried using + __Warning: There is no protection of global variables and state, write your script carefully!__ + + Additionally, scripts executed within scene modules can modify the internal playlist using: +-* remove_element(ID): removes a scene, group, sequence, timer, script or watcher with given ID from playlist +-* parse_element(JSON): parses a root playlist element and add it to the current playlist +-* parse_scene(JSON, parent): parses a scene and add it to `parent` group if not null or root otherwise +-* parse_group(JSON, parent): parses a group and add it to `parent` group if not null or root otherwise +-* reload_playlist(JSON): parses a new playlist (an empty JSON array will reset the playlist). If the calling scene is no longer in the resulting scene tree, it will be added to the root of the scene tree. ++ ++- remove_element(ID): removes a scene, group, sequence, timer, script or watcher with given ID from playlist ++- parse_element(JSON): parses a root playlist element and add it to the current playlist ++- parse_scene(JSON, parent): parses a scene and add it to `parent` group if not null or root otherwise ++- parse_group(JSON, parent): parses a group and add it to `parent` group if not null or root otherwise ++- reload_playlist(JSON): parses a new playlist (an empty JSON array will reset the playlist). If the calling scene is no longer in the resulting scene tree, it will be added to the root of the scene tree. ++ + + All these playlist-related functions must be called within the update() callback of the scene module. + + ## Sequences + ### Properties for `sequence` objects: + +- * id (null): sequence identifier +- * loop (0): number of loops for the sequence (0 means no loop, -1 will loop forever) +- * start (0): sequence start time (see notes). If negative, the sequence is not active +- * stop (0): sequence stop time (see notes). If less than `start`, the sequence will stop only when over +- * transition (null): a `transition` object to apply between sources of the sequence +- * seq ([]): array of one or more `source` objects ++ ++ - id (null): sequence identifier ++ - loop (0): number of loops for the sequence (0 means no loop, -1 will loop forever) ++ - start (0): sequence start time (see notes). If negative, the sequence is not active ++ - stop (0): sequence stop time (see notes). If less than `start`, the sequence will stop only when over ++ - transition (null): a `transition` object to apply between sources of the sequence ++ - seq ([]): array of one or more `source` objects ++ + + ### Notes + +@@ -145,50 +167,62 @@ Media source timing does not depend on the media being used by a scene or not, i + This means that a `sequence` not used by any active scene will not be rendered (video nor audio). + + The syntax for `start` and `stop` fields is: +-* `now`: resolves to current UTC clock in `live` mode, and to 0 for non-live mode +-* date: converted to UTC date in `live` mode, and to 0 for non-live mode +-* N: converted to current utc clock (or 0 for non-live mode) plus N seconds UTC +-* "N": converted to current utc clock (or 0 for non-live mode) plus N seconds UTC ++ ++- `now`: resolves to current UTC clock in `live` mode, and to 0 for non-live mode ++- date: converted to UTC date in `live` mode, and to 0 for non-live mode ++- N: converted to current utc clock (or 0 for non-live mode) plus N seconds UTC ++- "N": converted to current utc clock (or 0 for non-live mode) plus N seconds UTC ++ + + In 'live' mode, if `start` is set using a UTC date, the sequence will have a start range equal to `MAX(current_UTC - start_in_UTC, 0)`. Some sources may be skipped to fulfill this condition. + This allows different instances of the filter using the same playlist to initialize media time in the same fashion. + + When reloading the playlist: ++ + - if the sequence is active, `start` value is ignored + - if the sequence was not started, `start` value is updated + - if the sequence was over, `start` value is updated only of greater than previous resolved UTC start time. ++ + + ## Sources + ### Properties for `source` objects + +-* id (null): source identifier, used when reloading the playlist +-* src ([]): list of `sourceURL` describing the URLs to play. Multiple sources will be played in parallel +-* start (0.0): media start time in source +-* stop (0.0): media stop time in source, ignored if less than or equal to `start` +-* mix (true): if true, apply sequence transition or mix effect ratio as audio volume. Otherwise volume is not modified by transitions. +-* fade ('inout'): indicate how audio should be faded at stream start/end: +- * in: audio fade-in when playing first frame +- * out: audio fade-out when playing last frame +- * inout: both fade-in and fade-out are enabled +- * other: no audio fade +-* keep_alive (false): if using a dedicated gpac process for one or more input, relaunch process(es) at source end if exit code is greater than 2 or if not responding after `rtimeout` +-* seek (false): if true and `keep_alive` is active, adjust `start` according to the time elapsed since source start when relaunching process(es) +-* prefetch (500): prefetch duration in ms (play before start time of source), 0 for no prefetch +-* hold (false): if media duration is known and media stop time is greater than media duration, activate no signal mode until desired stop time is reached (disable transition), otherwise move to next source at end of stream ++ ++- id (null): source identifier, used when reloading the playlist ++- src ([]): list of `sourceURL` describing the URLs to play. Multiple sources will be played in parallel ++- start (0.0): media start time in source ++- stop (0.0): media stop time in source, ignored if less than or equal to `start` ++- mix (true): if true, apply sequence transition or mix effect ratio as audio volume. Otherwise volume is not modified by transitions. ++- fade ('inout'): indicate how audio should be faded at stream start/end: ++ ++ - in: audio fade-in when playing first frame ++ - out: audio fade-out when playing last frame ++ - inout: both fade-in and fade-out are enabled ++ - other: no audio fade ++ ++- keep_alive (false): if using a dedicated gpac process for one or more input, relaunch process(es) at source end if exit code is greater than 2 or if not responding after `rtimeout` ++- seek (false): if true and `keep_alive` is active, adjust `start` according to the time elapsed since source start when relaunching process(es) ++- prefetch (500): prefetch duration in ms (play before start time of source), 0 for no prefetch ++- hold (false): if media duration is known and media stop time is greater than media duration, activate no signal mode until desired stop time is reached (disable transition), otherwise move to next source at end of stream ++ + + ## Source Locations + ### Properties for `sourceURL` objects + +-* id (null): source URL identifier, used when reloading the playlist +-* in (null): input URL or filter chain to load as string. Words starting with `-` are ignored. The first entry must specify a source URL, and additional filters and links can be specified using `@N[#LINKOPT]` and `@@N[#LINKOPT]` syntax, as in gpac +-* port (null): input port for source. Possible values are: +- * pipe: launch a gpac process to play the source using GSF format over pipe +- * tcp, tcpu: launch a gpac process to play the source using GSF format over TCP socket (`tcp`) or unix domain TCP socket (`tcpu`) +- * not specified or empty string: loads source using the current process +- * other: use value as input filter declaration and launch `in` as a dedicated process (e.g. `in="ffmpeg ..." port="pipe://..."`) +-* opts (null): options for the gpac process instance when using a dedicated gpac process, ignored otherwise +-* media ('all'): filter input media by type, `a` for audio, `v` for video, `t` for text (several characters allowed, e.g. `av` or `va`), `all` accept all input media +-* raw (true): indicate if input port is decoded AV (true) or compressed AV (false) when using a dedicated gpac process, ignored otherwise ++ ++- id (null): source URL identifier, used when reloading the playlist ++- in (null): input URL or filter chain to load as string. Words starting with `-` are ignored. The first entry must specify a source URL, and additional filters and links can be specified using `@N[#LINKOPT]` and `@@N[#LINKOPT]` syntax, as in gpac ++- port (null): input port for source. Possible values are: ++ ++ - pipe: launch a gpac process to play the source using GSF format over pipe ++ - tcp, tcpu: launch a gpac process to play the source using GSF format over TCP socket (`tcp`) or unix domain TCP socket (`tcpu`) ++ - not specified or empty string: loads source using the current process ++ - other: use value as input filter declaration and launch `in` as a dedicated process (e.g. `in="ffmpeg ..." port="pipe://..."`) ++ ++- opts (null): options for the gpac process instance when using a dedicated gpac process, ignored otherwise ++- media ('all'): filter input media by type, `a` for audio, `v` for video, `t` for text (several characters allowed, e.g. `av` or `va`), `all` accept all input media ++- raw (true): indicate if input port is decoded AV (true) or compressed AV (false) when using a dedicated gpac process, ignored otherwise ++ + + ### Notes + +@@ -197,12 +231,14 @@ Example + ``` + in=ipid://#foo=bar + ``` ++ + This will use pids having property `foo` with value `bar`, regardless of source filter ID. + + Example + ``` + in=ipid://TEST#foo=bar + ``` ++ + This will use pids having property `foo` with value `bar` coming from filter with ID `TEST`. + + When using the `ipid://` scheme, filter chains cannot be specified (in accepts a single argument) and `port` is ignored. +@@ -215,83 +251,101 @@ __Warning: When launching a child process directly (e.g. `in="ffmpeg ..."`), any + ## 2D and 3D transformation + ### Common properties for `group` and `scene` objects + +-* active (true): indicate if the object is active or not. An inactive object will not be refreshed nor rendered +-* x (0): horizontal translation +-* y (0): vertical translation +-* cx (0): horizontal coordinate of rotation center +-* cy (0): vertical coordinate of rotation center +-* units ('rel'): unit type for `x`, `y`, `cx`, `cy`, `width` and `height`. Possible values are: +- * rel: units are expressed in percent of current reference (see below) +- * pix: units are expressed in pixels +-* rotation (0): rotation angle of the scene in degrees +-* hscale (1): horizontal scaling factor to apply to the group +-* vscale (1): vertical skewing factor to apply to the scene +-* hskew (0): horizontal skewing factor to apply to the scene +-* vskew (0): vertical skewing factor to apply to the scene +-* zorder (0): display order of the scene or of the offscreen group (ignored for regular groups) +-* untransform (false): if true, reset parent tree matrix to identity before computing matrix +-* mxjs (null): JS code for matrix evaluation +-* z (0): depth translation +-* cz (0): depth coordinate of rotation center +-* zscale (1): depth scaling factor to apply to the group +-* orientation ([0, 0, 1, 0]): scale along the given orientation axis [x, y, z, angle] - see VRML `scaleOrientation` +-* axis ([0, 0, 1]): rotation axis +-* position ([0, 0, auto]): camera location +-* target ([0, 0, 0]): point where the camera is looking +-* up ([0, 1, 0]): camera up vector +-* viewport ([0, 0, 100, 100]): viewport for camera +-* fov (45): field of view in degrees +-* ar (0): camera aspect ratio, 0 means default +-* znear (0): near Z plane distance, 0 means default +-* zfar (0): far Z plane distance, 0 means default ++ ++- active (true): indicate if the object is active or not. An inactive object will not be refreshed nor rendered ++- x (0): horizontal translation ++- y (0): vertical translation ++- cx (0): horizontal coordinate of rotation center ++- cy (0): vertical coordinate of rotation center ++- units ('rel'): unit type for `x`, `y`, `cx`, `cy`, `width` and `height`. Possible values are: ++ ++ - rel: units are expressed in percent of current reference (see below) ++ - pix: units are expressed in pixels ++ ++- rotation (0): rotation angle of the scene in degrees ++- hscale (1): horizontal scaling factor to apply to the group ++- vscale (1): vertical skewing factor to apply to the scene ++- hskew (0): horizontal skewing factor to apply to the scene ++- vskew (0): vertical skewing factor to apply to the scene ++- zorder (0): display order of the scene or of the offscreen group (ignored for regular groups) ++- untransform (false): if true, reset parent tree matrix to identity before computing matrix ++- mxjs (null): JS code for matrix evaluation ++- z (0): depth translation ++- cz (0): depth coordinate of rotation center ++- zscale (1): depth scaling factor to apply to the group ++- orientation ([0, 0, 1, 0]): scale along the given orientation axis [x, y, z, angle] - see VRML `scaleOrientation` ++- axis ([0, 0, 1]): rotation axis ++- position ([0, 0, auto]): camera location ++- target ([0, 0, 0]): point where the camera is looking ++- up ([0, 1, 0]): camera up vector ++- viewport ([0, 0, 100, 100]): viewport for camera ++- fov (45): field of view in degrees ++- ar (0): camera aspect ratio, 0 means default ++- znear (0): near Z plane distance, 0 means default ++- zfar (0): far Z plane distance, 0 means default ++ + + ### Coordinate System + + Each group or scene is specified in a local coordinate system for which: ++ + - {0,0} represents the center + - X values increase to the right + - Y values increase to the top + - Z values increase towards the eye of a viewer (Z=X^Y) ++ + + The 2D local transformation matrix is computed as `rotate(cx, cy, rotation)` * `hskew` * `vskew` * `scale(hscale, vscale)` * `translate(x, y)`. + The 3D local transformation matrix is computed as `translate(x, y, z)` * `rotate(cx, cy, cz, rotation)` * `scale(hscale, vscale, zscale)`. Skewing is not supported for 3D. + + The default unit system (`rel`) is relative to the current established reference space: ++ + - by default, the reference space is `{output_width, output_height}`, the origin {0,0} being the center of the output frame + - any group with `reference=true`, `width>0` and `height>0` establishes a new reference space `{group.width, group.height}` ++ + + Inside a reference space `R`, relative coordinates are interpreted as follows: ++ + - For horizontal coordinates, 0 means center, -50 means left edge (`-R.width/2`), 50 means right edge (`+R.width/2`). + - For vertical coordinates, 0 means center, -50 means bottom edge (`-R.height/2`), 50 means top edge (`+R.height/2`). + - For `width`, 100 means `R.width`. + - For `height`, 100 means `R.height`. + - For depth (z and cz) coordinates, the value is a percent of the reference height (`+R.height`). ++ + + If `width=height`, the width is set to the computed height of the object. + If `height=width`, the height is set to the computed width of the object. + For `x` property, the following special values are defined: ++ + - `y` will set the value to the computed `y` of the object. + - `-y` will set the value to the computed `-y` of the object. ++ + For `y` property, the following special values are defined: ++ + - `x` will set the value to the computed `x` of the object. + - `-x` will set the value to the computed `-x` of the object. ++ + + Changing reference is typically needed when creating offscreen groups, so that children relative coordinates are resolved against the offscreen canvas size. + + The selection between 2D and 3D is done automatically based on `z`, `cz`, `axis` and `orientation` values. + The default projection is: ++ + - viewport is the entire output frame + - field of view is PI/4 and aspect ratio is output width/height + - zNear is 0.1 and zFar is 10 times maximum(output width, output height) + - camera up direction is Y axis and camera distance is so that a rectangle facing the camera with `z=0` and size equal to output size covers exactly the output frame. + - depth buffer is disabled ++ + + The default projection can be changed by setting camera properties at group or scene level. When set on a group, all children of the group will use the given camera properties (camera parameters on children are ignored). + The `viewport` parameter is specified as an array `[x, y, w, h]`, where: +-* x: horizontal coordinate of the viewport center, in group or scene units, or 'y' to use `y` value, or '-y' to use -`y` value. +-* y: vertical coordinate of the viewport center, in group or scene units, or 'x' to use `x` value, or '-x' to use -`x` value. +-* w: width of the viewport, in group or scene units, or 'height' to use `h` value. +-* h: height of the viewport, in group or scene units, or 'width' to use `w` value. ++ ++- x: horizontal coordinate of the viewport center, in group or scene units, or 'y' to use `y` value, or '-y' to use -`y` value. ++- y: vertical coordinate of the viewport center, in group or scene units, or 'x' to use `x` value, or '-x' to use -`x` value. ++- w: width of the viewport, in group or scene units, or 'height' to use `h` value. ++- h: height of the viewport, in group or scene units, or 'width' to use `w` value. ++ + + ### z-ordering + +@@ -303,33 +357,42 @@ This order is independent of the parent group z-ordering. This allows moving obj + The `JSFun` specified in `mxjs` has a single parameter `tr`. + + The `tr` parameter is an object containing the following variables that the code can modify: +-* x, y, z, cx, cy, cz, hscale, vscale, zscale, hskew, vskew, rotation, untransform, axis, orientation: these values are initialized to the current group values in local coordinate system units +-* update: if set to true, the object matrix will be recomputed at each frame even if no change in the group or scene parameters (always enforced to true if `use` is set) +-* depth: for groups with `use`, indicates the recursion level of the used element. A value of 0 indicates this is a direct render of the element, otherwise it is a render through `use` ++ ++- x, y, z, cx, cy, cz, hscale, vscale, zscale, hskew, vskew, rotation, untransform, axis, orientation: these values are initialized to the current group values in local coordinate system units ++- update: if set to true, the object matrix will be recomputed at each frame even if no change in the group or scene parameters (always enforced to true if `use` is set) ++- depth: for groups with `use`, indicates the recursion level of the used element. A value of 0 indicates this is a direct render of the element, otherwise it is a render through `use` ++ + + The `JSFun` may return false to indicate that the scene should be considered as inactive. Any other return value (undefined or not false) will mark the scene as active. + +-EX: "mxjs": "tr.rotation = (get_media_time() % 8) * 360 / 8; tr.update=true;" ++Example ++``` ++"mxjs": "tr.rotation = (get_media_time() % 8) * 360 / 8; tr.update=true;" ++``` + + ## Grouping + ### Properties for `group` objects + +-* id (null): group identifier +-* scenes ([]): zero or more `group` or `scene` objects, cannot be animated or updated +-* opacity (1): group opacity +-* offscreen ('none'): set group in offscreen mode, cannot be animated or updated. An offscreen mode is not directly visible but can be used in some texture operations. Possible values are: +- * none: regular group +- * mask: offscreen surface is alpha+grey +- * color: offscreen surface is alpha+colors or colors if `back_color` is set +- * dual: same as `color` but allows group to be displayed +-* scaler (1): when opacity or offscreen rendering is used, offscreen canvas size is divided by this factor (>=1) +-* back_color ('none'): when opacity or offscreen rendering is used, fill offscreen canvas with the given color. +-* width (-1): when opacity or offscreen rendering is used, limit offscreen width to given value (see below) +-* height (-1): when opacity or offscreen rendering is used, limit offscreen height to given value (see below) +-* use (null): id of group or scene to re-use +-* use_depth (-1): number of recursion allowed for the used element, negative means global max branch depth as indicated by `maxdepth` +-* reverse (false): reverse scenes order before draw +-* reference (false): group is a reference space for relative coordinate of children nodes ++ ++- id (null): group identifier ++- scenes ([]): zero or more `group` or `scene` objects, cannot be animated or updated ++- opacity (1): group opacity ++- offscreen ('none'): set group in offscreen mode, cannot be animated or updated. An offscreen mode is not directly visible but can be used in some texture operations. Possible values are: ++ ++ - none: regular group ++ - mask: offscreen surface is alpha+grey ++ - color: offscreen surface is alpha+colors or colors if `back_color` is set ++ - dual: same as `color` but allows group to be displayed ++ ++- scaler (1): when opacity or offscreen rendering is used, offscreen canvas size is divided by this factor (>=1) ++- back_color ('none'): when opacity or offscreen rendering is used, fill offscreen canvas with the given color. ++- width (-1): when opacity or offscreen rendering is used, limit offscreen width to given value (see below) ++- height (-1): when opacity or offscreen rendering is used, limit offscreen height to given value (see below) ++- use (null): id of group or scene to re-use ++- use_depth (-1): number of recursion allowed for the used element, negative means global max branch depth as indicated by `maxdepth` ++- reverse (false): reverse scenes order before draw ++- reference (false): group is a reference space for relative coordinate of children nodes ++ + + ### Notes + +@@ -347,27 +410,33 @@ When enforcing `width` and `height` on a group with `opacity<1`, the display may + ## Scenes + ### Properties for `scene` objects + +-* id (null): scene identifier +-* js ('shape'): scene type, either builtin (see below) or path to a JS module, cannot be animated or updated +-* sources ([]): list of identifiers of sequences or offscreen groups used by this scene +-* width (-1): width of the scene, -1 means reference space width +-* height (-1): height of the scene, -1 means reference space height +-* mix (null): a `transition` object to apply if more than one source is set, ignored otherwise +-* mix_ratio (-1): mix ratio for transition effect, <=0 means first source only, >=1 means second source only +-* volume (1.0): audio volume (0: silence, 1: input volume), this value is not clamped by the mixer. +-* fade ('inout'): indicate how audio should be faded at scene activate/deactivate: +- * in: audio fade-in when playing first frame after scene activation +- * out: audio fade-out when playing last frame at scene activation +- * inout: both fade-in and fade-out are enabled +- * other: no audio fade +-* autoshow (true): automatically deactivate scene when sequences set in `sources` are not active +-* nosig ('lost'): enable no-signal message for scenes using sequences: +- * no: disable message +- * lost: display message when signal is lost +- * before: display message if source is not yet active +- * all: always display message if source is inactive +-* styles ([]): list of style IDs to use ++ ++- id (null): scene identifier ++- js ('shape'): scene type, either builtin (see below) or path to a JS module, cannot be animated or updated ++- sources ([]): list of identifiers of sequences or offscreen groups used by this scene ++- width (-1): width of the scene, -1 means reference space width ++- height (-1): height of the scene, -1 means reference space height ++- mix (null): a `transition` object to apply if more than one source is set, ignored otherwise ++- mix_ratio (-1): mix ratio for transition effect, <=0 means first source only, >=1 means second source only ++- volume (1.0): audio volume (0: silence, 1: input volume), this value is not clamped by the mixer. ++- fade ('inout'): indicate how audio should be faded at scene activate/deactivate: ++ ++ - in: audio fade-in when playing first frame after scene activation ++ - out: audio fade-out when playing last frame at scene activation ++ - inout: both fade-in and fade-out are enabled ++ - other: no audio fade ++ ++- autoshow (true): automatically deactivate scene when sequences set in `sources` are not active ++- nosig ('lost'): enable no-signal message for scenes using sequences: ++ ++ - no: disable message ++ - lost: display message when signal is lost ++ - before: display message if source is not yet active ++ - all: always display message if source is inactive ++ ++- styles ([]): list of style IDs to use + - any other property exposed by the underlying scene JS module. ++ + + ### Notes + +@@ -381,11 +450,13 @@ If a scene uses one or more sequences and `autoshow` is not set, the scene will + ### JSON syntax + + Properties for `transition` objects: +-* id (null): transition identifier +-* type: transition type, either builtin (see below) or path to a JS module +-* dur: transition duration (transitions always end at source stop time). Ignored if transition is specified for a scene `mix`. +-* fun (null): JS code modifying the ratio effect ++ ++- id (null): transition identifier ++- type: transition type, either builtin (see below) or path to a JS module ++- dur: transition duration (transitions always end at source stop time). Ignored if transition is specified for a scene `mix`. ++- fun (null): JS code modifying the ratio effect + - any other property exposed by the underlying transition module. ++ + + ### Notes + +@@ -401,41 +472,52 @@ Example + ## Timers and animations + ### Properties for `timer` objects + +-* id (null): id of the timer +-* dur (0): duration of the timer in seconds +-* loop (false): loops timer when `stop` is not set +-* pause (false): pause timer +-* start (-1): start time (see notes), negative value means inactive +-* stop (-1): stop time (see notes), ignored if less than `start` +-* keys ([]): list of keys used for interpolation, ordered list between 0.0 and 1.0 +-* anims ([]): list of `animation` objects ++ ++- id (null): id of the timer ++- dur (0): duration of the timer in seconds ++- loop (false): loops timer when `stop` is not set ++- pause (false): pause timer ++- start (-1): start time (see notes), negative value means inactive ++- stop (-1): stop time (see notes), ignored if less than `start` ++- keys ([]): list of keys used for interpolation, ordered list between 0.0 and 1.0 ++- anims ([]): list of `animation` objects ++ + + ### Properties for `animation` objects + +-* values ([]): list of values to interpolate, there must be as many values as there are keys +-* color (false): indicate the values are color (as strings) +-* angle (false): indicate the interpolation factor is an angle in degree, to convert to radians (interpolation ratio multiplied by PI and divided by 180) before interpolation +-* mode ('linear') : interpolation mode: +- * linear: linear interpolation between the values +- * discrete: do not interpolate +- * other: JS code modifying the interpolation ratio +-* postfun (null): JS code modifying the interpolation result +-* end ('freeze'): behavior at end of animation: +- * freeze: keep last animated values +- * restore: restore targets to their initial values +-* targets ([]): list of strings indicating targets properties to modify. Syntax is: +- * ID@option: modifies property `option` of object with given ID +- * ID@option[IDX]: modifies value at index `IDX` of array property `option` of object with given ID ++ ++- values ([]): list of values to interpolate, there must be as many values as there are keys ++- color (false): indicate the values are color (as strings) ++- angle (false): indicate the interpolation factor is an angle in degree, to convert to radians (interpolation ratio multiplied by PI and divided by 180) before interpolation ++- mode ('linear') : interpolation mode: ++ ++ - linear: linear interpolation between the values ++ - discrete: do not interpolate ++ - other: JS code modifying the interpolation ratio ++ ++- postfun (null): JS code modifying the interpolation result ++- end ('freeze'): behavior at end of animation: ++ ++ - freeze: keep last animated values ++ - restore: restore targets to their initial values ++ ++- targets ([]): list of strings indicating targets properties to modify. Syntax is: ++ ++ - ID@option: modifies property `option` of object with given ID ++ - ID@option[IDX]: modifies value at index `IDX` of array property `option` of object with given ID ++ + + ### Notes + + Currently, only `scene`, `group`, `transition` and `script` objects can be modified through timers (see playlist updates). + + The syntax for `start` and `stop` fields is: +-* `now`: resolves to current UTC clock in `live` mode, and to 0 for non-live mode +-* date: converted to UTC date in `live` mode, and to 0 for non-live mode +-* N: converted to UTC clock at init plus N seconds for `timer` objects (absolute offset from timeline init) +-* "N": converted to current UTC clock plus N seconds (relative offset from current time) with N a positive or negative number ++ ++- `now`: resolves to current UTC clock in `live` mode, and to 0 for non-live mode ++- date: converted to UTC date in `live` mode, and to 0 for non-live mode ++- N: converted to UTC clock at init plus N seconds for `timer` objects (absolute offset from timeline init) ++- "N": converted to current UTC clock plus N seconds (relative offset from current time) with N a positive or negative number ++ + + The `JSFun` specified by `mode` has one input parameter `interp` equal to the interpolation factor and must return the new interpolation factor. + Example +@@ -452,9 +534,11 @@ Example + ## Scripts + ### Properties for `script` objects + +-* id (null): id of the script +-* script (null): JavaScript code or path to JavaScript file to execute, cannot be animated or updated +-* active (true): indicate if script is active or not ++ ++- id (null): id of the script ++- script (null): JavaScript code or path to JavaScript file to execute, cannot be animated or updated ++- active (true): indicate if script is active or not ++ + + ### Notes + +@@ -462,20 +546,28 @@ Script objects allow read and write access to the playlist from script. They cur + + The `JSFun` function specified by `fun` has no input parameter. The return value (default 0) is the number of seconds (float) to wait until next evaluation of the script. + +-EX: { "script": "let s=get_scene('s1'); let rot = s.get('rotation'); rot += 10; s.set('rotation', rot); return 2;" } ++Example ++``` ++{ "script": "let s=get_scene('s1'); let rot = s.get('rotation'); rot += 10; s.set('rotation', rot); return 2;" } ++``` ++ + This will change scene `s1` rotation every 2 seconds + + ## Watchers + ### Properties for `watcher` objects + +-* id (null): ID of the watcher +-* active (true): indicate if watcher is active or not +-* watch (""): element watched, formatted as `ID@prop`, with `ID` the element ID and `prop` the property name to watch +-* target (""): action for watcher. Allowed syntaxes are: +- * `ID@prop`, `ID@prop[idx]`: copy value to property `prop` of the element `ID` (potentially at index `idx` if specified for arrays) +- * `ID.fun_name`: call function `fun_name` exported from scene module `ID`, using three arguments ['value', 'watchID', 'watchPropName'], no return value check +- * otherwise: action must be JS code, and the resulting `JSFun` has one argument `value` containing the watched value, and no return value check +-* with (undefined): for targets in the form `ID@prop`, use this value instead of the watched value ++ ++- id (null): ID of the watcher ++- active (true): indicate if watcher is active or not ++- watch (""): element watched, formatted as `ID@prop`, with `ID` the element ID and `prop` the property name to watch ++- target (""): action for watcher. Allowed syntaxes are: ++ ++ - `ID@prop`, `ID@prop[idx]`: copy value to property `prop` of the element `ID` (potentially at index `idx` if specified for arrays) ++ - `ID.fun_name`: call function `fun_name` exported from scene module `ID`, using three arguments ['value', 'watchID', 'watchPropName'], no return value check ++ - otherwise: action must be JS code, and the resulting `JSFun` has one argument `value` containing the watched value, and no return value check ++ ++- with (undefined): for targets in the form `ID@prop`, use this value instead of the watched value ++ + + ### Notes + +@@ -483,9 +575,11 @@ A watcher can be used to monitor changes in an object in the playlist. + Any object property that can be animated or updated can be monitored by a watcher. + + In addition, the following virtual properties (cannot be read or write) can be watched: +-* sequence.active: value is set to true when sequence is activated, and false when deactivated +-* source.active: value is set to true when source playback starts, and false when source playback stops +-* timer.active: value is set to true when timer starts, and false when timer stops ++ ++- sequence.active: value is set to true when sequence is activated, and false when deactivated ++- source.active: value is set to true when source playback starts, and false when source playback stops ++- timer.active: value is set to true when timer starts, and false when timer stops ++ + + Only the `active` property can be animated or updated in a watcher. + +@@ -493,19 +587,23 @@ Example + ``` + {'watch': 's1@rotation', 'target': 's2@rotation'} + ``` ++ + This will copy s1.rotation to s2.rotation. + + Example + ``` + {'watch': 's1@rotation', 'target': 'get_scene('s2').set('rotation', -value); } +-``` ++``` ++ + This will copy the -1*s1.rotation to s2.rotation. + + ### Watching UI events + + Watchers can also be used to monitor GPAC user events by setting `watch` to: ++ + - an event name to monitor, one of `keydown`, `keyup`, `mousemove`, `mouseup`, `mousedown`, `wheel`, `textInput` + - `events` to monitor all events (including internal events). ++ + + For `keyup` and `keydown` events, the key code to watch may additionally be given in parenthesis, e.g. `'watch': 'keyup(T)'`. + +@@ -518,14 +616,17 @@ Example + ``` + {'watch': 'mousemove', 'target': 'let s = mouse_over(evt); get_scene('s2').set('fill', (s && (s.id=='s1') ? 'white' : 'black' );'} + ``` ++ + This will set s1 fill color to white of mouse is over s2 and to black otherwise. + + ## Styles + ### Properties for `style` objects + +-* id (null): ID of the style +-* forced (false): always apply style even when no modifications +-* other: any property to share between scene ++ ++- id (null): ID of the style ++- forced (false): always apply style even when no modifications ++- other: any property to share between scene ++ + + ### Notes + +@@ -533,9 +634,11 @@ A style object allows scenes to share the same values for a given set of propert + + If a scene property has the same name as a style property, the scene property is replaced by the style property. + Styles only apply to scene properties as follows: ++ + - volume, fade, mix_ratio can use style + - all options defined by the scene module can use style + - transformation or other scene properties cannot use style ++ + + Properties of a style object can be animated or updated, but a style object cannot be watched. + +@@ -546,16 +649,20 @@ modified (animation, update), `st2` will only be applied once. + + ## Filter configuration + The playlist may specify configuration options of the filter, using a root object of type 'config': ++ + - property names are the same as the filter options + - property values are given in the native type, or as strings for fractions (format `N/D`), vectors (format `WxH`) or enums + - each declared property overrides the filter option of the same name (whether default or set at filter creation) ++ + + A configuration object in the playlist is only parsed when initially loading the playlist, and ignored when reloading it. + + The following additional properties are defined for testing: +-* reload_tests([]): list of playlists to reload +-* reload_timeout(1.0): timeout in seconds before playlist reload +-* reload_loop (0): number of times to repeat the reload tests (not including original playlist which is not reloaded) ++ ++- reload_tests([]): list of playlists to reload ++- reload_timeout(1.0): timeout in seconds before playlist reload ++- reload_loop (0): number of times to repeat the reload tests (not including original playlist which is not reloaded) ++ + + ## Playlist modification + The playlist file can be modified at any time. +@@ -613,22 +720,29 @@ __Warning: The `updates` file is only read when modified _AFTER_ the initializat + + The `updates` file content shall be either a single JSON object or an array of JSON objects. + The properties of these objects are: +-* skip: if true or 1, ignores the update, otherwise apply it +-* replace: string identifying the target replacement. Syntax is: +- * ID@name: indicate property name of element with given ID to replace +- * ID@name[idx]: indicate the index in the property name of element with given ID to replace +-* with: replacement value, must be of the same type as the target value. ++ ++- skip: if true or 1, ignores the update, otherwise apply it ++- replace: string identifying the target replacement. Syntax is: ++ ++ - ID@name: indicate property name of element with given ID to replace ++ - ID@name[idx]: indicate the index in the property name of element with given ID to replace ++ ++- with: replacement value, must be of the same type as the target value. ++ + + An `id` property cannot be updated. + + The following playlist elements of a playlist can be updated: +-* scene: all properties except `js` and read-only module properties +-* group: all properties except `scenes` and `offscreen` +-* sequence: `start`, `stop`, `loop` and `transition` properties +-* timer: `start`, `stop`, `loop`, `pause` and `dur` properties +-* transition: all properties +- * for sequence transitions: most of these properties will only be updated at next reload +- * for active scene transitions: whether these changes are applied right away depend on the transition module ++ ++- scene: all properties except `js` and read-only module properties ++- group: all properties except `scenes` and `offscreen` ++- sequence: `start`, `stop`, `loop` and `transition` properties ++- timer: `start`, `stop`, `loop`, `pause` and `dur` properties ++- transition: all properties ++ ++ - for sequence transitions: most of these properties will only be updated at next reload ++ - for active scene transitions: whether these changes are applied right away depend on the transition module ++ + + Example + ``` +@@ -640,18 +754,68 @@ Example + + # Scene modules + ++## Scene `mask` ++This scene sets the canvas alpha mask mode. ++ ++The canvas alpha mask is always full screen. ++ ++In software mode, combining mask effect in record mode and reverse group drawing allows drawing front to back while writing pixels only once. ++ ++Options: ++ ++- mode ('off'): if set, reset clipper otherwise set it to scene position and size ++ ++ - off: mask is disabled ++ - on: mask is enabled and cleared, further draw operations will take place on mask ++ - onkeep: mask is enabled but not cleared, further draw operations will take place on mask ++ - use: mask is enabled, further draw operations will be filtered by mask ++ - use_inv: mask is enabled, further draw operations will be filtered by 1-mask ++ - rec: mask is in record mode, further draw operations will be drawn on output and will set mask value to 0 ++ ++ ++ ++## Scene `clear` ++This scene clears the canvas area covered by the scene with a given color. ++ ++The default clear color of the mixer is `black`. ++ ++The clear area is always axis-aligned in output frame, so when skew/rotation are present, the axis-aligned bounding box of the transformed scene area will be cleared. ++ ++Options: ++ ++- color ('none'): clear color ++ ++ ++## Scene `clip` ++This scene resets the canvas clipper or sets the canvas clipper to the scene area. ++ ++The clipper is always axis-aligned in output frame, so when skew/rotation are present, the axis-aligned bounding box of the transformed clipper will be used. ++ ++Clippers are handled through a stack, resetting the clipper pops the stack and restores previous clipper. ++If a clipper is already defined when setting the clipper, the clipper set is the intersection of the two clippers. ++ ++Options: ++ ++- reset (false): if set, reset clipper otherwise set it to scene position and size ++- stack (true): if false, clipper is set/reset independently of the clipper stack (no intersection, no push/pop of the stack) ++ ++ + ## Scene `shape` + This scene can be used to setup a shape, its outline and specify the fill and strike modes. + Supported shapes include: ++ + - a variety of rectangles, ellipse and other polygons + - custom paths specified from JS + - text ++ + + The color modes for shapes and outlines include: ++ + - texturing using data from input media streams (shape fill only) + - texturing using local JPEG and PNG files (shape fill only) + - solid color + - linear and radial gradients ++ + + The default scene is optimized to fallback to fast blit when no transformations are used on a straight rectangle shape. + +@@ -660,8 +824,10 @@ All options can be updated at run time. + The module accepts 0, 1 or 2 sequences as input. + + Color replacement operations can be specified for base scenes using source videos by specifying the `replace` option. The replacement source is: ++ + - the image data if `img` is set, potentially altered using `*_rep` options + - otherwise a linear gradient if `fill=linear` or a radial gradient if `fill=radial` (NOT supported in GPU mode, use an offscreen group for this). ++ + + __Warning: Color replacement operations cannot be used with transition or mix effects.__ + +@@ -669,32 +835,38 @@ __Warning: Color replacement operations cannot be used with transition or mix ef + + Text can be loaded from file if `text[0]` is an existing local file. + By default all lines are loaded. The number of loaded lines can be specified using `text[1]` as follows: +-* 0 or not present: all lines are loaded +-* N > 0: only keep the last N lines +-* N < 0: only keep the first N lines ++ ++- 0 or not present: all lines are loaded ++- N > 0: only keep the last N lines ++- N < 0: only keep the first N lines ++ + + Text loaded from file will be refreshed whenever the file is modified. + + Predefined keywords can be used in input text, identified as `$KEYWORD$`. The following keywords (case insensitive) are defined: +-* time: replaced by UTC date +-* ltime: replaced by locale date +-* date: replaced by date (Y/M/D) +-* ldate: replaced by locale date (Y/M/D) +-* mtime: replaced by output media time +-* mtime_SRC: replaced by media time of input source `SRC` +-* cpu: replaced by current CPU usage of process +-* mem: replaced by current memory usage of process +-* version: replaced by GPAC version +-* fversion: replaced by GPAC full version +-* P4CC, PropName: replaced by corresponding PID property ++ ++- time: replaced by UTC date ++- ltime: replaced by locale date ++- date: replaced by date (Y/M/D) ++- ldate: replaced by locale date (Y/M/D) ++- mtime: replaced by output media time ++- mtime_SRC: replaced by media time of input source `SRC` ++- cpu: replaced by current CPU usage of process ++- mem: replaced by current memory usage of process ++- version: replaced by GPAC version ++- fversion: replaced by GPAC full version ++- P4CC, PropName: replaced by corresponding PID property ++ + + ## Custom paths + + Custom paths (shapes) can be created through JS code indicated in 'shape', either inline or through a file. + The following GPAC JS modules are imported: +- - `Sys` as `sys` +- - All EVG as `evg` +- - `os` form QuickJS ++ ++ - `Sys` as `sys` ++ - All EVG as `evg` ++ - `os` form QuickJS ++ + + See [https://doxygen.gpac.io]() for more information on EVG and Sys JS APIs. + +@@ -713,191 +885,186 @@ Example + ``` + "shape": "this.path.add_rectangle(0, 0, this.width, this.height); let el = new evg.Path().ellipse(0, 0, this.width, this.height/3); this.path.add_path(el); this.tx_adjust = true;" + ``` ++ + In this example, the texture mapping will be adjusted to the desired size. + + The global variables and functions are available (c.f. `gpac -h avmix:global`): +- * get_media_time(): return media time in seconds (float) of output +- * get_media_time(SRC): get time of source with id `SRC`, return -4 if not found, -3 if not playing, -2 if in prefetch, -1 if timing not yet known, media time in seconds (float) otherwise +- * current_utc_clock: current UTC time in ms +- * video_time: output video time +- * video_timescale: output video timescale +- * video_width: output video width +- * video_height: output video height ++ ++ - get_media_time(): return media time in seconds (float) of output ++ - get_media_time(SRC): get time of source with id `SRC`, return -4 if not found, -3 if not playing, -2 if in prefetch, -1 if timing not yet known, media time in seconds (float) otherwise ++ - current_utc_clock: current UTC time in ms ++ - video_time: output video time ++ - video_timescale: output video timescale ++ - video_width: output video width ++ - video_height: output video height ++ + + If your path needs to be reevaluated on regular basis, set the value `this.reload` to the timeout to next reload, in milliseconds. + + Options: +-* rx (0): horizontal radius for rounded rect in percent of object width if positive, in absolute value if negative, value `y` means use `ry` +-* ry (0): vertical radius for rounded rect in percent of object height if positive, in absolute value if negative, value `x` means use `rx` +-* tl (1): top-left corner scaler (positive, 0 disables corner) +-* bl (1): bottom-left corner scaler (positive, 0 disables corner) +-* tr (1): top-right corner scaler (positive, 0 disables corner) +-* br (1): bottom-right corner scaler (positive, 0 disables corner) +-* rs (false): repeat texture horizontally +-* rt (false): repeat texture vertically +-* keep_ar (true): keep aspect ratio +-* pad_color ('0x00FFFFFF'): color to use for texture padding if `rs` or `rt` are false. Use `none` to use texture edge, `0x00FFFFFF` for transparent (always enforced if source is transparent) +-* txmx ([]): texture matrix - all 6 coefficients must be set, i.e. [xx xy tx yx yy ty] +-* cmx ([]): color transform - all 20 coefficients must be set in order, i.e. [Mrr, Mrg, Mrb, Mra, Tr, Mgr, Mgg ...] +-* line_width (0): line width in percent of width if positive, or absolute value if negative +-* line_color ('white'): line color, `linear` for linear gradient and `radial` for radial gradient +-* line_pos ('center'): line/shape positioning. Possible values are: +- * center: line is centered around shape +- * outside: line is outside the shape +- * inside: line is inside the shape +-* line_dash ('plain'): line dashing mode. Possible values are: +- * plain: no dash +- * dash: predefined dash pattern is used +- * dot: predefined dot pattern is used +- * dashdot: predefined dash-dot pattern is used +- * dashdashdot: predefined dash-dash-dot pattern is used +- * dashdotdot: predefined dash-dot-dot pattern is used +-* dashes ([]): dash/dot pattern lengths for custom dashes (these will be multiplied by line size) +-* cap ('flat'): line end style. Possible values are: +- * flat: flat end +- * round: round end +- * square: square end (extends limit compared to flat) +- * triangle: triangle end +-* join ('miter'): line joint style. Possible values are: +- * miter: miter join (straight lines) +- * round: round join +- * bevel: bevel join +- * bevelmiter: bevel+miter join +-* miter_limit (2): miter limit for joint styles +-* dash_length (-1): length of path to outline, negative values mean full path +-* dash_offset (0): offset in path at which the outline starts +-* blit (true): use blit if possible, otherwise EVG texturing. If disabled, always use texturing +-* fill ('none'): fill color if used without sources, `linear` for linear gradient and `radial` for radial gradient +-* img (''): image for scene without sources or when `replace` is set. Accepts either a path to a local image (JPG or PNG), the ID of an offscreen group or the ID of a sequence +-* alpha (1): global texture transparency +-* replace (''): if `img` or `fill` is set and shape is using source, set multi texture option. Possible modes are: +- * a, r, g or b: replace alpha source component by indicated component from `img` . If prefix `-` is set, replace by one minus the indicated component +- * m: mix using `mix_ratio` the color components of source and `img` and set alpha to full opacity +- * M: mix using `mix_ratio` all components of source and `img`, including alpha +- * xC: mix source 1 and source 2 using `img` component `C` (`a`, `r`, `g` or `b`) and force alpha to full opacity +- * XC: mix source 1 and source 2 using `img` component `C` (`a`, `r`, `g` or `b`), including alpha +- +-* shape ('rect'): shape type. Possible values are: +- * rect: rounded rectangle +- * square: square using smaller width/height value +- * ellipse: ellipse +- * circle: circle using smaller width/height value +- * rhombus: axis-aligned rhombus +- * text: force text mode even if text field is empty +- * rects: same as rounded rectangle but use straight lines for corners +- * other value: JS code for custom path creation, either string or local file name (dynamic reload possible) +-* grad_p ([]): gradient positions between 0 and 1 +-* grad_c ([]): gradient colors for each position, as strings +-* grad_start ([]): start point for linear gradient or center point for radial gradient +-* grad_end ([]): end point for linear gradient or radius value for radial gradient +-* grad_focal ([]): focal point for radial gradient +-* grad_mode ('pad'): gradient mode. Possible values are: +- * pad: color padding outside of gradient bounds +- * spread: mirror gradient outside of bounds +- * repeat: repeat gradient outside of bounds +-* text ([]): text lines (UTF-8 only). If not empty, force `shape=text` +-* font ([]): font name(s) +-* size (20): font size in percent of height (horizontal text) or width (vertical text), or absolute value if negative +-* baseline ('alphabetic'): baseline position. Possible values are: +- * alphabetic: alphabetic position of baseline +- * top: baseline at top of EM Box +- * hanging: reserved, _not implemented_ +- * middle: baseline at middle of EM Box +- * ideograph: reserved, _not implemented_ +- * bottom: baseline at bottom of EM Box +-* align ('center'): horizontal text alignment. Possible values are: +- * center: center of shape +- * start: start of shape (left or right depending on text direction) +- * end: end of shape (right or left depending on text direction) +- * left: left of shape +- * right: right of shape +-* spacing (0): line spacing in percent of height (horizontal text) or width (vertical text), or absolute value if negative +-* bold (false): use bold version of font +-* italic (false): use italic version of font +-* underline (false): underline text +-* vertical (false): draw text vertically +-* flip (false): flip text vertically +-* extend (0): maximum text width in percent of width (for horizontal) or height (for vertical), or absolute value if negative +-* keep_ar_rep (true): same as `keep_ar` for local image in replace mode +-* txmx_rep ([]): same as `txmx` for local image in replace mode +-* cmx_rep ([]): same as `cmx` for local image in replace mode +-* pad_color_rep ('none'): same as `pad_color` for local image in replace mode +-* rs_rep (false): same as `rs` for local image in replace mode +-* rt_rep (false): same as `rt` for local image in replace mode + +-## Scene `mask` +-This scene sets the canvas alpha mask mode. +- +-The canvas alpha mask is always full screen. +- +-In software mode, combining mask effect in record mode and reverse group drawing allows drawing front to back while writing pixels only once. +- +-Options: +-* mode ('off'): if set, reset clipper otherwise set it to scene position and size +- * off: mask is disabled +- * on: mask is enabled and cleared, further draw operations will take place on mask +- * onkeep: mask is enabled but not cleared, further draw operations will take place on mask +- * use: mask is enabled, further draw operations will be filtered by mask +- * use_inv: mask is enabled, further draw operations will be filtered by 1-mask +- * rec: mask is in record mode, further draw operations will be drawn on output and will set mask value to 0 +- ++- rx (0): horizontal radius for rounded rect in percent of object width if positive, in absolute value if negative, value `y` means use `ry` ++- ry (0): vertical radius for rounded rect in percent of object height if positive, in absolute value if negative, value `x` means use `rx` ++- tl (1): top-left corner scaler (positive, 0 disables corner) ++- bl (1): bottom-left corner scaler (positive, 0 disables corner) ++- tr (1): top-right corner scaler (positive, 0 disables corner) ++- br (1): bottom-right corner scaler (positive, 0 disables corner) ++- rs (false): repeat texture horizontally ++- rt (false): repeat texture vertically ++- keep_ar (true): keep aspect ratio ++- pad_color ('0x00FFFFFF'): color to use for texture padding if `rs` or `rt` are false. Use `none` to use texture edge, `0x00FFFFFF` for transparent (always enforced if source is transparent) ++- txmx ([]): texture matrix - all 6 coefficients must be set, i.e. [xx xy tx yx yy ty] ++- cmx ([]): color transform - all 20 coefficients must be set in order, i.e. [Mrr, Mrg, Mrb, Mra, Tr, Mgr, Mgg ...] ++- line_width (0): line width in percent of width if positive, or absolute value if negative ++- line_color ('white'): line color, `linear` for linear gradient and `radial` for radial gradient ++- line_pos ('center'): line/shape positioning. Possible values are: + +-## Scene `clear` +-This scene clears the canvas area covered by the scene with a given color. +- +-The default clear color of the mixer is `black`. +- +-The clear area is always axis-aligned in output frame, so when skew/rotation are present, the axis-aligned bounding box of the transformed scene area will be cleared. +- +-Options: +-* color ('none'): clear color ++ - center: line is centered around shape ++ - outside: line is outside the shape ++ - inside: line is inside the shape ++ ++- line_dash ('plain'): line dashing mode. Possible values are: ++ ++ - plain: no dash ++ - dash: predefined dash pattern is used ++ - dot: predefined dot pattern is used ++ - dashdot: predefined dash-dot pattern is used ++ - dashdashdot: predefined dash-dash-dot pattern is used ++ - dashdotdot: predefined dash-dot-dot pattern is used ++ ++- dashes ([]): dash/dot pattern lengths for custom dashes (these will be multiplied by line size) ++- cap ('flat'): line end style. Possible values are: ++ ++ - flat: flat end ++ - round: round end ++ - square: square end (extends limit compared to flat) ++ - triangle: triangle end ++ ++- join ('miter'): line joint style. Possible values are: ++ ++ - miter: miter join (straight lines) ++ - round: round join ++ - bevel: bevel join ++ - bevelmiter: bevel+miter join ++ ++- miter_limit (2): miter limit for joint styles ++- dash_length (-1): length of path to outline, negative values mean full path ++- dash_offset (0): offset in path at which the outline starts ++- blit (true): use blit if possible, otherwise EVG texturing. If disabled, always use texturing ++- fill ('none'): fill color if used without sources, `linear` for linear gradient and `radial` for radial gradient ++- img (''): image for scene without sources or when `replace` is set. Accepts either a path to a local image (JPG or PNG), the ID of an offscreen group or the ID of a sequence ++- alpha (1): global texture transparency ++- replace (''): if `img` or `fill` is set and shape is using source, set multi texture option. Possible modes are: ++ ++ - a, r, g or b: replace alpha source component by indicated component from `img` . If prefix `-` is set, replace by one minus the indicated component ++ - m: mix using `mix_ratio` the color components of source and `img` and set alpha to full opacity ++ - M: mix using `mix_ratio` all components of source and `img`, including alpha ++ - xC: mix source 1 and source 2 using `img` component `C` (`a`, `r`, `g` or `b`) and force alpha to full opacity ++ - XC: mix source 1 and source 2 using `img` component `C` (`a`, `r`, `g` or `b`), including alpha + +-## Scene `clip` +-This scene resets the canvas clipper or sets the canvas clipper to the scene area. +- +-The clipper is always axis-aligned in output frame, so when skew/rotation are present, the axis-aligned bounding box of the transformed clipper will be used. +- +-Clippers are handled through a stack, resetting the clipper pops the stack and restores previous clipper. +-If a clipper is already defined when setting the clipper, the clipper set is the intersection of the two clippers. + +-Options: +-* reset (false): if set, reset clipper otherwise set it to scene position and size +-* stack (true): if false, clipper is set/reset independently of the clipper stack (no intersection, no push/pop of the stack) ++ ++- shape ('rect'): shape type. Possible values are: ++ ++ - rect: rounded rectangle ++ - square: square using smaller width/height value ++ - ellipse: ellipse ++ - circle: circle using smaller width/height value ++ - rhombus: axis-aligned rhombus ++ - text: force text mode even if text field is empty ++ - rects: same as rounded rectangle but use straight lines for corners ++ - other value: JS code for custom path creation, either string or local file name (dynamic reload possible) ++ ++- grad_p ([]): gradient positions between 0 and 1 ++- grad_c ([]): gradient colors for each position, as strings ++- grad_start ([]): start point for linear gradient or center point for radial gradient ++- grad_end ([]): end point for linear gradient or radius value for radial gradient ++- grad_focal ([]): focal point for radial gradient ++- grad_mode ('pad'): gradient mode. Possible values are: ++ ++ - pad: color padding outside of gradient bounds ++ - spread: mirror gradient outside of bounds ++ - repeat: repeat gradient outside of bounds ++ ++- text ([]): text lines (UTF-8 only). If not empty, force `shape=text` ++- font ([]): font name(s) ++- size (20): font size in percent of height (horizontal text) or width (vertical text), or absolute value if negative ++- baseline ('alphabetic'): baseline position. Possible values are: ++ ++ - alphabetic: alphabetic position of baseline ++ - top: baseline at top of EM Box ++ - hanging: reserved, _not implemented_ ++ - middle: baseline at middle of EM Box ++ - ideograph: reserved, _not implemented_ ++ - bottom: baseline at bottom of EM Box ++ ++- align ('center'): horizontal text alignment. Possible values are: ++ ++ - center: center of shape ++ - start: start of shape (left or right depending on text direction) ++ - end: end of shape (right or left depending on text direction) ++ - left: left of shape ++ - right: right of shape ++ ++- spacing (0): line spacing in percent of height (horizontal text) or width (vertical text), or absolute value if negative ++- bold (false): use bold version of font ++- italic (false): use italic version of font ++- underline (false): underline text ++- vertical (false): draw text vertically ++- flip (false): flip text vertically ++- extend (0): maximum text width in percent of width (for horizontal) or height (for vertical), or absolute value if negative ++- keep_ar_rep (true): same as `keep_ar` for local image in replace mode ++- txmx_rep ([]): same as `txmx` for local image in replace mode ++- cmx_rep ([]): same as `cmx` for local image in replace mode ++- pad_color_rep ('none'): same as `pad_color` for local image in replace mode ++- rs_rep (false): same as `rs` for local image in replace mode ++- rt_rep (false): same as `rt` for local image in replace mode ++ + + # Transition modules + + ## Transition `gltrans` - GPU only + This transition module wraps gl-transitions, see https://gl-transitions.com/ and `gpac -h avmix:gltrans` for builtin transitions + Options: +-* fx (''): effect name for built-in effects, or path to gl-transition GLSL file + +-## Transition `mix` - software/GPU +-This transition performs cross-fade of source videos ++- fx (''): effect name for built-in effects, or path to gl-transition GLSL file ++ + + ## Transition `swipe` - software/GPU + This transition performs simple 2D affine transformations for source videos transitions, with configurable effect origin + Options: +-* from ('left'): direction of video 2 entry. Possible values are: +- * left: from left to right edges +- * right: from right to left edges +- * top: from top to bottom edges +- * bottom: from bottom to top edges +- * topleft: from top-left to bottom-right corners +- * topright: from top-right to bottom-left corners +- * bottomleft: from bottom-left to top-right corners +- * bottomright: from bottom-right to top-left corners +- +-* mode ('slide'): how video 2 entry impacts video 1. Possible values are: +- * slide: video 1 position is not modified +- * push: video 2 pushes video 1 away +- * squeeze: video 2 squeezes video 1 along opposite edge +- * grow: video 2 size increases, video 1 not modified +- * swap: video 2 size increases, video 1 size decreases ++ ++- from ('left'): direction of video 2 entry. Possible values are: ++ ++ - left: from left to right edges ++ - right: from right to left edges ++ - top: from top to bottom edges ++ - bottom: from bottom to top edges ++ - topleft: from top-left to bottom-right corners ++ - topright: from top-right to bottom-left corners ++ - bottomleft: from bottom-left to top-right corners ++ - bottomright: from bottom-right to top-left corners ++ ++ ++ ++- mode ('slide'): how video 2 entry impacts video 1. Possible values are: ++ ++ - slide: video 1 position is not modified ++ - push: video 2 pushes video 1 away ++ - squeeze: video 2 squeezes video 1 along opposite edge ++ - grow: video 2 size increases, video 1 not modified ++ - swap: video 2 size increases, video 1 size decreases ++ + ++## Transition `mix` - software/GPU ++This transition performs cross-fade of source videos ++ + ## Transition `fade` - software/GPU + This transition performs fade to/from color of source videos + Options: +-* color ('black'): fade color ++ ++- color ('black'): fade color ++ + + + # Options +@@ -905,9 +1072,10 @@ Options: + __pl__ (str, default: _avmix.json_): local playlist file to load + __live__ (bool, default: _true_): live mode + __gpu__ (enum, default: _off_): enable GPU usage +- * off: no GPU +- * mix: only render textured path to GPU, use software rasterizer for the outlines, solid fills and gradients +- * all: try to use GPU for everything ++ ++- off: no GPU ++- mix: only render textured path to GPU, use software rasterizer for the outlines, solid fills and gradients ++- all: try to use GPU for everything + + __thread__ (sint, default: _-1_): use threads for software rasterizer (-1 for all available cores) + __lwait__ (uint, default: _1000_): timeout in ms before considering no signal is present +@@ -919,9 +1087,10 @@ Options: + __fps__ (frac, default: _25_): output video frame rate + __pfmt__ (pfmt, default: _yuv_): output pixel format. Use `rgba` in GPU mode to force alpha channel + __dynpfmt__ (enum, default: _init_): allow dynamic change of output pixel format in software mode +- * off: pixel format is forced to desired value +- * init: pixel format is forced to format of fullscreen input in first generated frame +- * all: pixel format changes each time a full-screen input PID at same resolution is used ++ ++- off: pixel format is forced to desired value ++- init: pixel format is forced to format of fullscreen input in first generated frame ++- all: pixel format changes each time a full-screen input PID at same resolution is used + + __sr__ (uint, default: _44100_): output audio sample rate, 0 disable audio output + __ch__ (uint, default: _2_): number of output audio channels, 0 disable audio output +diff --git a/docs/Filters/bifsdec.md b/docs/Filters/bifsdec.md +index 1ab46e2e..e6c88719 100644 +--- a/docs/Filters/bifsdec.md ++++ b/docs/Filters/bifsdec.md +@@ -1,6 +1,6 @@ + + +-# MPEG-4 BIFS decoder ++# MPEG-4 BIFS decoder {:data-level="all"} + + Register name used to load filter: __bifsdec__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/bsagg.md b/docs/Filters/bsagg.md +index 4d041e2d..c6ecae17 100644 +--- a/docs/Filters/bsagg.md ++++ b/docs/Filters/bsagg.md +@@ -1,6 +1,6 @@ + + +-# Compressed layered bitstream aggregator ++# Compressed layered bitstream aggregator {:data-level="all"} + + Register name used to load filter: __bsagg__ + This filter is not checked during graph resolution and needs explicit loading. +diff --git a/docs/Filters/bsrw.md b/docs/Filters/bsrw.md +index c41a6d52..583526d1 100644 +--- a/docs/Filters/bsrw.md ++++ b/docs/Filters/bsrw.md +@@ -1,6 +1,6 @@ + + +-# Compressed bitstream rewriter ++# Compressed bitstream rewriter {:data-level="all"} + + Register name used to load filter: __bsrw__ + This filter is not checked during graph resolution and needs explicit loading. +@@ -8,24 +8,33 @@ Filters of this class can connect to each-other. + + This filter rewrites some metadata of various bitstream formats. + The filter can currently modify the following properties in video bitstreams: ++ + - MPEG-4 Visual: +- - sample aspect ratio +- - profile and level ++ ++ - sample aspect ratio ++ - profile and level ++ + - AVC|H264, HEVC and VVC: +- - sample aspect ratio +- - profile, level, profile compatibility +- - video format, video fullrange +- - color primaries, transfer characteristics and matrix coefficients (or remove all info) ++ ++ - sample aspect ratio ++ - profile, level, profile compatibility ++ - video format, video fullrange ++ - color primaries, transfer characteristics and matrix coefficients (or remove all info) ++ + - ProRes: +- - sample aspect ratio +- - color primaries, transfer characteristics and matrix coefficients ++ ++ - sample aspect ratio ++ - color primaries, transfer characteristics and matrix coefficients ++ + + Values are by default initialized to -1, implying to keep the related info (present or not) in the bitstream. + A [sar](#sar) value of `0/0` will remove sample aspect ratio info from bitstream if possible. + + The filter can currently modify the following properties in the stream configuration but not in the bitstream: +-* HEVC: profile IDC, profile space, general compatibility flags +-* VVC: profile IDC, general profile and level indication ++ ++- HEVC: profile IDC, profile space, general compatibility flags ++- VVC: profile IDC, general profile and level indication ++ + + The filter will work in passthrough mode for all other codecs and media types. + +diff --git a/docs/Filters/bssplit.md b/docs/Filters/bssplit.md +index 3f4ef9db..f27bd1f2 100644 +--- a/docs/Filters/bssplit.md ++++ b/docs/Filters/bssplit.md +@@ -1,6 +1,6 @@ + + +-# Compressed layered bitstream splitter ++# Compressed layered bitstream splitter {:data-level="all"} + + Register name used to load filter: __bssplit__ + This filter is not checked during graph resolution and needs explicit loading. +@@ -14,27 +14,36 @@ Splitting is based on temporalID value (start from 1) and layerID value (start f + For AVC|H264, layerID is the dependency value, or quality value if `svcqid` is set. + + Each input stream is filtered according to the `ltid` option as follows: +-* no value set: input stream is split by layerID, i.e. each layer creates an output +-* `all`: input stream is split by layerID and temporalID, i.e. each {layerID,temporalID} creates an output +-* `lID`: input stream is split according to layer `lID` value, and temporalID is ignored +-* `.tID`: input stream is split according to temporal sub-layer `tID` value and layerID is ignored +-* `lID.tID`: input stream is split according to layer `lID` and sub-layer `tID` values ++ ++- no value set: input stream is split by layerID, i.e. each layer creates an output ++- `all`: input stream is split by layerID and temporalID, i.e. each {layerID,temporalID} creates an output ++- `lID`: input stream is split according to layer `lID` value, and temporalID is ignored ++- `.tID`: input stream is split according to temporal sub-layer `tID` value and layerID is ignored ++- `lID.tID`: input stream is split according to layer `lID` and sub-layer `tID` values ++ + + _Note: A tID value of 0 in `ltid` is equivalent to value 1._ + + Multiple values can be given in `ltid`, in which case each value gives the maximum {layerID,temporalID} values for the current layer. + A few examples on an input with 2 layers each with 2 temporal sublayers: +-* `ltid=0.2`: this will split the stream in: +- - one stream with {lID=0,tID=1} and {lID=0,tID=2} NAL units +- - one stream with all other layers/substreams +-* `ltid=0.1,1.1`: this will split the stream in: +- - one stream with {lID=0,tID=1} NAL units +- - one stream with {lID=0,tID=2}, {lID=1,tID=1} NAL units +- - one stream with the rest {lID=0,tID=2}, {lID=1,tID=2} NAL units +-* `ltid=0.1,0.2`: this will split the stream in: +- - one stream with {lID=0,tID=1} NAL units +- - one stream with {lID=0,tID=2} NAL units +- - one stream with the rest {lID=1,tID=1}, {lID=1,tID=2} NAL units ++ ++- `ltid=0.2`: this will split the stream in: ++ ++ - one stream with {lID=0,tID=1} and {lID=0,tID=2} NAL units ++ - one stream with all other layers/substreams ++ ++- `ltid=0.1,1.1`: this will split the stream in: ++ ++ - one stream with {lID=0,tID=1} NAL units ++ - one stream with {lID=0,tID=2}, {lID=1,tID=1} NAL units ++ - one stream with the rest {lID=0,tID=2}, {lID=1,tID=2} NAL units ++ ++- `ltid=0.1,0.2`: this will split the stream in: ++ ++ - one stream with {lID=0,tID=1} NAL units ++ - one stream with {lID=0,tID=2} NAL units ++ - one stream with the rest {lID=1,tID=1}, {lID=1,tID=2} NAL units ++ + + The filter can also be used on AVC and HEVC DolbyVision streams to split base stream and DV RPU/EL. + +diff --git a/docs/Filters/btplay.md b/docs/Filters/btplay.md +index 8f53fc97..92d24014 100644 +--- a/docs/Filters/btplay.md ++++ b/docs/Filters/btplay.md +@@ -1,6 +1,6 @@ + + +-# BT/XMT/X3D loader ++# BT/XMT/X3D loader {:data-level="all"} + + Register name used to load filter: __btplay__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/ccdec.md b/docs/Filters/ccdec.md +index 61af34e2..f3d1fa0a 100644 +--- a/docs/Filters/ccdec.md ++++ b/docs/Filters/ccdec.md +@@ -1,6 +1,6 @@ + + +-# Closed-Caption decoder ++# Closed-Caption decoder {:data-level="all"} + + Register name used to load filter: __ccdec__ + This filter is not checked during graph resolution and needs explicit loading. +diff --git a/docs/Filters/cdcrypt.md b/docs/Filters/cdcrypt.md +index b65a674a..e6eb50db 100644 +--- a/docs/Filters/cdcrypt.md ++++ b/docs/Filters/cdcrypt.md +@@ -1,6 +1,6 @@ + + +-# CENC decryptor ++# CENC decryptor {:data-level="all"} + + Register name used to load filter: __cdcrypt__ + This filter may be automatically loaded during graph resolution. +@@ -18,12 +18,13 @@ When the file is set per PID, the first `CryptInfo` with the same ID is used, ot + + __cfile__ (str): crypt file location + __decrypt__ (enum, default: _full_): decrypt mode (CENC only) +-* full: decrypt everything, throwing error if keys are not found +-* nokey: decrypt everything for which a key is found, skip decryption otherwise +-* skip: decrypt nothing +-* pad0: decrypt nothing and replace all crypted bits with 0 +-* pad1: decrypt nothing and replace all crypted bits with 1 +-* padsc: decrypt nothing and replace all crypted bytes with start codes ++ ++- full: decrypt everything, throwing error if keys are not found ++- nokey: decrypt everything for which a key is found, skip decryption otherwise ++- skip: decrypt nothing ++- pad0: decrypt nothing and replace all crypted bits with 0 ++- pad1: decrypt nothing and replace all crypted bits with 1 ++- padsc: decrypt nothing and replace all crypted bytes with start codes + + __drop_keys__ (uintl): consider keys with given 1-based indexes as not available (multi-key debug) + __kids__ (strl): define KIDs. If `keys` is empty, consider keys with given KID (as hex string) as not available (debug) +diff --git a/docs/Filters/cecrypt.md b/docs/Filters/cecrypt.md +index a78fe72b..0a633e97 100644 +--- a/docs/Filters/cecrypt.md ++++ b/docs/Filters/cecrypt.md +@@ -1,6 +1,6 @@ + + +-# CENC encryptor ++# CENC encryptor {:data-level="all"} + + Register name used to load filter: __cecrypt__ + This filter is not checked during graph resolution and needs explicit loading. +diff --git a/docs/Filters/compositor.md b/docs/Filters/compositor.md +index 3b4bd5e7..aeb29227 100644 +--- a/docs/Filters/compositor.md ++++ b/docs/Filters/compositor.md +@@ -1,6 +1,6 @@ + + +-# Compositor ++# Compositor {:data-level="all"} + + Register name used to load filter: __compositor__ + This filter may be automatically loaded during graph resolution. +@@ -24,8 +24,10 @@ It will stop generating frames as soon as all input streams are done, unless ext + If audio streams are loaded, an audio output PID is created. + + The default output pixel format in filter mode is: ++ + - `rgb` when the filter is explicitly loaded by the application + - `rgba` when the filter is loaded during a link resolution ++ + This can be changed by assigning the [opfmt](#opfmt) option. + If either [opfmt](#opfmt) specifies alpha channel or [bc](#bc) is not 0 but has alpha=0, background creation in default scene will be skipped. + +@@ -42,13 +44,17 @@ If 3D graphics are used or display driver is forced, OpenGL will be used on offs + # Specific URL syntaxes + + The compositor accepts any URL type supported by GPAC. It also accepts the following schemes for URLs: +-* views:// : creates an auto-stereo scene of N views from `views://v1::.::vN` +-* mosaic:// : creates a mosaic of N views from `mosaic://v1::.::vN` ++ ++- views:// : creates an auto-stereo scene of N views from `views://v1::.::vN` ++- mosaic:// : creates a mosaic of N views from `mosaic://v1::.::vN` ++ + + For both syntaxes, `vN` can be any type of URL supported by GPAC. + For `views://` syntax, the number of rendered views is set by [nbviews](#nbviews): ++ + - If the URL gives less views than rendered, the views will be repeated + - If the URL gives more views than rendered, the extra views will be ignored ++ + + The compositor can act as a source filter when the [src](#src) option is explicitly set, independently from the operating mode: + Example +@@ -66,9 +72,10 @@ gpac -i mosaic://URL1:URL2 vout + # Options + + __aa__ (enum, default: _all_, updatable): set anti-aliasing mode for raster graphics; whether the setting is applied or not depends on the graphics module or graphic card +-* none: no anti-aliasing +-* text: anti-aliasing for text only +-* all: complete anti-aliasing ++ ++- none: no anti-aliasing ++- text: anti-aliasing for text only ++- all: complete anti-aliasing + + __hlfill__ (uint, default: _0x0_, updatable): set highlight fill color (ARGB) + __hlline__ (uint, default: _0xFF000000_, updatable): set highlight stroke color (ARGB) +@@ -81,14 +88,16 @@ gpac -i mosaic://URL1:URL2 vout + __stress__ (bool, default: _false_, updatable): enable stress mode of compositor (rebuild all vector graphics and texture states at each frame) + __fast__ (bool, default: _false_, updatable): enable speed optimization - whether the setting is applied or not depends on the graphics module / graphic card + __bvol__ (enum, default: _no_, updatable): draw bounding volume of objects +-* no: disable bounding box +-* box: draws a rectangle (2D) or box (3D) +-* aabb: draws axis-aligned bounding-box tree (3D) or rectangle (2D) ++ ++- no: disable bounding box ++- box: draws a rectangle (2D) or box (3D) ++- aabb: draws axis-aligned bounding-box tree (3D) or rectangle (2D) + + __textxt__ (enum, default: _default_, updatable): specify whether text shall be drawn to a texture and then rendered or directly rendered. Using textured text can improve text rendering in 3D and also improve text-on-video like content +-* default: use texturing for OpenGL rendering, no texture for 2D rasterizer +-* never: never uses text textures +-* always: always render text to texture before drawing ++ ++- default: use texturing for OpenGL rendering, no texture for 2D rasterizer ++- never: never uses text textures ++- always: always render text to texture before drawing + + __out8b__ (bool, default: _false_, updatable): convert 10-bit video to 8 bit texture before GPU upload + __drop__ (bool, default: _false_, updatable): drop late frame when drawing. If not set, frames are not dropped until a desynchronization of 1 second or more is observed +@@ -103,9 +112,11 @@ gpac -i mosaic://URL1:URL2 vout + __dur__ (dbl, default: _0_, updatable): duration of generation. Mostly used when no video input is present. Negative values mean number of frames, positive values duration in second, 0 stops as soon as all streams are done + __fsize__ (bool, default: _false_, updatable): force the scene to resize to the biggest bitmap available if no size info is given in the BIFS configuration + __mode2d__ (enum, default: _defer_, updatable): specify whether immediate drawing should be used or not +-* immediate: the screen is completely redrawn at each frame (always on if pass-through mode is detected) +-* defer: object positioning is tracked from frame to frame and dirty rectangles info is collected in order to redraw the minimal amount of the screen buffer +-* debug: only renders changed areas, resetting other areas ++ ++- immediate: the screen is completely redrawn at each frame (always on if pass-through mode is detected) ++- defer: object positioning is tracked from frame to frame and dirty rectangles info is collected in order to redraw the minimal amount of the screen buffer ++- debug: only renders changed areas, resetting other areas ++ + Whether the setting is applied or not depends on the graphics module and player mode + + __amc__ (bool, default: _true_): audio multichannel support; if disabled always down-mix to stereo. Useful if the multichannel output does not work properly +@@ -128,76 +139,86 @@ Whether the setting is applied or not depends on the graphics module and player + __nojs__ (bool, default: _false_): disable javascript + __noback__ (bool, default: _false_): ignore background nodes and viewport fill (useful when dumping to PNG) + __ogl__ (enum, default: _auto_, updatable): specify 2D rendering mode +-* auto: automatically decides between on, off and hybrid based on content +-* off: disables OpenGL; 3D will not be rendered +-* on: uses OpenGL for all graphics; this will involve polygon tesselation and 2D graphics will not look as nice as 2D mode +-* hybrid: the compositor performs software drawing of 2D graphics with no textures (better quality) and uses OpenGL for all 2D objects with textures and 3D objects ++ ++- auto: automatically decides between on, off and hybrid based on content ++- off: disables OpenGL; 3D will not be rendered ++- on: uses OpenGL for all graphics; this will involve polygon tesselation and 2D graphics will not look as nice as 2D mode ++- hybrid: the compositor performs software drawing of 2D graphics with no textures (better quality) and uses OpenGL for all 2D objects with textures and 3D objects + + __pbo__ (bool, default: _false_, updatable): enable PixelBufferObjects to push YUV textures to GPU in OpenGL Mode. This may slightly increase the performances of the playback + __nav__ (enum, default: _none_, updatable): override the default navigation mode of MPEG-4/VRML (Walk) and X3D (Examine) +-* none: disables navigation +-* walk: 3D world walk +-* fly: 3D world fly (no ground detection) +-* pan: 2D/3D world zoom/pan +-* game: 3D world game (mouse gives walk direction) +-* slide: 2D/3D world slide +-* exam: 2D/3D object examine +-* orbit: 3D object orbit +-* vr: 3D world VR (yaw/pitch/roll) ++ ++- none: disables navigation ++- walk: 3D world walk ++- fly: 3D world fly (no ground detection) ++- pan: 2D/3D world zoom/pan ++- game: 3D world game (mouse gives walk direction) ++- slide: 2D/3D world slide ++- exam: 2D/3D object examine ++- orbit: 3D object orbit ++- vr: 3D world VR (yaw/pitch/roll) + + __linegl__ (bool, default: _false_, updatable): indicate that outlining shall be done through OpenGL pen width rather than vectorial outlining + __epow2__ (bool, default: _true_, updatable): emulate power-of-2 textures for OpenGL (old hardware). Ignored if OpenGL rectangular texture extension is enabled +-* yes: video texture is not resized but emulated with padding. This usually speeds up video mapping on shapes but disables texture transformations +-* no: video is resized to a power of 2 texture when mapping to a shape ++ ++- yes: video texture is not resized but emulated with padding. This usually speeds up video mapping on shapes but disables texture transformations ++- no: video is resized to a power of 2 texture when mapping to a shape + + __paa__ (bool, default: _false_, updatable): indicate whether polygon antialiasing should be used in full antialiasing mode. If not set, only lines and points antialiasing are used + __bcull__ (enum, default: _on_, updatable): indicate whether backface culling shall be disable or not +-* on: enables backface culling +-* off: disables backface culling +-* alpha: only enables backface culling for transparent meshes ++ ++- on: enables backface culling ++- off: disables backface culling ++- alpha: only enables backface culling for transparent meshes + + __wire__ (enum, default: _none_, updatable): wireframe mode +-* none: objects are drawn as solid +-* only: objects are drawn as wireframe only +-* solid: objects are drawn as solid and wireframe is then drawn ++ ++- none: objects are drawn as solid ++- only: objects are drawn as wireframe only ++- solid: objects are drawn as solid and wireframe is then drawn + + __norms__ (enum, default: _none_, updatable): normal vector drawing for debug +-* none: no normals drawn +-* face: one normal per face drawn +-* vertex: one normal per vertex drawn ++ ++- none: no normals drawn ++- face: one normal per face drawn ++- vertex: one normal per vertex drawn + + __rext__ (bool, default: _true_, updatable): use non power of two (rectangular) texture GL extension + __cull__ (bool, default: _true_, updatable): use aabb culling: large objects are rendered in multiple calls when not fully in viewport + __depth_gl_scale__ (flt, default: _100_, updatable): set depth scaler + __depth_gl_type__ (enum, default: _none_, updatable): set geometry type used to draw depth video +-* none: no geometric conversion +-* point: compute point cloud from pixel+depth +-* strip: same as point but thins point set ++ ++- none: no geometric conversion ++- point: compute point cloud from pixel+depth ++- strip: same as point but thins point set + + __nbviews__ (uint, default: _0_, updatable): number of views to use in stereo mode + __stereo__ (enum, default: _none_, updatable): stereo output type. If your graphic card does not support OpenGL shaders, only `top` and `side` modes will be available +-* none: no stereo +-* side: images are displayed side by side from left to right +-* top: images are displayed from top (laft view) to bottom (right view) +-* hmd: same as side except that view aspect ratio is not changed +-* ana: standard color anaglyph (red for left view, green and blue for right view) is used (forces views=2) +-* cols: images are interleaved by columns, left view on even columns and left view on odd columns (forces views=2) +-* rows: images are interleaved by columns, left view on even rows and left view on odd rows (forces views=2) +-* spv5: images are interleaved by for SpatialView 5 views display, fullscreen mode (forces views=5) +-* alio8: images are interleaved by for Alioscopy 8 views displays, fullscreen mode (forces views=8) +-* custom: images are interleaved according to the shader file indicated in [mvshader](#mvshader). The shader is exposed each view as uniform sampler2D gfViewX, where X is the view number starting from the left ++ ++- none: no stereo ++- side: images are displayed side by side from left to right ++- top: images are displayed from top (laft view) to bottom (right view) ++- hmd: same as side except that view aspect ratio is not changed ++- ana: standard color anaglyph (red for left view, green and blue for right view) is used (forces views=2) ++- cols: images are interleaved by columns, left view on even columns and left view on odd columns (forces views=2) ++- rows: images are interleaved by columns, left view on even rows and left view on odd rows (forces views=2) ++- spv5: images are interleaved by for SpatialView 5 views display, fullscreen mode (forces views=5) ++- alio8: images are interleaved by for Alioscopy 8 views displays, fullscreen mode (forces views=8) ++- custom: images are interleaved according to the shader file indicated in [mvshader](#mvshader). The shader is exposed each view as uniform sampler2D gfViewX, where X is the view number starting from the left + + __mvshader__ (str, updatable): file path to the custom multiview interleaving shader + __fpack__ (enum, default: _none_, updatable): default frame packing of input video +-* none: no frame packing +-* top: top bottom frame packing +-* side: side by side packing ++ ++- none: no frame packing ++- top: top bottom frame packing ++- side: side by side packing + + __camlay__ (enum, default: _offaxis_, updatable): camera layout in multiview modes +-* straight: camera is moved along a straight line, no rotation +-* offaxis: off-axis projection is used +-* linear: camera is moved along a straight line with rotation +-* circular: camera is moved along a circle with rotation ++ ++- straight: camera is moved along a straight line, no rotation ++- offaxis: off-axis projection is used ++- linear: camera is moved along a straight line with rotation ++- circular: camera is moved along a circle with rotation + + __iod__ (flt, default: _6.4_, updatable): inter-ocular distance (eye separation) in cm (distance between the cameras). + __rview__ (bool, default: _false_, updatable): reverse view order +@@ -205,9 +226,10 @@ Whether the setting is applied or not depends on the graphics module and player + __tvtn__ (uint, default: _30_, updatable): number of point sampling for tile visibility algorithm + __tvtt__ (uint, default: _8_, updatable): number of points above which the tile is considered visible + __tvtd__ (enum, default: _off_, updatable): debug tiles and full coverage SRD +-* off: regular draw +-* partial: only displaying partial tiles, not the full sphere video +-* full: only display the full sphere video ++ ++- off: regular draw ++- partial: only displaying partial tiles, not the full sphere video ++- full: only display the full sphere video + + __tvtf__ (bool, default: _false_, updatable): force all tiles to be considered visible, regardless of viewpoint + __fov__ (flt, default: _1.570796326794897_, updatable): default field of view for VR +@@ -221,17 +243,19 @@ Whether the setting is applied or not depends on the graphics module and player + __dpi__ (v2di, default: _96x96_, updatable): default dpi if not indicated by video output + __dbgpvr__ (flt, default: _0_, updatable): debug scene used by PVR addon + __player__ (enum, default: _no_): set compositor in player mode +-* no: regular mode +-* base: player mode +-* gui: player mode with GUI auto-start ++ ++- no: regular mode ++- base: player mode ++- gui: player mode with GUI auto-start + + __noaudio__ (bool, default: _false_): disable audio output + __opfmt__ (pfmt, default: _none_, Enum: none|yuv420|yvu420|yuv420_10|yuv422|yuv422_10|yuv444|yuv444_10|uyvy|vyuy|yuyv|yvyu|uyvl|vyul|yuyl|yvyl|nv12|nv21|nv1l|nv2l|yuva|yuvd|yuv444a|yuv444p|v308|yuv444ap|v408|v410|v210|grey|algr|gral|rgb4|rgb5|rgb6|rgba|argb|bgra|abgr|rgb|bgr|xrgb|rgbx|xbgr|bgrx|rgbd|rgbds|uncv): pixel format to use for output. Ignored in [player](#player) mode + + __drv__ (enum, default: _auto_): indicate if graphics driver should be used +-* no: never loads a graphics driver, software blit is used, no 3D possible (in player mode, disables OpenGL) +-* yes: always loads a graphics driver, output pixel format will be RGB (in player mode, same as `auto`) +-* auto: decides based on the loaded content ++ ++- no: never loads a graphics driver, software blit is used, no 3D possible (in player mode, disables OpenGL) ++- yes: always loads a graphics driver, output pixel format will be RGB (in player mode, same as `auto`) ++- auto: decides based on the loaded content + + __src__ (cstr): URL of source content + __gaze_x__ (sint, default: _0_, updatable): horizontal gaze coordinate (0=left, width=right) +diff --git a/docs/Filters/core_config.md b/docs/Filters/core_config.md +index 497284ff..094db8de 100644 +--- a/docs/Filters/core_config.md ++++ b/docs/Filters/core_config.md +@@ -1,20 +1,24 @@ + + +-# Configuration file ++# Configuration file {:data-level="all"} + + GPAC uses a configuration file to modify default options of libgpac and filters. This file is called `GPAC.cfg` and is located: ++ + - on Windows platforms, in `C:\Users\FOO\AppData\Roaming\GPAC` or in `C:\Program Files\GPAC`. + - on iOS platforms, in a .gpac folder in the app storage directory. + - on Android platforms, in `/sdcard/GPAC/` if this directory exists, otherwise in `/data/data/io.gpac.gpac/GPAC`. + - on other platforms, in a `$HOME/.gpac/`. ++ + + Applications in GPAC can also specify a different configuration file through the [-p](gpac_general/#p) profile option. EX gpac -p=foo [] + This will load configuration from $HOME/.gpac/foo/GPAC.cfg, creating it if needed. + The reserved name `0` is used to disable configuration file writing. + + The configuration file is structured in sections, each made of one or more keys: ++ + - section `foo` is declared as `[foo]\n` + - key `bar` with value `N` is declared as `bar=N\n`. The key value `N` is not interpreted and always handled as ASCII text. ++ + + By default the configuration file only holds a few system specific options and directories. It is possible to serialize the entire set of options to the configuration file, using [-wc](gpac_general/#wc) [-wf](gpac_general/#wf). + This should be avoided as the resulting configuration file size will be quite large, hence larger memory usage for the applications. +@@ -29,6 +33,7 @@ Example + [core] + threads=2 + ``` ++ + Setting this in the config file is equivalent to using `-threads=2`. + The options specified at prompt overrides the value of the config file. + +@@ -40,6 +45,7 @@ Example + [filter@rtpin] + interleave=yes + ``` ++ + This will force the rtp input filter to always request RTP over RTSP by default. + To generate a configuration file with all filters options serialized, use [-wf](gpac_general/#wf). + +@@ -51,11 +57,13 @@ Example + ``` + --buffer=100 -i file vout aout + ``` ++ + This is equivalent to specifying `vout:buffer=100 aout:buffer=100`. + Example + ``` + --buffer=100 -i file vout aout:buffer=10 + ``` ++ + This is equivalent to specifying `vout:buffer=100 aout:buffer=10`. + + __Warning: This syntax only applies to regular filter options. It cannot be used with builtin shortcuts (gfreg, enc, ...).__ +@@ -65,6 +73,7 @@ Example + ``` + --profile=Baseline -i file.cmp -o dump.264 + ``` ++ + This is equivalent to specifying `-o dump.264:profile=Baseline`. + + For both syntaxes, it is possible to specify the filter registry name of the option, using `--FNAME:OPTNAME=VAL` or `--FNAME@OPTNAME=VAL`. +@@ -73,4 +82,5 @@ Example + ``` + --flist@timescale=100 -i plist1 -i plist2 -o live.mpd + ``` ++ + This will set the timescale option on the playlists filters but not on the dasher filter. +diff --git a/docs/Filters/core_logs.md b/docs/Filters/core_logs.md +index cf6bbf54..e1ff0ae4 100644 +--- a/docs/Filters/core_logs.md ++++ b/docs/Filters/core_logs.md +@@ -1,7 +1,7 @@ + + # GPAC Log System + +-# libgpac logs options: ++# libgpac logs options: {:data-level="all"} + + __-noprog__: disable progress messages + __-quiet__: disable all messages, including errors +@@ -13,40 +13,44 @@ + You can independently log different tools involved in a session. + log_args is formatted as a colon (':') separated list of `toolX[:toolZ]@levelX` + `levelX` can be one of: +-* quiet: skip logs +-* error: logs only error messages +-* warning: logs error+warning messages +-* info: logs error+warning+info messages +-* debug: logs all messages ++ ++- quiet: skip logs ++- error: logs only error messages ++- warning: logs error+warning messages ++- info: logs error+warning+info messages ++- debug: logs all messages ++ + + `toolX` can be one of: +-* core: libgpac core +-* mutex: log all mutex calls +-* mem: GPAC memory tracker +-* module: GPAC modules (av out, font engine, 2D rasterizer) +-* filter: filter session debugging +-* sched: filter session scheduler debugging +-* codec: codec messages (used by encoder and decoder filters) +-* coding: bitstream formats (audio, video, scene) +-* container: container formats (ISO File, MPEG-2 TS, AVI, ...) and multiplexer/demultiplexer filters +-* network: TCP/UDP sockets and TLS +-* http: HTTP traffic +-* cache: HTTP cache subsystem +-* rtp: RTP traffic +-* dash: HTTP streaming logs +-* route: ROUTE (ATSC3) debugging +-* media: messages from generic filters and reframer/rewriter filters +-* parser: textual parsers (svg, xmt, bt, ...) +-* mmio: I/O management (AV devices, file, pipes, OpenGL) +-* audio: audio renderer/mixer/output +-* script: script engine except console log +-* console: script console log +-* scene: scene graph and scene manager +-* compose: composition engine (2D, 3D, etc) +-* ctime: media and SMIL timing info from composition engine +-* interact: interaction messages (UI events and triggered DOM events and VRML route) +-* rti: run-time stats of compositor +-* all: all tools logged - other tools can be specified afterwards. ++ ++- core: libgpac core ++- mutex: log all mutex calls ++- mem: GPAC memory tracker ++- module: GPAC modules (av out, font engine, 2D rasterizer) ++- filter: filter session debugging ++- sched: filter session scheduler debugging ++- codec: codec messages (used by encoder and decoder filters) ++- coding: bitstream formats (audio, video, scene) ++- container: container formats (ISO File, MPEG-2 TS, AVI, ...) and multiplexer/demultiplexer filters ++- network: TCP/UDP sockets and TLS ++- http: HTTP traffic ++- cache: HTTP cache subsystem ++- rtp: RTP traffic ++- dash: HTTP streaming logs ++- route: ROUTE (ATSC3) debugging ++- media: messages from generic filters and reframer/rewriter filters ++- parser: textual parsers (svg, xmt, bt, ...) ++- mmio: I/O management (AV devices, file, pipes, OpenGL) ++- audio: audio renderer/mixer/output ++- script: script engine except console log ++- console: script console log ++- scene: scene graph and scene manager ++- compose: composition engine (2D, 3D, etc) ++- ctime: media and SMIL timing info from composition engine ++- interact: interaction messages (UI events and triggered DOM events and VRML route) ++- rti: run-time stats of compositor ++- all: all tools logged - other tools can be specified afterwards. ++ + The special keyword `ncl` can be set to disable color logs. + The special keyword `strict` can be set to exit at first error. + +@@ -54,6 +58,7 @@ Example + ``` + -logs=all@info:dash@debug:ncl + ``` ++ + This moves all log to info level, dash to debug level and disable color logs + + __-proglf__: use new line at each progress messages +diff --git a/docs/Filters/core_options.md b/docs/Filters/core_options.md +index 083c90c8..10ea9de9 100644 +--- a/docs/Filters/core_options.md ++++ b/docs/Filters/core_options.md +@@ -1,7 +1,7 @@ + +-# GPAC Core Options ++# GPAC Core Options + +-# libgpac core options: ++# libgpac core options: {:data-level="all"} + + __-noprog__: disable progress messages + __-quiet__: disable all messages, including errors +@@ -14,12 +14,13 @@ + __-ifce__ (string): set default multicast interface (default is ANY), either an IP address or a device name as listed by `gpac -h net`. Prefix '+' will force using IPv6 for dual interface + __-lang__ (string): set preferred language + __-cfg__,__-opt__ (string): get or set configuration file value. The string parameter can be formatted as: +-* `section:key=val`: set the key to a new value +-* `section:key=null`, `section:key`: remove the key +-* `section=null`: remove the section +-* no argument: print the entire configuration file +-* `section`: print the given section +-* `section:key`: print the given `key` in `section` (section can be set to `*`)- `*:key`: print the given `key` in all sections ++ ++- `section:key=val`: set the key to a new value ++- `section:key=null`, `section:key`: remove the key ++- `section=null`: remove the section ++- no argument: print the entire configuration file ++- `section`: print the given section ++- `section:key`: print the given `key` in `section` (section can be set to `*`)- `*:key`: print the given `key` in all sections + + __-no-save__: discard any changes made to the config file upon exit + __-mod-reload__: unload / reload module shared libs when no longer used +@@ -40,45 +41,50 @@ + __-xml-max-csize__ (int, default: __100k__): maximum XML content or attribute size + __-netcap__ (string): set packet capture and filtering rules formatted as [CFG][RULES]. Each `-netcap` argument will define a configuration + [CFG] is an optional comma-separated list of: +-* id=ID: ID (string) for this configuration. If NULL, configuration will apply to all sockets not specifying a netcap ID +-* src=F: read packets from `F`, as produced by GPAC or a pcap or pcapng file +-* dst=F: output packets to `F` (GPAC or pcap/pcapng file), cannot be set if src is set +-* loop[=N]: loop capture file N times, or forever if N is not set or negative +-* nrt: disable real-time playback ++ ++- id=ID: ID (string) for this configuration. If NULL, configuration will apply to all sockets not specifying a netcap ID ++- src=F: read packets from `F`, as produced by GPAC or a pcap or pcapng file ++- dst=F: output packets to `F` (GPAC or pcap/pcapng file), cannot be set if src is set ++- loop[=N]: loop capture file N times, or forever if N is not set or negative ++- nrt: disable real-time playback ++ + [RULES] is an optional list of `[OPT,OPT2...]` with OPT in: +-* m=K: set rule mode - `K` can be `r` for reception only (default), `w` for send only or `rw` for both +-* s=K: set packet start range to `K` +-* e=K: set packet end range to `K` - only used for `r` and `f` rules, 0 or not set means rule apply until end +-* n=K: set number of packets to drop to `K` - not set, 0 or 1 means single packet +-* r=K: random drop `n` packet every `K` +-* f=K: drop first `n` packets every `K` +-* d=K: reorder `n` packets after the next `K` packets, can be used with `f` or `r` rules +-* p=K: filter packets on port `K` only, if not set the rule applies to all packets +-* o=K: patch packet instead of droping (always true for TCP), replacing byte at offset `K` (0 is first byte, <0 for random) +-* v=K: set patch byte value to `K` (hexa) or negative value for random (default) ++ ++- m=K: set rule mode - `K` can be `r` for reception only (default), `w` for send only or `rw` for both ++- s=K: set packet start range to `K` ++- e=K: set packet end range to `K` - only used for `r` and `f` rules, 0 or not set means rule apply until end ++- n=K: set number of packets to drop to `K` - not set, 0 or 1 means single packet ++- r=K: random drop `n` packet every `K` ++- f=K: drop first `n` packets every `K` ++- d=K: reorder `n` packets after the next `K` packets, can be used with `f` or `r` rules ++- p=K: filter packets on port `K` only, if not set the rule applies to all packets ++- o=K: patch packet instead of droping (always true for TCP), replacing byte at offset `K` (0 is first byte, <0 for random) ++- v=K: set patch byte value to `K` (hexa) or negative value for random (default) ++ + + Example + ``` + -netcap=dst=dump.gpc +-``` ++``` ++ + This will record packets to dump.gpc + + Example + ``` + -netcap=src=dump.gpc,id=NC1 -i session1.sdp:NCID=NC1 -i session2.sdp +-``` ++``` ++ + This will read packets from dump.gpc only for session1.sdp and let session2.sdp use regular sockets + + Example + ``` + -netcap=[p=1234,s=100,n=20][r=200,s=500,o=10,v=FE] +-``` ++``` ++ + This will use regular network interface and drop packets 100 to 119 on port 1234 and patch one random packet every 200 starting from packet 500, setting byte 10 to FE + + __-cache__ (string): cache directory location +-__-proxy-on__: enable HTTP proxy +-__-proxy-name__ (string): set HTTP proxy address +-__-proxy-port__ (int, default: __80__): set HTTP proxy port ++__-proxy__ (string): set HTTP proxy server address and port + __-maxrate__ (int): set max HTTP download rate in bits per sec. 0 means unlimited + __-no-cache__: disable HTTP caching + __-offline-cache__: enable offline HTTP caching (no re-validation of existing resource in cache) +@@ -95,25 +101,33 @@ This will use regular network interface and drop packets 100 to 119 on port 1234 + __-dm-threads__: force using threads for async download requests rather than session scheduler + __-cte-rate-wnd__ (int, default: __20__): set window analysis length in milliseconds for chunk-transfer encoding rate estimation + __-cred__ (string): path to 128 bits key for credential storage ++__-no-h2__: disable HTTP2 ++__-no-h2c__: disable HTTP2 upgrade (i.e. over non-TLS) ++__-h2-copy__: enable intermediate copy of data in nghttp2 (default is disabled but may report as broken frames in wireshark) ++__-curl__: use CURL instead of GPAC HTTP stack ++__-no-h3__: disable HTTP3 (CURL only) + __-dbg-edges__: log edges status in filter graph before dijkstra resolution (for debug). Edges are logged as edge_source(status(disable_depth), weight, src_cap_idx -> dst_cap_idx) + __-full-link__: throw error if any PID in the filter graph cannot be linked + __-no-dynf__: disable dynamically loaded filters + __-no-block__ (Enum, default: __no__): disable blocking mode of filters +-* no: enable blocking mode +-* fanout: disable blocking on fan-out, unblocking the PID as soon as one of its destinations requires a packet +-* all: disable blocking ++ ++- no: enable blocking mode ++- fanout: disable blocking on fan-out, unblocking the PID as soon as one of its destinations requires a packet ++- all: disable blocking + + __-no-reg__: disable regulation (no sleep) in session + __-no-reassign__: disable source filter reassignment in PID graph resolution + __-sched__ (Enum, default: __free__): set scheduler mode +-* free: lock-free queues except for task list (default) +-* lock: mutexes for queues when several threads +-* freex: lock-free queues including for task lists (experimental) +-* flock: mutexes for queues even when no thread (debug mode) +-* direct: no threads and direct dispatch of tasks whenever possible (debug mode) ++ ++- free: lock-free queues except for task list (default) ++- lock: mutexes for queues when several threads ++- freex: lock-free queues including for task lists (experimental) ++- flock: mutexes for queues even when no thread (debug mode) ++- direct: no threads and direct dispatch of tasks whenever possible (debug mode) + + __-max-chain__ (int, default: __6__): set maximum chain length when resolving filter links. Default value covers for _[ in -> ] dmx -> reframe -> decode -> encode -> reframe -> mx [ -> out]_. Filter chains loaded for adaptation (e.g. pixel format change) are loaded after the link resolution. Setting the value to 0 disables dynamic link resolution. You will have to specify the entire chain manually + __-max-sleep__ (int, default: __50__): set maximum sleep time slot in milliseconds when regulation is enabled ++__-step-link__: load filters one by one when solvink a link instead of loading all filters for the solved path + __-threads__ (int): set N extra thread for the session. -1 means use all available cores + __-no-probe__: disable data probing on sources and relies on extension (faster load but more error-prone) + __-no-argchk__: disable tracking of argument usage (all arguments will be considered as used) +@@ -135,10 +149,11 @@ This will use regular network interface and drop packets 100 to 119 on port 1234 + __-wait-fonts__: wait for SVG fonts to be loaded before displaying frames + __-webvtt-hours__: force writing hour when serializing WebVTT + __-charset__ (string): set charset when not recognized from input. Possible values are: +-* utf8: force UTF-8 +-* utf16: force UTF-16 little endian +-* utf16be: force UTF-16 big endian +-* other: attempt to parse anyway ++ ++- utf8: force UTF-8 ++- utf16: force UTF-16 little endian ++- utf16be: force UTF-16 big endian ++- other: attempt to parse anyway + + __-rmt__: enable profiling through [Remotery](https://github.com/Celtoys/Remotery). A copy of Remotery visualizer is in gpac/share/vis, usually installed in _/usr/share/gpac/vis_ or _Program Files/GPAC/vis_ + __-rmt-port__ (int, default: __17815__): set remotery port +diff --git a/docs/Filters/cryptin.md b/docs/Filters/cryptin.md +index df8ef72b..5dda470c 100644 +--- a/docs/Filters/cryptin.md ++++ b/docs/Filters/cryptin.md +@@ -1,6 +1,6 @@ + + +-# CryptFile input ++# CryptFile input {:data-level="all"} + + Register name used to load filter: __cryptin__ + This filter is not checked during graph resolution and needs explicit loading. +diff --git a/docs/Filters/cryptout.md b/docs/Filters/cryptout.md +index 01a22fd2..f6ca07ff 100644 +--- a/docs/Filters/cryptout.md ++++ b/docs/Filters/cryptout.md +@@ -1,6 +1,6 @@ + + +-# CryptFile output ++# CryptFile output {:data-level="all"} + + Register name used to load filter: __cryptout__ + This filter is not checked during graph resolution and needs explicit loading. +diff --git a/docs/Filters/dasher.md b/docs/Filters/dasher.md +index 153274cb..8b354f87 100644 +--- a/docs/Filters/dasher.md ++++ b/docs/Filters/dasher.md +@@ -1,6 +1,6 @@ + + +-# DASH and HLS segmenter ++# DASH and HLS segmenter {:data-level="all"} + + Register name used to load filter: __dasher__ + This filter may be automatically loaded during graph resolution. +@@ -8,12 +8,14 @@ This filter requires the graph resolver to be activated. + + This filter provides segmentation and manifest generation for MPEG-DASH and HLS formats. + The segmenter currently supports: ++ + - MPD and m3u8 generation (potentially in parallel) + - ISOBMFF, MPEG-2 TS, MKV and raw bitstream segment formats + - override of profiles and levels in manifest for codecs + - most MPEG-DASH profiles + - static and dynamic (live) manifest offering + - context store and reload for batch processing of live/dynamic sessions ++ + + The filter does perform per-segment real-time regulation using [sreg](#sreg). + If you need per-frame real-time regulation on non-real-time inputs, insert a [reframer](reframer) before to perform real-time regulation. +@@ -36,45 +38,55 @@ template=Great_$File$_$Width$ + If input is `foo.mp4` with `640x360` video resolution, this will resolve in `Great_foo_640.mp4` for onDemand case. + + Standard DASH replacement strings: +-* $Number[%%0Nd]$: replaced by the segment number, possibly prefixed with 0 +-* $RepresentationID$: replaced by representation name +-* $Time$: replaced by segment start time +-* $Bandwidth$: replaced by representation bandwidth. ++ ++- $Number[%%0Nd]$: replaced by the segment number, possibly prefixed with 0 ++- $RepresentationID$: replaced by representation name ++- $Time$: replaced by segment start time ++- $Bandwidth$: replaced by representation bandwidth. ++ + _Note: these strings are not replaced in the manifest templates elements._ + + Additional replacement strings (not DASH, not generic GPAC replacements but may occur multiple times in template): +-* $Init=NAME$: replaced by NAME for init segment, ignored otherwise +-* $XInit=NAME$: complete replace by NAME for init segment, ignored otherwise +-* $InitExt=EXT$: replaced by EXT for init segment file extensions, ignored otherwise +-* $Index=NAME$: replaced by NAME for index segments, ignored otherwise +-* $Path=PATH$: replaced by PATH when creating segments, ignored otherwise +-* $Segment=NAME$: replaced by NAME for media segments, ignored for init segments +-* $SegExt=EXT$: replaced by EXT for media segment file extensions, ignored for init segments +-* $FS$ (FileSuffix): replaced by `_trackN` in case the input is an AV multiplex, or kept empty otherwise ++ ++- $Init=NAME$: replaced by NAME for init segment, ignored otherwise ++- $XInit=NAME$: complete replace by NAME for init segment, ignored otherwise ++- $InitExt=EXT$: replaced by EXT for init segment file extensions, ignored otherwise ++- $Index=NAME$: replaced by NAME for index segments, ignored otherwise ++- $Path=PATH$: replaced by PATH when creating segments, ignored otherwise ++- $Segment=NAME$: replaced by NAME for media segments, ignored for init segments ++- $SegExt=EXT$: replaced by EXT for media segment file extensions, ignored for init segments ++- $FS$ (FileSuffix): replaced by `_trackN` in case the input is an AV multiplex, or kept empty otherwise ++ + _Note: these strings are replaced in the manifest templates elements._ + + ## PID assignment and configuration + To assign PIDs into periods and adaptation sets and configure the session, the segmenter looks for the following properties on each input PID: +-* `Representation`: assigns representation ID to input PID. If not set, the default behavior is to have each media component in different adaptation sets. Setting the `Representation` allows explicit multiplexing of the source(s) +-* `Period`: assigns period ID to input PID. If not set, the default behavior is to have all media in the same period with the same start time +-* `PStart`: assigns period start. If not set, 0 is assumed, and periods appear in the Period ID declaration order. If negative, this gives the period order (-1 first, then -2 ...). If positive, this gives the true start time and will abort DASHing at period end ++ ++- `Representation`: assigns representation ID to input PID. If not set, the default behavior is to have each media component in different adaptation sets. Setting the `Representation` allows explicit multiplexing of the source(s) ++- `Period`: assigns period ID to input PID. If not set, the default behavior is to have all media in the same period with the same start time ++- `PStart`: assigns period start. If not set, 0 is assumed, and periods appear in the Period ID declaration order. If negative, this gives the period order (-1 first, then -2 ...). If positive, this gives the true start time and will abort DASHing at period end ++ + _Note: When both positive and negative values are found, the by-order periods (negative) will be inserted AFTER the timed period (positive)_ +-* `ASID`: assigns parent adaptation set ID. If not 0, only sources with same AS ID will be in the same adaptation set ++ ++- `ASID`: assigns parent adaptation set ID. If not 0, only sources with same AS ID will be in the same adaptation set ++ + _Note: If multiple streams in source, only the first stream will have an AS ID assigned_ +-* `xlink`: for remote periods, only checked for null PID +-* `Role`, `PDesc`, `ASDesc`, `ASCDesc`, `RDesc`: various descriptors to set for period, AS or representation +-* `BUrl`: overrides segmenter [-base] with a set of BaseURLs to use for the PID (per representation) +-* `Template`: overrides segmenter [template](#template) for this PID +-* `DashDur`: overrides segmenter segment duration for this PID +-* `StartNumber`: sets the start number for the first segment in the PID, default is 1 +-* `IntraOnly`: indicates input PID follows HLS EXT-X-I-FRAMES-ONLY guidelines +-* `CropOrigin`: indicates x and y coordinates of video for SRD (size is video size) +-* `SRD`: indicates SRD position and size of video for SRD, ignored if `CropOrigin` is set +-* `SRDRef`: indicates global width and height of SRD, ignored if `CropOrigin` is set +-* `HLSPL`: name of variant playlist, can use templates +-* `HLSMExt`: list of extensions to add to master playlist entries, ['foo','bar=val'] added as `,foo,bar=val` +-* `HLSVExt`: list of extensions to add to variant playlist, ['#foo','#bar=val'] added as `#foo \n #bar=val` +-* Non-dash properties: `Bitrate`, `SAR`, `Language`, `Width`, `Height`, `SampleRate`, `NumChannels`, `Language`, `ID`, `DependencyID`, `FPS`, `Interlaced`, `Codec`. These properties are used to setup each representation and can be overridden on input PIDs using the general PID property settings (cf global help). ++ ++- `xlink`: for remote periods, only checked for null PID ++- `Role`, `PDesc`, `ASDesc`, `ASCDesc`, `RDesc`: various descriptors to set for period, AS or representation ++- `BUrl`: overrides segmenter [-base] with a set of BaseURLs to use for the PID (per representation) ++- `Template`: overrides segmenter [template](#template) for this PID ++- `DashDur`: overrides segmenter segment duration for this PID ++- `StartNumber`: sets the start number for the first segment in the PID, default is 1 ++- `IntraOnly`: indicates input PID follows HLS EXT-X-I-FRAMES-ONLY guidelines ++- `CropOrigin`: indicates x and y coordinates of video for SRD (size is video size) ++- `SRD`: indicates SRD position and size of video for SRD, ignored if `CropOrigin` is set ++- `SRDRef`: indicates global width and height of SRD, ignored if `CropOrigin` is set ++- `HLSPL`: name of variant playlist, can use templates ++- `HLSMExt`: list of extensions to add to master playlist entries, ['foo','bar=val'] added as `,foo,bar=val` ++- `HLSVExt`: list of extensions to add to variant playlist, ['#foo','#bar=val'] added as `#foo \n #bar=val` ++- Non-dash properties: `Bitrate`, `SAR`, `Language`, `Width`, `Height`, `SampleRate`, `NumChannels`, `Language`, `ID`, `DependencyID`, `FPS`, `Interlaced`, `Codec`. These properties are used to setup each representation and can be overridden on input PIDs using the general PID property settings (cf global help). ++ + + Example + ``` +@@ -149,19 +161,25 @@ This will put video segments and playlist in `dash/video/` and audio segments an + ## Segmentation + The default behavior of the segmenter is to estimate the theoretical start time of each segment based on target segment duration, and start a new segment when a packet with SAP type 1,2,3 or 4 with time greater than the theoretical time is found. + This behavior can be changed to find the best SAP packet around a segment theoretical boundary using [sbound](#sbound): +-* `closest` mode: the segment will start at the closest SAP of the theoretical boundary +-* `in` mode: the segment will start at or before the theoretical boundary ++ ++- `closest` mode: the segment will start at the closest SAP of the theoretical boundary ++- `in` mode: the segment will start at or before the theoretical boundary ++ + + __Warning: These modes will introduce delay in the segmenter (typically buffering of one GOP) and should not be used for low-latency modes.__ + + The segmenter can also be configured to: ++ + - completely ignore SAP when segmenting using [sap](#sap). + - ignore SAP on non-video streams when segmenting using [strict_sap](#strict_sap). ++ + + When [seg_sync](#seg_sync) is disabled, the segmenter will by default announce a new segment in the manifest(s) as soon as its size/offset is known or its name is known, but the segment (or part in LL-HLS) may still not be completely written/sent. + This may result in temporary mismatches between segment/part size currently received versus size as advertized in manifest. + When [seg_sync](#seg_sync) is enabled, the segmenter will wait for the last byte of the fragment/segment to be pushed before announcing a new segment in the manifest(s). This can however slightly increase the latency in MPEG-DASH low-latency. + ++When (-sflush)[] is set to `single`, segmentation is skipped and a single segment is generated per input. ++ + ## Dynamic (real-time live) Mode + The dasher does not perform real-time regulation by default. + For regular segmentation, you should enable segment regulation [sreg](#sreg) if your sources are not real-time. +@@ -171,9 +189,11 @@ gpac -i source.mp4 -o live.mpd:segdur=2:profile=live:dmode=dynamic:sreg + ``` + + For low latency segmentation with fMP4, you will need to specify the following options: +-* cdur: set the fMP4 fragment duration +-* asto: set the availability time offset for DASH. This value should be equal or slightly greater than segment duration minus cdur +-* llhls: enable low latency for HLS ++ ++- cdur: set the fMP4 fragment duration ++- asto: set the availability time offset for DASH. This value should be equal or slightly greater than segment duration minus cdur ++- llhls: enable low latency for HLS ++ + + _Note: [llhls](#llhls) does not force `cmaf` mode to allow for multiplexed media in segments but it enforces to `tfdt_traf` in the muxer._ + +@@ -219,15 +239,19 @@ The segmenter can take a list of instructions, or Cues, to use for the segmentat + + Cue files can be specified for the entire segmenter, or per PID using `DashCue` property. + Cues are given in an XML file with a root element called <DASHCues>, with currently no attribute specified. The children are one or more <Stream> elements, with attributes: +-* id: integer for stream/track/PID ID +-* timescale: integer giving the units of following timestamps +-* mode: if present and value is `edit`, the timestamp are in presentation time (edit list applied) otherwise they are in media time +-* ts_offset: integer giving a value (in timescale) to subtract to the DTS/CTS values listed ++ ++- id: integer for stream/track/PID ID ++- timescale: integer giving the units of following timestamps ++- mode: if present and value is `edit`, the timestamp are in presentation time (edit list applied) otherwise they are in media time ++- ts_offset: integer giving a value (in timescale) to subtract to the DTS/CTS values listed ++ + + The children of <Stream> are one or more <Cue> elements, with attributes: +-* sample: integer giving the sample/frame number of a sample at which splitting shall happen +-* dts: long integer giving the decoding time stamp of a sample at which splitting shall happen +-* cts: long integer giving the composition / presentation time stamp of a sample at which splitting shall happen ++ ++- sample: integer giving the sample/frame number of a sample at which splitting shall happen ++- dts: long integer giving the decoding time stamp of a sample at which splitting shall happen ++- cts: long integer giving the composition / presentation time stamp of a sample at which splitting shall happen ++ + + __Warning: Cues shall be listed in decoding order.__ + +@@ -246,13 +270,17 @@ The segmenter can be used to generate manifests from already fragmented ISOBMFF + In this case, segment boundaries are attached to each packet starting a segment and used to drive the segmentation. + This can be used with single-track ISOBMFF sources, either single file or multi file. + For single file source: ++ + - if onDemand [profile](#profile) is requested, sources have to be formatted as a DASH self-initializing media segment with the proper sidx. + - templates are disabled. + - [sseg](#sseg) is forced for all profiles except onDemand ones. ++ + For multi files source: ++ + - input shall be a playlist containing the initial file followed by the ordered list of segments. + - if no [template](#template) is provided, the full or main [profile](#profile) will be used +-* if [-template]() is provided, it shall be correct: the filter will not try to guess one from the input file names and will not validate it either. ++- if [-template]() is provided, it shall be correct: the filter will not try to guess one from the input file names and will not validate it either. ++ + + The manifest generation-only mode supports both MPD and HLS generation. + +@@ -285,9 +313,11 @@ The segmentation logic is not changed, and packets are forwarded with the same i + Output PIDs are forwarded with `DashCue=inband` property, so that any subsequent dasher follows the same segmentation process (see above). + + The first packet in a segment has: ++ + - property `FileNumber` (and, if multiple files, `FileName`) set as usual + - property `CueStart` set + - property `DFPStart=0` set if this is the first packet in a period ++ + + This mode can be used to pre-segment the streams for later processing that must take place before final dashing. + Example +@@ -300,9 +330,11 @@ Example + gpac -i s1.mp4 -i s2.mp4:#CryptInfo=clear:#Period=3 -i s3.mp4:#Period=3 dasher:gencues cecrypt:cfile=roll_period.xml -o live.mpd + ``` + If the DRM file uses `keyRoll=period`, this will generate: ++ + - first period crypted with one key + - second period clear + - third period crypted with another key ++ + + ## Forced-Template mode + When [tpl_force](#tpl_force) is set, the [template](#template) string is not analyzed nor modified for missing elements. +@@ -315,6 +347,46 @@ This will trash the manifest and open `mypipe` as destination for the muxer resu + + __Warning: Options for segment destination cannot be set through the [template](#template), global options must be used.__ + ++## Batch Operations ++The segmentation can be performed in multiple calls using a DASH context set with [state](#state). ++Between calls, the PIDs are reassigned by checking that the PID ID match between the calls and: ++ ++- the input file names match between the calls ++- or the representation ID (and period ID if specified) match between the calls ++ ++ ++If a PID is not matched, it will be assigned to a new period. ++ ++The default behaviour assume that the same inputs are used for segmentation and rebuilds a contiguous timeline at each new file start. ++If the inputs change but form a continuous timeline, [-keep_ts])() must be used to skip timeline reconstruction. ++ ++The inputs will be segmented for a duration of [subdur](#subdur) if set, otherwise the input media duration. ++When inputs are over, they are restarted if [loop](#loop) is set otherwise a new period is created. ++To avoid this behaviour, the [sflush](#sflush) option should be set to `end` or `single`, indicating that further sources for the same representations will be added in subsequent calls. When [sflush](#sflush) is not `off`, the (-loop)[] option is ignored. ++ ++Example ++``` ++gpac -i SRC -o dash.mpd:segdur=2:state=CTX && gpac -i SRC -o dash.mpd:segdur=2:state=CTX ++``` ++This will generate all dash segments for `SRC` (last one possibly shorter) and create a new period at end of input. ++Example ++``` ++gpac -i SRC -o dash.mpd:segdur=2:state=CTX:loop && gpac -i SRC -o dash.mpd:segdur=2:state=CTX:loop ++``` ++This will generate all dash segments for `SRC` and restart `SRC` to fill-up last segment. ++Example ++``` ++gpac -i SRC -o dash.mpd:segdur=2:state=CTX:sflush=end && gpac -i SRC -o dash.mpd:segdur=2:state=CTX:sflush=end ++``` ++This will generate all dash segments for `SRC` without looping/closing the period at end of input. Timestamps in the second call will be rewritten to be contiguous with timestamp at end of first call. ++Example ++``` ++gpac -i SRC1 -o dash.mpd:segdur=2:state=CTX:sflush=end:keep_ts && gpac -i SRC2 -o dash.mpd:segdur=2:state=CTX:sflush=end:keep_ts ++``` ++This will generate all dash segments for `SRC1` without looping/closing the period at end of input, then for `SRC2`. Timestamps of the sources will not be rewritten. ++ ++_Note: The default behaviour of MP4Box `-dash-ctx` option is to set the (-loop)[] to true._ ++ + ## Output redirecting + When loaded implicitly during link resolution, the dasher will only link its outputs to the target sink + Example +@@ -335,25 +407,32 @@ When explicitly loading the filter, the [dual](#dual) option will be disabled un + + ## Multiplexer development considerations + Output multiplexers allowing segmented output must obey the following: ++ + - inspect packet properties +- * FileNumber: if set, indicate the start of a new DASH segment +- * FileName: if set, indicate the file name. If not present, output shall be a single file. This is only set for packet carrying the `FileNumber` property, and only on one PID (usually the first) for multiplexed outputs +- * IDXName: gives the optional index name. If not present, index shall be in the same file as dash segment. Only used for MPEG-2 TS for now +- * EODS: property is set on packets with no payload and no timestamp to signal the end of a DASH segment. This is only used when stopping/resuming the segmentation process, in order to flush segments without dispatching an EOS (see [subdur](#subdur) ) ++ ++ - FileNumber: if set, indicate the start of a new DASH segment ++ - FileName: if set, indicate the file name. If not present, output shall be a single file. This is only set for packet carrying the `FileNumber` property, and only on one PID (usually the first) for multiplexed outputs ++ - IDXName: gives the optional index name. If not present, index shall be in the same file as dash segment. Only used for MPEG-2 TS for now ++ - EODS: property is set on packets with no payload and no timestamp to signal the end of a DASH segment. This is only used when stopping/resuming the segmentation process, in order to flush segments without dispatching an EOS (see [subdur](#subdur) ) ++ + - for each segment done, send a downstream event on the first connected PID signaling the size of the segment and the size of its index if any + - for multiplexers with init data, send a downstream event signaling the size of the init and the size of the global index if any + - the following filter options are passed to multiplexers, which should declare them as arguments: +- * noinit: disables output of init segment for the multiplexer (used to handle bitstream switching with single init in DASH) +- * frag: indicates multiplexer shall use fragmented format (used for ISOBMFF mostly) +- * subs_sidx=0: indicates an SIDX shall be generated - only added if not already specified by user +- * xps_inband=all|no|both: indicates AVC/HEVC/... parameter sets shall be sent inband, out of band, or both +- * nofragdef: indicates fragment defaults should be set in each segment rather than in init segment ++ ++ - noinit: disables output of init segment for the multiplexer (used to handle bitstream switching with single init in DASH) ++ - frag: indicates multiplexer shall use fragmented format (used for ISOBMFF mostly) ++ - subs_sidx=0: indicates an SIDX shall be generated - only added if not already specified by user ++ - xps_inband=all|no|both: indicates AVC/HEVC/... parameter sets shall be sent inband, out of band, or both ++ - nofragdef: indicates fragment defaults should be set in each segment rather than in init segment ++ + + The segmenter adds the following properties to the output PIDs: +-* DashMode: identifies VoD (single file with global index) or regular DASH mode used by segmenter +-* DashDur: identifies target DASH segment duration - this can be used to estimate the SIDX size for example +-* LLHLS: identifies LLHLS is used; the multiplexer must send fragment size events back to the dasher, and set `LLHLSFragNum` on the first packet of each fragment +-* SegSync: indicates that fragments/segments must be completely flushed before sending back size events ++ ++- DashMode: identifies VoD (single file with global index) or regular DASH mode used by segmenter ++- DashDur: identifies target DASH segment duration - this can be used to estimate the SIDX size for example ++- LLHLS: identifies LLHLS is used; the multiplexer must send fragment size events back to the dasher, and set `LLHLSFragNum` on the first packet of each fragment ++- SegSync: indicates that fragments/segments must be completely flushed before sending back size events ++ + + + # Options +@@ -362,10 +441,11 @@ The segmenter adds the following properties to the output PIDs: + __tpl__ (bool, default: _true_): use template mode (multiple segment, template URLs) + __stl__ (bool, default: _false_): use segment timeline (ignored in on_demand mode) + __dmode__ (enum, default: _static_, updatable): dash content mode +-* static: static content +-* dynamic: live generation +-* dynlast: last call for live, will turn the MPD into static +-* dynauto: live generation and move to static manifest upon end of stream ++ ++- static: static content ++- dynamic: live generation ++- dynlast: last call for live, will turn the MPD into static ++- dynauto: live generation and move to static manifest upon end of stream + + __sseg__ (bool, default: _false_): single segment is used + __sfile__ (bool, default: _false_): use a single file for all segments (default in on_demand) +@@ -373,59 +453,65 @@ The segmenter adds the following properties to the output PIDs: + __sap__ (bool, default: _true_): enable splitting segments at SAP boundaries + __mix_codecs__ (bool, default: _false_): enable mixing different codecs in an adaptation set + __ntp__ (enum, default: _rem_): insert/override NTP clock at the beginning of each segment +-* rem: removes NTP from all input packets +-* yes: inserts NTP at each segment start +-* keep: leaves input packet NTP untouched ++ ++- rem: removes NTP from all input packets ++- yes: inserts NTP at each segment start ++- keep: leaves input packet NTP untouched + + __no_sar__ (bool, default: _false_): do not check for identical sample aspect ratio for adaptation sets + __bs_switch__ (enum, default: _def_): bitstream switching mode (single init segment) +-* def: resolves to off for onDemand and inband for live +-* off: disables BS switching +-* on: enables it if same decoder configuration is possible +-* inband: moves decoder config inband if possible +-* both: inband and outband parameter sets +-* pps: moves PPS and APS inband, keep VPS,SPS and DCI out of band (used for VVC RPR) +-* force: enables it even if only one representation +-* multi: uses multiple stsd entries in ISOBMFF ++ ++- def: resolves to off for onDemand and inband for live ++- off: disables BS switching ++- on: enables it if same decoder configuration is possible ++- inband: moves decoder config inband if possible ++- both: inband and outband parameter sets ++- pps: moves PPS and APS inband, keep VPS,SPS and DCI out of band (used for VVC RPR) ++- force: enables it even if only one representation ++- multi: uses multiple stsd entries in ISOBMFF + + __template__ (str): template string to use to generate segment name + __segext__ (str): file extension to use for segments + __initext__ (str): file extension to use for the init segment + __muxtype__ (enum, default: _auto_): muxtype to use for the segments +-* mp4: uses ISOBMFF format +-* ts: uses MPEG-2 TS format +-* mkv: uses Matroska format +-* webm: uses WebM format +-* ogg: uses OGG format +-* raw: uses raw media format (disables multiplexed representations) +-* auto: guess format based on extension, default to mp4 if no extension ++ ++- mp4: uses ISOBMFF format ++- ts: uses MPEG-2 TS format ++- mkv: uses Matroska format ++- webm: uses WebM format ++- ogg: uses OGG format ++- raw: uses raw media format (disables multiplexed representations) ++- auto: guess format based on extension, default to mp4 if no extension + + __rawsub__ (bool, default: _no_): use raw subtitle format instead of encapsulating in container + __asto__ (dbl, default: _0_): availabilityStartTimeOffset to use in seconds. A negative value simply increases the AST, a positive value sets the ASToffset to representations + __profile__ (enum, default: _auto_): target DASH profile. This will set default option values to ensure conformance to the desired profile. For MPEG-2 TS, only main and live are used, others default to main +-* auto: turns profile to live for dynamic and full for non-dynamic +-* live: DASH live profile, using segment template +-* onDemand: MPEG-DASH live profile +-* main: MPEG-DASH main profile, using segment list +-* full: MPEG-DASH full profile +-* hbbtv1.5.live: HBBTV 1.5 DASH profile +-* dashavc264.live: DASH-IF live profile +-* dashavc264.onDemand: DASH-IF onDemand profile +-* dashif.ll: DASH IF low-latency profile (set UTC server to time.akamai.com if none set) ++ ++- auto: turns profile to live for dynamic and full for non-dynamic ++- live: DASH live profile, using segment template ++- onDemand: MPEG-DASH live profile ++- main: MPEG-DASH main profile, using segment list ++- full: MPEG-DASH full profile ++- hbbtv1.5.live: HBBTV 1.5 DASH profile ++- dashavc264.live: DASH-IF live profile ++- dashavc264.onDemand: DASH-IF onDemand profile ++- dashif.ll: DASH IF low-latency profile (set UTC server to time.akamai.com if none set) + + __profX__ (str): list of profile extensions, as used by DASH-IF and DVB. The string will be colon-concatenated with the profile used. If starting with `+`, the profile string by default is erased and `+` is skipped + __cp__ (enum, default: _set_): content protection element location +-* set: in adaptation set element +-* rep: in representation element +-* both: in both adaptation set and representation elements ++ ++- set: in adaptation set element ++- rep: in representation element ++- both: in both adaptation set and representation elements + + __pssh__ (enum, default: _v_): storage mode for PSSH box +-* f: stores in movie fragment only +-* v: stores in movie only, or movie and fragments if key roll is detected +-* m: stores in mpd only +-* mf: stores in mpd and movie fragment +-* mv: stores in mpd and movie +-* n: discard pssh from mpd and segments ++ ++- f: stores in movie fragment only ++- v: stores in movie only, or movie and fragments if key roll is detected ++- m: stores in mpd only ++- mf: stores in mpd and movie fragment ++- mv: stores in mpd and movie ++- n: discard pssh from mpd and segments + + __buf__ (sint, default: _-100_): min buffer duration in ms. negative value means percent of segment duration (e.g. -150 = 1.5*seg_dur) + __spd__ (sint, default: _0_): suggested presentation delay in ms +@@ -442,78 +528,94 @@ The segmenter adds the following properties to the output PIDs: + __refresh__ (dbl, default: _0_): refresh rate for dynamic manifests, in seconds (a negative value sets the MPD duration, value 0 uses dash duration) + __tsb__ (dbl, default: _30_): time-shift buffer depth in seconds (a negative value means infinity) + __keep_segs__ (bool, default: _false_): do not delete segments no longer in time-shift buffer +-__subdur__ (dbl, default: _0_): maximum duration of the input file to be segmented. This does not change the segment duration, segmentation stops once segments produced exceeded the duration + __ast__ (str): set start date (as xs:date, e.g. YYYY-MM-DDTHH:MM:SSZ) for live mode. Default is now. !! Do not use with multiple periods, nor when DASH duration is not a multiple of GOP size !! + __state__ (str): path to file used to store/reload state info when simulating live. This is stored as a valid MPD with GPAC XML extensions ++__keep_ts__ (bool, default: _false_): do not shift timestamp when reloading a context + __loop__ (bool, default: _false_): loop sources when dashing with subdur and state. If not set, a new period is created once the sources are over ++__subdur__ (dbl, default: _0_): maximum duration of the input file to be segmented. This does not change the segment duration, segmentation stops once segments produced exceeded the duration + __split__ (bool, default: _true_): enable cloning samples for text/metadata/scene description streams, marking further clones as redundant + __hlsc__ (bool, default: _false_): insert clock reference in variant playlist in live HLS + __cues__ (str): set cue file + __strict_cues__ (bool, default: _false_): strict mode for cues, complains if splitting is not on SAP type 1/2/3 or if unused cue is found + __strict_sap__ (enum, default: _off_): strict mode for sap +-* off: ignore SAP types for PID other than video, enforcing _startsWithSAP=1_ +-* sig: same as [off](#off) but keep _startsWithSAP_ to the true SAP value +-* on: warn if any PID uses SAP 3 or 4 and switch to FULL profile +-* intra: ignore SAP types greater than 3 on all media types ++ ++- off: ignore SAP types for PID other than video, enforcing `AdaptationSet@startsWithSAP=1` ++- sig: same as [off](#off) but keep `AdaptationSet@startsWithSAP` to the true SAP value ++- on: warn if any PID uses SAP 3 or 4 and switch to FULL profile ++- intra: ignore SAP types greater than 3 on all media types + +-__subs_sidx__ (sint, default: _-1_): number of subsegments per sidx. negative value disables sidx. Only used to inherit sidx option of destination ++__subs_sidx__ (sint, default: _-1_): number of subsegments per sidx. Negative value disables sidx. Only used to inherit sidx option of destination + __cmpd__ (bool, default: _false_): skip line feed and spaces in MPD XML for compactness + __styp__ (str): indicate the 4CC to use for styp boxes when using ISOBMFF output + __dual__ (bool): indicate to produce both MPD and M3U files + __sigfrag__ (bool): use manifest generation only mode + __sbound__ (enum, default: _out_): indicate how the theoretical segment start `TSS (= segment_number * duration)` should be handled +-* out: segment split as soon as `TSS` is exceeded (`TSS` <= segment_start) +-* closest: segment split at closest SAP to theoretical bound +-* in: `TSS` is always in segment (`TSS` >= segment_start) ++ ++- out: segment split as soon as `TSS` is exceeded (`TSS` <= segment_start) ++- closest: segment split at closest SAP to theoretical bound ++- in: `TSS` is always in segment (`TSS` >= segment_start) + + __reschedule__ (bool, default: _false_): reschedule sources with no period ID assigned once done (dynamic mode only) + __sreg__ (bool, default: _false_): regulate the session ++ + - when using subdur and context, only generate segments from the past up to live edge + - otherwise in dynamic mode without context, do not generate segments ahead of time + + __scope_deps__ (bool, default: _true_): scope PID dependencies to be within source. If disabled, PID dependencies will be checked across all input PIDs regardless of their sources + __utcs__ (str): URL to use as time server / UTCTiming source. Special value `inband` enables inband UTC (same as publishTime), special prefix `xsd@` uses xsDateTime schemeURI rather than ISO +-__force_flush__ (bool, default: _false_): force generating a single segment for each input. This can be useful in batch mode when average source duration is known and used as segment duration but actual duration may sometimes be greater ++__sflush__ (enum, default: _off_): segment flush mode - see filter help: ++ ++- off: no specific actions ++- single: force generating a single segment for each input ++- end: skip loop detection and clamp duration adjustment at end of input, used for state mode ++ + __last_seg_merge__ (bool, default: _false_): force merging last segment if less than half the target duration + __mha_compat__ (enum, default: _no_): adaptation set generation mode for compatible MPEG-H Audio profile +-* no: only generate the adaptation set for the main profile +-* comp: only generate the adaptation sets for all compatible profiles +-* all: generate the adaptation set for the main profile and all compatible profiles ++ ++- no: only generate the adaptation set for the main profile ++- comp: only generate the adaptation sets for all compatible profiles ++- all: generate the adaptation set for the main profile and all compatible profiles + + __mname__ (str): output manifest name for ATSC3 multiplexing (using 'm3u8' only toggles HLS generation) + __llhls__ (enum, default: _off_): HLS low latency type +-* off: do not use LL-HLS +-* br: use LL-HLS with byte-range for segment parts, pointing to full segment (DASH-LL compatible) +-* sf: use separate files for segment parts (post-fixed .1, .2 etc.) +-* brsf: generate two sets of manifest, one for byte-range and one for files (`_IF` added before extension of manifest) ++ ++- off: do not use LL-HLS ++- br: use LL-HLS with byte-range for segment parts, pointing to full segment (DASH-LL compatible) ++- sf: use separate files for segment parts (post-fixed .1, .2 etc.) ++- brsf: generate two sets of manifest, one for byte-range and one for files (`_IF` added before extension of manifest) + + __hlsdrm__ (str): cryp file info for HLS full segment encryption + __hlsx__ (strl): list of string to append to master HLS header before variants with `['#foo','#bar=val']` added as `#foo \n #bar=val` ++__hlsiv__ (bool, default: _true_): inject IV in variant HLS playlist`` + __ll_preload_hint__ (bool, default: _true_): inject preload hint for LL-HLS + __ll_rend_rep__ (bool, default: _true_): inject rendition reports for LL-HLS + __ll_part_hb__ (dbl, default: _-1_): user-defined part hold-back for LLHLS, negative value means 3 times max part duration in session + __ckurl__ (str): set the ClearKey URL common to all encrypted streams (overriden by `CKUrl` pid property) + __hls_absu__ (enum, default: _no_): use absolute url in HLS generation using first URL in [base]() +-* no: do not use absolute URL +-* var: use absolute URL only in variant playlists +-* mas: use absolute URL only in master playlist +-* both: use absolute URL everywhere ++ ++- no: do not use absolute URL ++- var: use absolute URL only in variant playlists ++- mas: use absolute URL only in master playlist ++- both: use absolute URL everywhere + + __hls_ap__ (bool, default: _false_): use audio as primary media instead of video when generating playlists + __seg_sync__ (enum, default: _auto_): control how waiting on last packet P of fragment/segment to be written impacts segment injection in manifest +-* no: do not wait for P +-* yes: wait for P +-* auto: wait for P if HLS is used ++ ++- no: do not wait for P ++- yes: wait for P ++- auto: wait for P if HLS is used + + __cmaf__ (enum, default: _no_): use cmaf guidelines +-* no: CMAF not enforced +-* cmfc: use CMAF `cmfc` guidelines +-* cmf2: use CMAF `cmf2` guidelines ++ ++- no: CMAF not enforced ++- cmfc: use CMAF `cmfc` guidelines ++- cmf2: use CMAF `cmf2` guidelines + + __pswitch__ (enum, default: _single_): period switch control mode +-* single: change period if PID configuration changes +-* force: force period switch at each PID reconfiguration instead of absorbing PID reconfiguration (for splicing or add insertion not using periodID) +-* stsd: change period if PID configuration changes unless new configuration was advertised in initial config ++ ++- single: change period if PID configuration changes ++- force: force period switch at each PID reconfiguration instead of absorbing PID reconfiguration (for splicing or add insertion not using periodID) ++- stsd: change period if PID configuration changes unless new configuration was advertised in initial config + + __chain__ (str): URL of next MPD for regular chaining + __chain_fbk__ (str): URL of fallback MPD +@@ -522,9 +624,11 @@ The segmenter adds the following properties to the output PIDs: + __keep_src__ (bool, default: _false_): keep source URLs in manifest generation mode + __gxns__ (bool, default: _false_): insert some gpac extensions in manifest (for now, only tfdt of first segment) + __dkid__ (enum, default: _auto_): control injection of default KID in MPD +-* off: default KID not injected +-* on: default KID always injected +-* auto: default KID only injected if no key roll is detected (as per DASH-IF guidelines) ++ ++- off: default KID not injected ++- on: default KID always injected ++- auto: default KID only injected if no key roll is detected (as per DASH-IF guidelines) + + __tpl_force__ (bool, default: _false_): use template string as is without trying to add extension or solve conflicts in names ++__ttml_agg__ (bool, default: _false_): force aggregation of TTML samples of a DASH segment into a single sample + +diff --git a/docs/Filters/dashin.md b/docs/Filters/dashin.md +index 98c26b4f..52dc660f 100644 +--- a/docs/Filters/dashin.md ++++ b/docs/Filters/dashin.md +@@ -1,6 +1,6 @@ + + +-# MPEG-DASH and HLS client ++# MPEG-DASH and HLS client {:data-level="all"} + + Register name used to load filter: __dashin__ + This filter may be automatically loaded during graph resolution. +@@ -12,12 +12,16 @@ This filter reads MPEG-DASH, HLS and MS Smooth manifests. + + This is the default mode, in which the filter produces media PIDs and frames from sources indicated in the manifest. + The default behavior is to perform adaptation according to [algo](#algo), but the filter can: ++ + - run with no adaptation, to grab maximum quality. ++ + Example + ``` + gpac -i MANIFEST_URL:algo=none:start_with=max_bw -o dest.mp4 + ``` ++ + - run with no adaptation, fetching all qualities. ++ + Example + ``` + gpac -i MANIFEST_URL:split_as -o dst=$File$.mp4 +@@ -60,28 +64,34 @@ This will encrypt an existing DASH session and republish it as HLS, using same s + This mode will force [noseek](#noseek)=`true` to ensure the first segment fetched is complete, and [split_as](#split_as)=`true` to fetch all qualities. + + Each first packet of a segment will have the following properties attached: +-* `CueStart`: indicate this is a segment start +-* `FileNumber`: current segment number +-* `FileName`: current segment file name without manifest (MPD or master HLS) base url +-* `DFPStart`: set with value `0` if this is the first packet in the period, absent otherwise ++ ++- `CueStart`: indicate this is a segment start ++- `FileNumber`: current segment number ++- `FileName`: current segment file name without manifest (MPD or master HLS) base url ++- `DFPStart`: set with value `0` if this is the first packet in the period, absent otherwise ++ + + If [forward](#forward) is set to `mani`, the first packet of a segment dispatched after a manifest update will also carry the manifest payload as a property: +-* `DFManifest`: contains main manifest (MPD, M3U8 master) +-* `DFVariant`: contains list of HLS child playlists as strings for the given quality +-* `DFVariantName`: contains list of associated HLS child playlists name, in same order as manifests in `DFVariant` ++ ++- `DFManifest`: contains main manifest (MPD, M3U8 master) ++- `DFVariant`: contains list of HLS child playlists as strings for the given quality ++- `DFVariantName`: contains list of associated HLS child playlists name, in same order as manifests in `DFVariant` ++ + + Each output PID will have the following properties assigned: +-* `DFMode`: set to 1 for `segb` or 2 for `mani` +-* `DCue`: set to `inband` +-* `DFPStart`: set to current period start value +-* `FileName`: set to associated init segment if any +-* `Representation`: set to the associated representation ID in the manifest +-* `DashDur`: set to the average segment duration as indicated in the manifest +-* `source_template`: set to true to indicate the source template is known +-* `stl_timescale`: timescale used by SegmentTimeline, or 0 if no SegmentTimeline +-* `init_url`: unresolved intialization URL (as it appears in the MPD or in the variant playlist) +-* `manifest_url`: manifest URL +-* `hls_variant_name`: HLS variant playlist name (as it appears in the HLS master playlist) ++ ++- `DFMode`: set to 1 for `segb` or 2 for `mani` ++- `DCue`: set to `inband` ++- `DFPStart`: set to current period start value ++- `FileName`: set to associated init segment if any ++- `Representation`: set to the associated representation ID in the manifest ++- `DashDur`: set to the average segment duration as indicated in the manifest ++- `source_template`: set to true to indicate the source template is known ++- `stl_timescale`: timescale used by SegmentTimeline, or 0 if no SegmentTimeline ++- `init_url`: unresolved intialization URL (as it appears in the MPD or in the variant playlist) ++- `manifest_url`: manifest URL ++- `hls_variant_name`: HLS variant playlist name (as it appears in the HLS master playlist) ++ + + When the [dasher](dasher) is used together with this mode, this will force all generated segments to have the same name, duration and fragmentation properties as the input ones. It is therefore not recommended for sessions stored/generated on local storage to generate the output in the same directory. + +@@ -89,56 +99,62 @@ When the [dasher](dasher) is used together with this mode, this will force all g + # Options + + __auto_switch__ (sint, default: _0_): switch quality every N segments +-* positive: go to higher quality or loop to lowest +-* negative: go to lower quality or loop to highest +-* 0: disabled ++ ++- positive: go to higher quality or loop to lowest ++- negative: go to lower quality or loop to highest ++- 0: disabled + + __segstore__ (enum, default: _mem_): enable file caching +-* mem: all files are stored in memory, no disk IO +-* disk: files are stored to disk but discarded once played +-* cache: all files are stored to disk and kept ++ ++- mem: all files are stored in memory, no disk IO ++- disk: files are stored to disk but discarded once played ++- cache: all files are stored to disk and kept + + __algo__ (str, default: _gbuf_, Enum: none|grate|gbuf|bba0|bolaf|bolab|bolau|bolao|JS): adaptation algorithm to use +-* none: no adaptation logic +-* grate: GPAC legacy algo based on available rate +-* gbuf: GPAC legacy algo based on buffer occupancy +-* bba0: BBA-0 +-* bolaf: BOLA Finite +-* bolab: BOLA Basic +-* bolau: BOLA-U +-* bolao: BOLA-O +-* JS: use file JS (either with specified path or in $GSHARE/scripts/) for algo (.js extension may be omitted) ++ ++- none: no adaptation logic ++- grate: GPAC legacy algo based on available rate ++- gbuf: GPAC legacy algo based on buffer occupancy ++- bba0: BBA-0 ++- bolaf: BOLA Finite ++- bolab: BOLA Basic ++- bolau: BOLA-U ++- bolao: BOLA-O ++- JS: use file JS (either with specified path or in $GSHARE/scripts/) for algo (.js extension may be omitted) + + __start_with__ (enum, default: _max_bw_): initial selection criteria +-* min_q: start with lowest quality +-* max_q: start with highest quality +-* min_bw: start with lowest bitrate +-* max_bw: start with highest bitrate; if tiles are used, all low priority tiles will have the lower (below max) bandwidth selected +-* max_bw_tiles: start with highest bitrate; if tiles are used, all low priority tiles will have their lowest bandwidth selected ++ ++- min_q: start with lowest quality ++- max_q: start with highest quality ++- min_bw: start with lowest bitrate ++- max_bw: start with highest bitrate; if tiles are used, all low priority tiles will have the lower (below max) bandwidth selected ++- max_bw_tiles: start with highest bitrate; if tiles are used, all low priority tiles will have their lowest bandwidth selected + + __max_res__ (bool, default: _true_): use max media resolution to configure display + __abort__ (bool, default: _false_): allow abort during a segment download + __use_bmin__ (enum, default: _auto_): playout buffer handling +-* no: use default player settings +-* auto: notify player of segment duration if not low latency +-* mpd: use the indicated min buffer time of the MPD ++ ++- no: use default player settings ++- auto: notify player of segment duration if not low latency ++- mpd: use the indicated min buffer time of the MPD + + __shift_utc__ (sint, default: _0_): shift DASH UTC clock in ms + __spd__ (sint, default: _-I_): suggested presentation delay in ms +-__route_shift__ (sint, default: _0_): shift ROUTE requests time by given ms ++__mcast_shift__ (sint, default: _0_): shift requests time by given ms for multicast sources + __server_utc__ (bool, default: _yes_): use `ServerUTC` or `Date` HTTP headers instead of local UTC + __screen_res__ (bool, default: _yes_): use screen resolution in selection phase + __init_timeshift__ (sint, default: _0_): set initial timeshift in ms (if >0) or in per-cent of timeshift buffer (if <0) + __tile_mode__ (enum, default: _none_): tile adaptation mode +-* none: bitrate is shared equally across all tiles +-* rows: bitrate decreases for each row of tiles starting from the top, same rate for each tile on the row +-* rrows: bitrate decreases for each row of tiles starting from the bottom, same rate for each tile on the row +-* mrows: bitrate decreased for top and bottom rows only, same rate for each tile on the row +-* cols: bitrate decreases for each columns of tiles starting from the left, same rate for each tile on the columns +-* rcols: bitrate decreases for each columns of tiles starting from the right, same rate for each tile on the columns +-* mcols: bitrate decreased for left and right columns only, same rate for each tile on the columns +-* center: bitrate decreased for all tiles on the edge of the picture +-* edges: bitrate decreased for all tiles on the center of the picture ++ ++- none: bitrate is shared equally across all tiles ++- rows: bitrate decreases for each row of tiles starting from the top, same rate for each tile on the row ++- rrows: bitrate decreases for each row of tiles starting from the bottom, same rate for each tile on the row ++- mrows: bitrate decreased for top and bottom rows only, same rate for each tile on the row ++- cols: bitrate decreases for each columns of tiles starting from the left, same rate for each tile on the columns ++- rcols: bitrate decreases for each columns of tiles starting from the right, same rate for each tile on the columns ++- mcols: bitrate decreased for left and right columns only, same rate for each tile on the columns ++- center: bitrate decreased for all tiles on the edge of the picture ++- edges: bitrate decreased for all tiles on the center of the picture + + __tiles_rate__ (uint, default: _100_): indicate the amount of bandwidth to use at each quality level. The rate is recursively applied at each level, e.g. if 50%, Level1 gets 50%, level2 gets 25%, ... If 100, automatic rate allocation will be done by maximizing the quality in order of priority. If 0, bitstream will not be smoothed across tiles/qualities, and concurrency may happen between different media + __delay40X__ (uint, default: _500_): delay in milliseconds to wait between two 40X on the same segment +@@ -153,24 +169,27 @@ When the [dasher](dasher) is used together with this mode, this will force all g + __noseek__ (bool, default: _no_): disable seeking of initial segment(s) in dynamic mode (useful when UTC clocks do not match) + __bwcheck__ (uint, default: _5_): minimum time in milliseconds between two bandwidth checks when allowing segment download abort + __lowlat__ (enum, default: _early_): segment scheduling policy in low latency mode +-* no: disable low latency +-* strict: strict respect of AST offset in low latency +-* early: allow fetching segments earlier than their AST in low latency when input PID is empty ++ ++- no: disable low latency ++- strict: strict respect of AST offset in low latency ++- early: allow fetching segments earlier than their AST in low latency when input PID is empty + + __forward__ (enum, default: _none_): segment forwarding mode +-* none: regular DASH read +-* file: do not demultiplex files and forward them as file PIDs (imply `segstore=mem`) +-* segb: turn on [split_as](#split_as), segment and fragment bounds signaling (`sigfrag`) in sources and DASH cue insertion +-* mani: same as `segb` and also forward manifests ++ ++- none: regular DASH read ++- file: do not demultiplex files and forward them as file PIDs (imply `segstore=mem`) ++- segb: turn on [split_as](#split_as), segment and fragment bounds signaling (`sigfrag`) in sources and DASH cue insertion ++- mani: same as `segb` and also forward manifests + + __fmodefwd__ (bool, default: _yes_): forward packet rather than copy them in `file` forward mode. Packet copy might improve performances in low latency mode + __skip_lqt__ (bool, default: _no_): disable decoding of tiles with highest degradation hints (not visible, not gazed at) for debug purposes + __llhls_merge__ (bool, default: _yes_): merge LL-HLS byte range parts into a single open byte range request + __groupsel__ (bool, default: _no_): select groups based on language (by default all playable groups are exposed) + __chain_mode__ (enum, default: _on_): MPD chaining mode +-* off: do not use MPD chaining +-* on: use MPD chaining once over, fallback if MPD load failure +-* error: use MPD chaining once over or if error (MPD or segment download) ++ ++- off: do not use MPD chaining ++- on: use MPD chaining once over, fallback if MPD load failure ++- error: use MPD chaining once over or if error (MPD or segment download) + + __asloop__ (bool, default: _false_): when auto switch is enabled, iterates back and forth from highest to lowest qualities + __bsmerge__ (bool, default: _true_): allow merging of video bitstreams (only HEVC for now) +diff --git a/docs/Filters/dtout.md b/docs/Filters/dtout.md +index 69a60051..d73965aa 100644 +--- a/docs/Filters/dtout.md ++++ b/docs/Filters/dtout.md +@@ -1,6 +1,6 @@ + + +-# DekTec SDIOut ++# DekTec SDIOut {:data-level="all"} + + Register name used to load filter: __dtout__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/dvbin.md b/docs/Filters/dvbin.md +index e2c626e0..5b7468a2 100644 +--- a/docs/Filters/dvbin.md ++++ b/docs/Filters/dvbin.md +@@ -1,6 +1,6 @@ + + +-# DVB for Linux ++# DVB for Linux {:data-level="all"} + + Register name used to load filter: __dvbin__ + This filter may be automatically loaded during graph resolution. +@@ -8,8 +8,10 @@ This filter may be automatically loaded during graph resolution. + Experimental DVB support for linux, requires a channel config file through [chcfg](#chcfg) + + The URL syntax is `dvb://CHANNAME[@FRONTEND]`, with: +- * CHANNAME: the channel name as listed in the channel config file +- * frontend: the index of the DVB adapter to use (optional, default is 0) ++ ++ - CHANNAME: the channel name as listed in the channel config file ++ - frontend: the index of the DVB adapter to use (optional, default is 0) ++ + + + # Options +diff --git a/docs/Filters/evgs.md b/docs/Filters/evgs.md +index d4dad26a..156ffa0a 100644 +--- a/docs/Filters/evgs.md ++++ b/docs/Filters/evgs.md +@@ -1,6 +1,6 @@ + + +-# EVG video rescaler ++# EVG video rescaler {:data-level="all"} + + Register name used to load filter: __evgs__ + This filter may be automatically loaded during graph resolution. +@@ -22,18 +22,24 @@ evgs:osize=288x240:osar=3/2 + The output dimensions will be 192x240. + + When aspect ratio is not kept ([keepar=off](#keepar=off)): ++ + - source is resampled to desired dimensions + - if output aspect ratio is not set, output will use source sample aspect ratio ++ + + When aspect ratio is partially kept ([keepar=nosrc](#keepar=nosrc)): ++ + - resampling is done on the input data without taking input sample aspect ratio into account + - if output sample aspect ratio is not set ([osar=0/N](#osar=0/N)), source aspect ratio is forwarded to output. ++ + + When aspect ratio is fully kept ([keepar=full](#keepar=full)), output aspect ratio is force to 1/1 if not set. + + When sample aspect ratio is kept, the filter will: ++ + - center the rescaled input frame on the output frame + - fill extra pixels with [padclr](#padclr) ++ + + + # Options +@@ -42,9 +48,10 @@ When sample aspect ratio is kept, the filter will: + __ofmt__ (pfmt, default: _none_): pixel format for output video. When not set, input format is used + __ofr__ (bool, default: _false_): force output full range + __keepar__ (enum, default: _off_): keep aspect ratio +-* off: ignore aspect ratio +-* full: respect aspect ratio, applying input sample aspect ratio info +-* nosrc: respect aspect ratio but ignore input sample aspect ratio ++ ++- off: ignore aspect ratio ++- full: respect aspect ratio, applying input sample aspect ratio info ++- nosrc: respect aspect ratio but ignore input sample aspect ratio + + __padclr__ (str, default: _black_): clear color when aspect ration preservation is used + __osar__ (frac, default: _0/1_): force output pixel aspect ratio +diff --git a/docs/Filters/faad.md b/docs/Filters/faad.md +index a2be3b59..01271771 100644 +--- a/docs/Filters/faad.md ++++ b/docs/Filters/faad.md +@@ -1,6 +1,6 @@ + + +-# FAAD decoder ++# FAAD decoder {:data-level="all"} + + Register name used to load filter: __faad__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/ffavf.md b/docs/Filters/ffavf.md +index 06a18419..c53656f5 100644 +--- a/docs/Filters/ffavf.md ++++ b/docs/Filters/ffavf.md +@@ -1,6 +1,6 @@ + + +-# FFmpeg AVFilter ++# FFmpeg AVFilter {:data-level="all"} + + Register name used to load filter: __ffavf__ + This filter is not checked during graph resolution and needs explicit loading. +@@ -49,9 +49,11 @@ Example + gpac -i video:#ffid=a -i logo:#ffid=b ffavf::f=[a][b]overlay=main_w-overlay_w-10:main_h-overlay_h-10 vout + ``` + In this example: ++ + - the video source is identified as `a` + - the logo source is identified as `b` + - the filter declaration maps `a` to its first input (in this case, main video) and `b` to its second input (in this case the overlay) ++ + + When a graph has several outputs, output PIDs will be identified using the `ffid` property set to the output avfilter name. + Example +@@ -59,17 +61,21 @@ Example + gpac -i source ffavf::f=split inspect:SID=#ffid=out0 vout#SID=out1 + ``` + In this example: ++ + - the splitter produces 2 video streams `out0` and `out1` + - the inspector only process stream with ffid `out0` + - the video output only displays stream with ffid `out1` ++ + + The name(s) of the final output of the avfilter graph cannot be configured in GPAC. You can however name intermediate output(s) in a complex filter chain as usual. + + # Filter graph commands + + The filter handles option updates as commands passed to the AV filter graph. The syntax expected in the option name is: +-* com_name=value: sends command `com_name` with value `value` to all filters +-* name#com_name=value: sends command `com_name` with value `value` to filter named `name` ++ ++- com_name=value: sends command `com_name` with value `value` to all filters ++- name#com_name=value: sends command `com_name` with value `value` to filter named `name` ++ + + + # Options +diff --git a/docs/Filters/ffavin.md b/docs/Filters/ffavin.md +index 9e1db959..505a9c45 100644 +--- a/docs/Filters/ffavin.md ++++ b/docs/Filters/ffavin.md +@@ -1,6 +1,6 @@ + + +-# FFmpeg AV Capture ++# FFmpeg AV Capture {:data-level="all"} + + Register name used to load filter: __ffavin__ + This filter may be automatically loaded during graph resolution. +@@ -14,14 +14,18 @@ To list all supported grabbers for your GPAC build, use `gpac -h ffavin:*`. + Typical classes are `dshow` on windows, `avfoundation` on OSX, `video4linux2` or `x11grab` on linux + + Typical device name can be the webcam name: ++ + - `FaceTime HD Camera` on OSX, device name on windows, `/dev/video0` on linux + - `screen-capture-recorder`, see http://screencapturer.sf.net/ on windows + - `Capture screen 0` on OSX (0=first screen), or `screenN` for short + - X display name (e.g. `:0.0`) on linux ++ + + The general mapping from ffmpeg command line is: ++ + - ffmpeg `-f` maps to [fmt](#fmt) option + - ffmpeg `-i` maps to [dev](#dev) option ++ + + Example + ``` +@@ -42,10 +46,11 @@ gpac -i av://::dev=0:1 ... + __fmt__ (str): name of device class. If not set, defaults to first device class + __dev__ (str, default: _0_): name of device or index of device + __copy__ (enum, default: _A_): set copy mode of raw frames +-* N: frames are only forwarded (shared memory, no copy) +-* A: audio frames are copied, video frames are forwarded +-* V: video frames are copied, audio frames are forwarded +-* AV: all frames are copied ++ ++- N: frames are only forwarded (shared memory, no copy) ++- A: audio frames are copied, video frames are forwarded ++- V: video frames are copied, audio frames are forwarded ++- AV: all frames are copied + + __sclock__ (bool, default: _false_): use system clock (us) instead of device timestamp (for buggy devices) + __probes__ (uint, default: _10_, minmax: 0-100): probe a given number of video frames before emitting (this usually helps with bad timing of the first frames) +diff --git a/docs/Filters/ffbsf.md b/docs/Filters/ffbsf.md +index 3bfa30e4..30e8ac37 100644 +--- a/docs/Filters/ffbsf.md ++++ b/docs/Filters/ffbsf.md +@@ -1,6 +1,6 @@ + + +-# FFmpeg BitStream filter ++# FFmpeg BitStream filter {:data-level="all"} + + Register name used to load filter: __ffbsf__ + This filter is not checked during graph resolution and needs explicit loading. +@@ -13,8 +13,10 @@ Several BSF may be specified in [f](#f) for different coding types. BSF not matc + When no BSF matches the input coding type, or when [f](#f) is empty, the filter acts as a passthrough filter. + + Options are specified after the desired filters: ++ + - `ffbsf:f=h264_metadata:video_full_range_flag=0` + - `ffbsf:f=h264_metadata,av1_metadata:video_full_range_flag=0:color_range=tv` ++ + + _Note: Using BSFs on some media types (e.g. avc, hevc) may trigger creation of a reframer filter (e.g. rfnalu)_ + +diff --git a/docs/Filters/ffdec.md b/docs/Filters/ffdec.md +index 0d9ed510..49632554 100644 +--- a/docs/Filters/ffdec.md ++++ b/docs/Filters/ffdec.md +@@ -1,6 +1,6 @@ + + +-# FFmpeg decoder ++# FFmpeg decoder {:data-level="all"} + + Register name used to load filter: __ffdec__ + This filter may be automatically loaded during graph resolution. +@@ -16,9 +16,11 @@ The default threading mode is to let libavcodec decide how many threads to use. + + The [ffcmap](#ffcmap) option allows specifying FFmpeg codecs for codecs not supported by GPAC. + Each entry in the list is formatted as `GID@name` or `GID@+name`, with: +-* GID: 4CC or 32 bit identifier of codec ID, as indicated by `gpac -i source inspect:full` +-* name: FFmpeg codec name +-* `+': is set and extra data is set and formatted as an ISOBMFF box, removes box header ++ ++- GID: 4CC or 32 bit identifier of codec ID, as indicated by `gpac -i source inspect:full` ++- name: FFmpeg codec name ++- `+': is set and extra data is set and formatted as an ISOBMFF box, removes box header ++ + + Example + ``` +diff --git a/docs/Filters/ffdmx.md b/docs/Filters/ffdmx.md +index fde5938a..a723256c 100644 +--- a/docs/Filters/ffdmx.md ++++ b/docs/Filters/ffdmx.md +@@ -1,6 +1,6 @@ + + +-# FFmpeg demultiplexer ++# FFmpeg demultiplexer {:data-level="all"} + + Register name used to load filter: __ffdmx__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/ffenc.md b/docs/Filters/ffenc.md +index 01ce1e82..bdb2669b 100644 +--- a/docs/Filters/ffenc.md ++++ b/docs/Filters/ffenc.md +@@ -1,6 +1,6 @@ + + +-# FFmpeg encoder ++# FFmpeg encoder {:data-level="all"} + + Register name used to load filter: __ffenc__ + This filter may be automatically loaded during graph resolution. +@@ -17,8 +17,10 @@ Options can be passed from prompt using `--OPT=VAL` (global options) or appendin + The filter will look for property `TargetRate` on input PID to set the desired bitrate per PID. + + The filter will force a closed gop boundary: ++ + - at each packet with a `FileNumber` property set or a `CueStart` property set to true. + - if [fintra](#fintra) and [rc](#rc) is set. ++ + + When forcing a closed GOP boundary, the filter will flush, destroy and recreate the encoder to make sure a clean context is used, as currently many encoders in libavcodec do not support clean reset when forcing picture types. + If [fintra](#fintra) is not set and the output of the encoder is a DASH session in live profile without segment timeline, [fintra](#fintra) will be set to the target segment duration and [rc](#rc) will be set. +diff --git a/docs/Filters/ffmx.md b/docs/Filters/ffmx.md +index d5c78101..4e261fea 100644 +--- a/docs/Filters/ffmx.md ++++ b/docs/Filters/ffmx.md +@@ -1,6 +1,6 @@ + + +-# FFmpeg multiplexer ++# FFmpeg multiplexer {:data-level="all"} + + Register name used to load filter: __ffmx__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/ffsws.md b/docs/Filters/ffsws.md +index a87efbb4..b458607d 100644 +--- a/docs/Filters/ffsws.md ++++ b/docs/Filters/ffsws.md +@@ -1,6 +1,6 @@ + + +-# FFmpeg video rescaler ++# FFmpeg video rescaler {:data-level="all"} + + Register name used to load filter: __ffsws__ + This filter may be automatically loaded during graph resolution. +@@ -22,23 +22,31 @@ ffsws:osize=288x240:osar=3/2 + The output dimensions will be 192x240. + + When aspect ratio is not kept ([keepar=off](#keepar=off)): ++ + - source is resampled to desired dimensions + - if output aspect ratio is not set, output will use source sample aspect ratio ++ + + When aspect ratio is partially kept ([keepar=nosrc](#keepar=nosrc)): ++ + - resampling is done on the input data without taking input sample aspect ratio into account + - if output sample aspect ratio is not set ([osar=0/N](#osar=0/N)), source aspect ratio is forwarded to output. ++ + + When aspect ratio is fully kept ([keepar=full](#keepar=full)), output aspect ratio is force to 1/1 if not set. + + When sample aspect ratio is kept, the filter will: ++ + - center the rescaled input frame on the output frame + - fill extra pixels with [padclr](#padclr) ++ + + ## Algorithms options ++ + - for bicubic, to tune the shape of the basis function, [p1](#p1) tunes f(1) and [p2](#p2) f´(1) + - for gauss [p1](#p1) tunes the exponent and thus cutoff frequency + - for lanczos [p1](#p1) tunes the width of the window function ++ + + See FFmpeg documentation (https://ffmpeg.org/documentation.html) for more details + +@@ -59,9 +67,10 @@ See FFmpeg documentation (https://ffmpeg.org/documentation.html) for more detail + __otable__ (sintl): the yuv2rgb coefficients describing the output yuv space, normally ff_yuv2rgb_coeffs[x], use default if not set + __itable__ (sintl): the yuv2rgb coefficients describing the input yuv space, normally ff_yuv2rgb_coeffs[x], use default if not set + __keepar__ (enum, default: _off_): keep aspect ratio +-* off: ignore aspect ratio +-* full: respect aspect ratio, applying input sample aspect ratio info +-* nosrc: respect aspect ratio but ignore input sample aspect ratio ++ ++- off: ignore aspect ratio ++- full: respect aspect ratio, applying input sample aspect ratio info ++- nosrc: respect aspect ratio but ignore input sample aspect ratio + + __padclr__ (str, default: _black_): clear color when aspect ration preservation is used + __osar__ (frac, default: _0/1_): force output pixel aspect ratio +diff --git a/docs/Filters/filters_general.md b/docs/Filters/filters_general.md +index d7dea8ea..9c2dc994 100644 +--- a/docs/Filters/filters_general.md ++++ b/docs/Filters/filters_general.md +@@ -1,6 +1,6 @@ + + +-# Overview ++# Overview {:data-level="all"} + + Filters are configurable processing units consuming and producing data packets. These packets are carried between filters through a data channel called _PID_. A PID is in charge of allocating/tracking data packets, and passing the packets to the destination filter(s). A filter output PID may be connected to zero or more filters. This fan-out is handled internally by GPAC (no such thing as a tee filter in GPAC). + _Note: When a PID cannot be connected to any filter, a warning is thrown and all packets dispatched on this PID will be destroyed. The session may however still run, unless [-full-link](core_options/#full-link) is set._ +@@ -11,26 +11,35 @@ Each filter exposes a set of argument to configure itself, using property types + + # Property and filter option format + +-* boolean: formatted as `yes`,`true`,`1` or `no`,`false`,`0` +-* enumeration (for filter arguments only): must use the syntax given in the argument description, otherwise value `0` (first in enum) is assumed. +-* 1-dimension (numbers, floats, ints...): formatted as `value[unit]`, where `unit` can be `k`,`K` (x 1000) or `m`,`M` (x 1000000) or `g`,`G` (x 1000000000) or `sec` (x 1000) or `min` (x 60000). `+I` means max float/int/uint value, `-I` min float/int/uint value. +-* fraction: formatted as `num/den` or `num-den` or `num`, in which case the denominator is 1 if `num` is an integer, or 1000000 if `num` is a floating-point value. +-* unsigned 32 bit integer: formatted as number or hexadecimal using the format `0xAABBCCDD`. +-* N-dimension (vectors): formatted as `DIM1xDIM2[xDIM3[xDIM4]]` values, without unit multiplier. +- * For 2D integer vectors, the following resolution names can be used: `360`, `480`, `576`, `720`, `1080`, `hd`, `2k`, `2160`, `4k`, `4320`, `8k` +-* string: formatted as: +- * `value`: copies value to string. +- * `file@FILE`: load string from local `FILE` (opened in binary mode). +- * `bxml@FILE`: binarize XML from local `FILE` and set property type to data - see https://wiki.gpac.io/xmlformats/NHML-Format. +-* data: formatted as: +- * `size@address`: constant data block, not internally copied; `size` gives the size of the block, `address` the data pointer. +- * `0xBYTESTRING`: data block specified in hexadecimal, internally copied. +- * `file@FILE`: load data from local `FILE` (opened in binary mode). +- * `bxml@FILE`: binarize XML from local `FILE` - see https://wiki.gpac.io/xmlformats/NHML-Format. +- * `b64@DATA`: load data from base-64 encoded `DATA`. +-* pointer: pointer address as formatted by `%p` in C. +-* string lists: formatted as `val1,val2[,...]`. Each value can also use `file@FILE` syntax. +-* integer lists: formatted as `val1,val2[,...]` ++ ++- boolean: formatted as `yes`,`true`,`1` or `no`,`false`,`0` ++- enumeration (for filter arguments only): must use the syntax given in the argument description, otherwise value `0` (first in enum) is assumed. ++- 1-dimension (numbers, floats, ints...): formatted as `value[unit]`, where `unit` can be `k`,`K` (x 1000) or `m`,`M` (x 1000000) or `g`,`G` (x 1000000000) or `sec` (x 1000) or `min` (x 60000). `+I` means max float/int/uint value, `-I` min float/int/uint value. ++- fraction: formatted as `num/den` or `num-den` or `num`, in which case the denominator is 1 if `num` is an integer, or 1000000 if `num` is a floating-point value. ++- unsigned 32 bit integer: formatted as number or hexadecimal using the format `0xAABBCCDD`. ++- N-dimension (vectors): formatted as `DIM1xDIM2[xDIM3[xDIM4]]` values, without unit multiplier. ++ ++ - For 2D integer vectors, the following resolution names can be used: `360`, `480`, `576`, `720`, `1080`, `hd`, `2k`, `2160`, `4k`, `4320`, `8k` ++ ++- string: formatted as: ++ ++ - `value`: copies value to string. ++ - `file@FILE`: load string from local `FILE` (opened in binary mode). ++ - `bxml@FILE`: binarize XML from local `FILE` and set property type to data - see https://wiki.gpac.io/xmlformats/NHML-Format. ++ ++- data: formatted as: ++ ++ - `size@address`: constant data block, not internally copied; `size` gives the size of the block, `address` the data pointer. ++ - `0xBYTESTRING`: data block specified in hexadecimal, internally copied. ++ - `file@FILE`: load data from local `FILE` (opened in binary mode). ++ - `bxml@FILE`: binarize XML from local `FILE` - see https://wiki.gpac.io/xmlformats/NHML-Format. ++ - `b64@DATA`: load data from base-64 encoded `DATA`. ++ - `FMT@val`: load values from val (comma-separated list) with `FMT` being `u8`, `s8`, `u16`, `s16`, `u32`, `s32`, `u64`, `s64`, `flt`, `dbl`, `hex` or `str`. ++ ++- pointer: pointer address as formatted by `%p` in C. ++- string lists: formatted as `val1,val2[,...]`. Each value can also use `file@FILE` syntax. ++- integer lists: formatted as `val1,val2[,...]` ++ + + _Note: The special characters in property formats (0x,/,-,+I,-I,x) cannot be configured._ + +@@ -40,8 +49,10 @@ Numbers and fraction can be expressed as `THH:MM:SS.ms`, `TMM:SS.ms`, `THH:MM:SS + + ## Generic declaration + Each filter is declared by its name, with optional filter arguments appended as a list of colon-separated `name=value` pairs. Additional syntax is provided for: +-* boolean: `value` can be omitted, defaulting to `true` (e.g. `:allt`). Using `!` before the name negates the result (e.g. `:!moof_first`) +-* enumerations: name can be omitted, e.g. `:disp=pbo` is equivalent to `:pbo`. ++ ++- boolean: `value` can be omitted, defaulting to `true` (e.g. `:allt`). Using `!` before the name negates the result (e.g. `:!moof_first`) ++- enumerations: name can be omitted, e.g. `:disp=pbo` is equivalent to `:pbo`. ++ + + + When string parameters are used (e.g. URLs), it is recommended to escape the string using the keyword `gpac`. +@@ -49,31 +60,36 @@ Example + ``` + filter:ARG=http://foo/bar?yes:gpac:opt=VAL + ``` ++ + This will properly extract the URL. + Example + ``` + filter:ARG=http://foo/bar?yes:opt=VAL +-``` ++``` ++ + This will fail to extract it and keep `:opt=VAL` as part of the URL. + The escape mechanism is not needed for local source, for which file existence is probed during argument parsing. It is also not needed for builtin protocol handlers (`avin://`, `video://`, `audio://`, `pipe://`) +-For `tcp://` and `udp://` protocols, the escape is not needed if a trailing `/` is appended after the port number. ++For schemes not using a server path, e.g. `tcp://` and `udp://`, the escape is not needed if a trailing `/` is appended after the port number. + Example + ``` + -i tcp://127.0.0.1:1234:OPT + ``` ++ + This will fail to extract the URL and options. + Example + ``` + -i tcp://127.0.0.1:1234/:OPT + ``` ++ + This will extract the URL and options. + _Note: one trick to avoid the escape sequence is to declare the URLs option at the end, e.g. `f1:opt1=foo:url=http://bar`, provided you have only one URL parameter to specify on the filter._ + +-It is possible to disable option parsing (for string options) by duplicating the separator. ++It is possible to locally disable option parsing (usefull for string options) by duplicating the separator. + Example + ``` + filter::opt1=UDP://IP:PORT/:someopt=VAL::opt2=VAL2 + ``` ++ + This will pass `UDP://IP:PORT/:someopt=VAL` to `opt1` without inspecting it, and `VAL2` to `opt2`. + + +@@ -83,11 +99,13 @@ Example + ``` + "src=file.mp4" or "-src file.mp4" or "-i file.mp4" + ``` ++ + This will find a filter (for example `fin`) able to load `file.mp4`. The same result can be achieved by using `fin:src=file.mp4`. + Example + ``` + "dst=dump.yuv" or "-dst dump.yuv" or "-o dump.yuv" +-``` ++``` ++ + This will dump the video content in `dump.yuv`. The same result can be achieved by using `fout:dst=dump.yuv`. + + Specific source or sink filters may also be specified using `filterName:src=URL` or `filterName:dst=URL`. +@@ -99,17 +117,20 @@ There is a special option called `gfreg` which allows specifying preferred filte + Example + ``` + src=file.mp4:gfreg=ffdmx,ffdec +-``` ++``` ++ + This will use _ffdmx_ to read `file.mp4` and _ffdec_ to decode it. + This can be used to test a specific filter when alternate filter chains are possible. + + ## Specifying encoders and decoders + By default filters chain will be resolved without any decoding/encoding if the destination accepts the desired format. Otherwise, decoders/encoders will be dynamically loaded to perform the conversion, unless dynamic resolution is disabled. There is a special shortcut filter name for encoders `enc` allowing to match a filter providing the desired encoding. The parameters for `enc` are: +-* c=NAME: identifies the desired codec. `NAME` can be the GPAC codec name or the encoder instance for ffmpeg/others +-* b=UINT, rate=UINT, bitrate=UINT: indicates the bitrate in bits per second +-* g=UINT, gop=UINT: indicates the GOP size in frames +-* pfmt=NAME: indicates the target pixel format name (see [properties (-h props)](filters_properties) ) of the source, if supported by codec +-* all_intra=BOOL: indicates all frames should be intra frames, if supported by codec ++ ++- c=NAME: identifies the desired codec. `NAME` can be the GPAC codec name or the encoder instance for ffmpeg/others ++- b=UINT, rate=UINT, bitrate=UINT: indicates the bitrate in bits per second ++- g=UINT, gop=UINT: indicates the GOP size in frames ++- pfmt=NAME: indicates the target pixel format name (see [properties (-h props)](filters_properties) ) of the source, if supported by codec ++- all_intra=BOOL: indicates all frames should be intra frames, if supported by codec ++ + + Other options will be passed to the filter if it accepts generic argument parsing (as is the case for ffmpeg). + The shortcut syntax `c=TYPE` (e.g. `c=aac:opts`) is also supported. +@@ -118,13 +139,15 @@ Example + ``` + gpac -i dump.yuv:size=320x240:fps=25 enc:c=avc:b=150000:g=50:cgop=true:fast=true -o raw.264 + ``` ++ + This creates a 25 fps AVC at 175kbps with a gop duration of 2 seconds, using closed gop and fast encoding settings for ffmpeg. + + The inverse operation (forcing a decode to happen) is possible using the _reframer_ filter. + Example + ``` + gpac -i file.mp4 reframer:raw=av -o null +-``` ++``` ++ + This will force decoding media from `file.mp4` and trash (send to `null`) the result (doing a decoder benchmark for example). + + ## Escaping option separators +@@ -132,17 +155,20 @@ When a filter uses an option defined as a string using the same separator charac + Example + ``` + f:a=foo:b=bar +-``` ++``` ++ + This will set option `a` to `foo` and option `b` to `bar` on the filter. + Example + ``` + f::a=foo:b=bar +-``` ++``` ++ + This will set option `a` to `foo:b=bar` on the filter. + Example + ``` + f:a=foo::b=bar:c::d=fun + ``` ++ + This will set option `a` to `foo`, `b` to `bar:c` and the option `d` to `fun` on the filter. + + # Filter linking [_LINK_] +@@ -159,12 +185,15 @@ _They do not specify which destination a filter can connect to._ + When no link instructions are given (see below), the default linking strategy used is either _implicit mode_ (default in `gpac`) or _complete mode_ (if [-cl](gpac_general/#cl) is set). + Each PID is checked for possible connection to all defined filters, in their declaration order. + For each filter `DST` accepting a connection from the PID, directly or with intermediate filters: ++ + - if `DST` filter has link directives, use them to allow or reject PID connection. + - otherwise, if _complete mode_ is enabled, allow connection.. + - otherwise (_implicit mode_): +- - if `DST` is not a sink and is the first matching filter with no link directive, allow connection. +- - otherwise, if `DST` is not a sink and is not the first matching filter with no link directive, reject connection. +- - otherwise (`DST` is a sink) and no previous connections to a non-sink filter, allow connection. ++ ++ - if `DST` is not a sink and is the first matching filter with no link directive, allow connection. ++ - otherwise, if `DST` is not a sink and is not the first matching filter with no link directive, reject connection. ++ - otherwise (`DST` is a sink) and no previous connections to a non-sink filter, allow connection. ++ + + In all linking modes, a filter can prevent being linked to a filter with no link directives by setting `RSID` option on the filter. + This is typically needed when dynamically inserting/removing filters in an existing session where some filters have no ID defined and are not desired for the inserted chain. +@@ -173,32 +202,42 @@ A filter with `RSID` set is not clonable. + Example + ``` + gpac -i file.mp4 c=avc -o output +-``` ++``` ++ + With this setup in _implicit mode_: ++ + - if the file has a video PID, it will connect to `enc` but not to `output`. The output PID of `enc` will connect to `output`. + - if the file has other PIDs than video, they will connect to `output`, since this `enc` filter accepts only video. ++ + + Example + ``` + gpac -cl -i file.mp4 c=avc -o output + ``` ++ + With this setup in _complete mode_: ++ + - if the file has a video PID, it will connect both to `enc` and to `output`, and the output PID of `enc` will connect to `output`. + - if the file has other PIDs than video, they will connect to `output`. ++ + + Furthermore in _implicit mode_, filter connections are restricted to filters defined between the last source and the sink(s). + Example + ``` + gpac -i video1 reframer:saps=1 -i video2 ffsws:osize=128x72 -o output + ``` ++ + This will connect: ++ + - `video1` to `reframer` then `reframer` to `output` but will prevent `reframer` to `ffsws` connection. + - `video2` to `ffsws` then `ffsws` to `output` but will prevent `video2` to `reframer` connection. ++ + + Example + ``` + gpac -i video1 -i video2 reframer:saps=1 ffsws:osize=128x72 -o output +-``` ++``` ++ + This will connect `video1` AND `video2` to `reframer->ffsws->output` + + The _implicit mode_ allows specifying linear processing chains (no PID fan-out except for final output(s)) without link directives, simplifying command lines for common cases. +@@ -209,6 +248,7 @@ Example + ``` + gpac -i file.mp4 c=avc c=aac -o output + ``` ++ + If the file has a video PID, it will connect to `c=avc` but not to `output`. The output PID of `c=avc` will connect to `output`. + If the file has an audio PID, it will connect to `c=aac` but not to `output`. The output PID of `c=aac` will connect to `output`. + If the file has other PIDs than audio or video, they will connect to `output`. +@@ -216,10 +256,13 @@ If the file has other PIDs than audio or video, they will connect to `output`. + Example + ``` + gpac -i file.mp4 ffswf=osize:128x72 c=avc resample=osr=48k c=aac -o output +-``` ++``` ++ + This will force: ++ + - `SRC(video)->ffsws->enc(video)->output` and prevent `SRC(video)->output`, `SRC(video)->enc(video)` and `ffsws->output` connections which would happen in _complete mode_. + - `SRC(audio)->resample->enc(audio)->output` and prevent `SRC(audio)->output`, `SRC(audio)->enc(audio)` and `resample->output` connections which would happen in _complete mode_. ++ + + ## Quick links + Link between filters may be manually specified. The syntax is an `@` character optionally followed by an integer (0 if omitted). +@@ -231,56 +274,68 @@ Example + ``` + fA fB @1 fC + ``` ++ + This indicates that `fC` only accepts inputs from `fA`. + Example + ``` + fA fB fC @1 @0 fD + ``` ++ + This indicates that `fD` only accepts inputs from `fB` and `fC`. + Example + ``` + fA fB fC ... @@1 fZ +-``` ++``` ++ + This indicates that `fZ` only accepts inputs from `fB`. + + ## Complex links + The `@` link directive is just a quick shortcut to set the following filter arguments: +-* FID=name: assigns an identifier to the filter +-* SID=name1[,name2...]: sets a list of filter identifiers, or _sourceIDs_, restricting the list of possible inputs for a filter. ++ ++- FID=name: assigns an identifier to the filter ++- SID=name1[,name2...]: sets a list of filter identifiers, or _sourceIDs_, restricting the list of possible inputs for a filter. ++ + + Example + ``` + fA fB @1 fC +-``` ++``` ++ + This is equivalent to `fA:FID=1 fB fC:SID=1`. + Example + ``` + fA:FID=1 fB fC:SID=1 +-``` ++``` ++ + This indicates that `fC` only accepts input from `fA`, but `fB` might accept inputs from `fA`. + Example + ``` + fA:FID=1 fB:FID=2 fC:SID=1 fD:SID=1,2 +-``` ++``` ++ + This indicates that `fD` only accepts input from `fA` and `fB` and `fC` only from `fA` + _Note: A filter with sourceID set cannot get input from filters with no IDs._ + + A sourceID name can be further extended using fragment identifier (`#` by default): +-* name#PIDNAME: accepts only PID(s) with name `PIDNAME` +-* name#TYPE: accepts only PIDs of matching media type. TYPE can be `audio`, `video`, `scene`, `text`, `font`, `meta` +-* name#TYPEN: accepts only `N` (1-based index) PID of matching type from source (e.g. `video2` to only accept second video PID) +-* name#TAG=VAL: accepts the PID if its parent filter has no tag or a tag matching `VAL` +-* name#ITAG=VAL: accepts the PID if its parent filter has no inherited tag or an inherited tag matching `VAL` +-* name#P4CC=VAL: accepts only PIDs with builtin property of type `P4CC` and value `VAL`. +-* name#PName=VAL: same as above, using the builtin name corresponding to the property. +-* name#AnyName=VAL: same as above, using the name of a non built-in property. +-* name#Name=OtherPropName: compares the value with the value of another property of the PID. The matching will fail if the value to compare to is not present or different from the value to check. The property to compare with shall be a built-in property. ++ ++- name#PIDNAME: accepts only PID(s) with name `PIDNAME` ++- name#TYPE: accepts only PIDs of matching media type. TYPE can be `audio`, `video`, `scene`, `text`, `font`, `meta` ++- name#TYPEN: accepts only `N` (1-based index) PID of matching type from source (e.g. `video2` to only accept second video PID) ++- name#TAG=VAL: accepts the PID if its parent filter has no tag or a tag matching `VAL` ++- name#ITAG=VAL: accepts the PID if its parent filter has no inherited tag or an inherited tag matching `VAL` ++- name#P4CC=VAL: accepts only PIDs with builtin property of type `P4CC` and value `VAL`. ++- name#PName=VAL: same as above, using the builtin name corresponding to the property. ++- name#AnyName=VAL: same as above, using the name of a non built-in property. ++- name#Name=OtherPropName: compares the value with the value of another property of the PID. The matching will fail if the value to compare to is not present or different from the value to check. The property to compare with shall be a built-in property. ++ + If the property is not defined on the PID, the property is matched. Otherwise, its value is checked against the given value. + + The following modifiers for comparisons are allowed (for any fragment format using `=`): +-* name#P4CC=!VAL: accepts only PIDs with property NOT matching `VAL`. +-* name#P4CC-VAL: accepts only PIDs with property strictly less than `VAL` (only for 1-dimension number properties). +-* name#P4CC+VAL: accepts only PIDs with property strictly greater than `VAL` (only for 1-dimension number properties). ++ ++- name#P4CC=!VAL: accepts only PIDs with property NOT matching `VAL`. ++- name#P4CC-VAL: accepts only PIDs with property strictly less than `VAL` (only for 1-dimension number properties). ++- name#P4CC+VAL: accepts only PIDs with property strictly greater than `VAL` (only for 1-dimension number properties). ++ + + A sourceID name can also use wildcard or be empty to match a property regardless of the source filter. + Example +@@ -288,44 +343,52 @@ Example + fA fB:SID=*#ServiceID=2 + fA fB:SID=#ServiceID=2 + ``` ++ + This indicates to match connection between `fA` and `fB` only for PIDs with a `ServiceID` property of `2`. + These extensions also work with the _LINK_ `@` shortcut. + Example + ``` + fA fB @1#video fC +-``` ++``` ++ + This indicates that `fC` only accepts inputs from `fA`, and of type video. + Example + ``` + gpac -i img.heif @#ItemID=200 vout +-``` ++``` ++ + This indicates to connect to `vout` only PIDs with `ItemID` property equal to `200`. + Example + ``` + gpac -i vid.mp4 @#PID=1 vout +-``` ++``` ++ + This indicates to connect to `vout` only PIDs with `ID` property equal to `1`. + Example + ``` + gpac -i vid.mp4 @#Width=640 vout +-``` ++``` ++ + This indicates to connect to `vout` only PIDs with `Width` property equal to `640`. + Example + ``` + gpac -i vid.mp4 @#Width-640 vout + ``` ++ + This indicates to connect to `vout` only PIDs with `Width` property less than `640` + Example + ``` + gpac -i vid.mp4 @#ID=ItemID#ItemNumber=1 vout +-``` ++``` ++ + This will connect to `vout` only PID with an ID property equal to ItemID property (keep items, discard tracks) and an Item number of 1 (first item). + + Multiple fragment can be specified to check for multiple PID properties. + Example + ``` + gpac -i vid.mp4 @#Width=640#Height+380 vout +-``` ++``` ++ + This indicates to connect to `vout` only PIDs with `Width` property equal to `640` and `Height` greater than `380`. + + __Warning: If a PID directly connects to one or more explicitly loaded filters, no further dynamic link resolution will be done to connect it to other filters with no sourceID set. Link directives should be carefully setup.__ +@@ -333,7 +396,8 @@ __Warning: If a PID directly connects to one or more explicitly loaded filters, + Example + ``` + fA @ reframer fB +-``` ++``` ++ + If `fB` accepts inputs provided by `fA` but `reframer` does not, this will link `fA` PID to `fB` filter since `fB` has no sourceID. + Since the PID is connected, the filter engine will not try to solve a link between `fA` and `reframer`. + +@@ -341,36 +405,44 @@ An exception is made for local files: by default, a local file destination will + Example + ``` + gpac -i file.mp4 -o dump.mp4 +-``` ++``` ++ + This will prevent direct connection of PID of type `file` to dst `file.mp4`, remultiplexing the file. + + The special option `nomux` is used to allow direct connections (ignored for non-sink filters). + Example + ``` + gpac -i file.mp4 -o dump.mp4:nomux +-``` ++``` ++ + This will result in a direct file copy. + + This only applies to local files destination. For pipes, sockets or other file outputs (HTTP, ROUTE): ++ + - direct copy is enabled by default + - `nomux=0` can be used to force remultiplex ++ + + ## Sub-session tagging + Filters may be assigned to a sub-session using `:FS=N`, with `N` a positive integer. + Filters belonging to different sub-sessions may only link to each-other: ++ + - if explicitly allowed through sourceID directives (`@` or `SID`) + - or if they have the same sub-session identifier ++ + + This is mostly used for _implicit mode_ in `gpac`: each first source filter specified after a sink filter will trigger a new sub-session. + Example + ``` + gpac -i in1.mp4 -i in2.mp4 -o out1.mp4 -o out2.mp4 +-``` ++``` ++ + This will result in both inputs multiplexed in both outputs. + Example + ``` + gpac -i in1.mp4 -o out1.mp4 -i in2.mp4 -o out2.mp4 +-``` ++``` ++ + This will result in in1 mixed to out1 and in2 mixed to out2, these last two filters belonging to a different sub-session. + + # Arguments inheriting +@@ -379,24 +451,28 @@ Unless explicitly disabled (see [-max-chain](core_options/#max-chain)), the filt + Example + ``` + gpac -i file.mp4:OPT -o file.aac -o file.264 +-``` ++``` ++ + This will pass the `:OPT` to all filters loaded between the source and the two destinations. + Example + ``` + gpac -i file.mp4 -o file.aac:OPT -o file.264 + ``` ++ + This will pass the `:OPT` to all filters loaded between the source and the file.aac destination. + _Note: the destination arguments inherited are the arguments placed __AFTER__ the `dst=` option._ + Example + ``` + gpac -i file.mp4 fout:OPTFOO:dst=file.aac:OPTBAR +-``` ++``` ++ + This will pass the `:OPTBAR` to all filters loaded between `file.mp4` source and `file.aac` destination, but not `OPTFOO`. + Arguments inheriting can be stopped by using the keyword `gfloc`: arguments after the keyword will not be inherited. + Example + ``` + gpac -i file.mp4 -o file.aac:OPTFOO:gfloc:OPTBAR -o file.264 + ``` ++ + This will pass `:OPTFOO` to all filters loaded between `file.mp4` source and `file.aac` destination, but not `OPTBAR` + Arguments are by default tracked to check if they were used by the filter chain, and a warning is thrown if this is not the case. + It may be useful to specify arguments which may not be consumed depending on the graph resolution; the specific keyword `gfopt` indicates that arguments after the keyword will not be tracked. +@@ -404,6 +480,7 @@ Example + ``` + gpac -i file.mp4 -o file.aac:OPTFOO:gfopt:OPTBAR -o file.264 + ``` ++ + This will warn if `OPTFOO` is not consumed, but will not track `OPTBAR`. + + A filter may be assigned a name (for inspection purposes, not inherited) using `:N=name` option. This name is not used in link resolution and may be changed at runtime by the filter instance. +@@ -417,16 +494,18 @@ A filter may also be assigned an inherited tag (any string) using `:ITAG=name` o + + Destination URLs can be dynamically constructed using templates. Pattern `$KEYWORD$` is replaced in the template with the resolved value and `$KEYWORD%%0Nd$` is replaced in the template with the resolved integer, padded with up to N zeros if needed. + `KEYWORD` is __case sensitive__, and may be present multiple times in the string. Supported `KEYWORD`: +-* num: replaced by file number if defined, 0 otherwise +-* PID: ID of the source PID +-* URL: URL of source file +-* File: path on disk for source file; if not found, use URL if set, or PID name otherwise +-* Type: name of stream type of PID (`video`, `audio` ...) +-* OType: same as `Type` but uses original type when stream is encrypted (e.g. move from `crypt` to `video`) +-* p4cc=ABCD: uses PID property with 4CC value `ABCD` +-* pname=VAL: uses PID property with name `VAL` +-* cts, dts, dur, sap: uses properties of first packet in PID at template resolution time +-* OTHER: locates property 4CC for the given name, or property name if no 4CC matches. ++ ++- num: replaced by file number if defined, 0 otherwise ++- PID: ID of the source PID ++- URL: URL of source file ++- File: path on disk for source file; if not found, use URL if set, or PID name otherwise ++- Type: name of stream type of PID (`video`, `audio` ...) ++- OType: same as `Type` but uses original type when stream is encrypted (e.g. move from `crypt` to `video`) ++- p4cc=ABCD: uses PID property with 4CC value `ABCD` ++- pname=VAL: uses PID property with name `VAL` ++- cts, dts, dur, sap: uses properties of first packet in PID at template resolution time ++- OTHER: locates property 4CC for the given name, or property name if no 4CC matches. ++ + + `$$` is an escape for $ + +@@ -435,6 +514,7 @@ Example + ``` + gpac -i dump.yuv:size=640x360 vcrop:wnd=0x0x320x180 c=avc:b=1M @2 c=avc:b=750k -o dump_$CropOrigin$x$Width$x$Height$.264 + ``` ++ + This will create a cropped version of the source, encoded in AVC at 1M, and a full version of the content in AVC at 750k. Outputs will be `dump_0x0x320x180.264` for the cropped version and `dump_0x0x640x360.264` for the non-cropped one. + + # Cloning filters +@@ -444,30 +524,35 @@ Example + ``` + gpac -i img.heif -o dump_$ItemID$.jpg + ``` ++ + In this case, only one item (likely the first declared in the file) will connect to the destination. + Other items will not be connected since the destination only accepts one input PID. + Example + ``` + gpac -i img.heif -o dump_$ItemID$.jpg +-``` ++``` ++ + In this case, the destination will be cloned for each item, and all will be exported to different JPEGs thanks to URL templating. + Example + ``` + gpac -i vid.mpd c=avc:FID=1 -o transcode.mpd:SID=1 +-``` ++``` ++ + In this case, the encoder will be cloned for each video PIDs in the source, and the destination will only use PIDs coming from the encoders. + + When implicit linking is enabled, all filters are by default clonable. This allows duplicating the processing for each PIDs of the same type. + Example + ``` + gpac -i dual_audio resample:osr=48k c=aac -o dst +-``` ++``` ++ + The `resampler` filter will be cloned for each audio PID, and the encoder will be cloned for each resampler output. + You can explicitly deactivate the cloning instructions: + Example + ``` + gpac -i dual_audio resample:osr=48k:clone=0 c=aac -o dst +-``` ++``` ++ + The first audio will connect to the `resample` filter, the second to the `enc` filter and the `resample` output will connect to a clone of the `enc` filter. + + # Templating filter chains +@@ -481,14 +566,16 @@ Example + ``` + gpac -i source.ts -o file_$ServiceID$.mp4:SID=*#ServiceID=* + gpac -i source.ts -o file_$ServiceID$.mp4:SID=#ServiceID= +-``` ++``` ++ + In this case, each new `ServiceID` value found when connecting PIDs to the destination will create a new destination file. + + Cloning in implicit linking mode applies to output as well: + Example + ``` + gpac -i dual_audio -o dst_$PID$.aac +-``` ++``` ++ + Each audio track will be dumped to aac (potentially reencoding if needed). + + # Assigning PID properties +@@ -496,10 +583,12 @@ Each audio track will be dumped to aac (potentially reencoding if needed). + It is possible to define properties on output PIDs that will be declared by a filter. This allows tagging parts of the graph with different properties than other parts (for example `ServiceID`). The syntax is the same as filter option, and uses the fragment separator to identify properties, e.g. `#Name=Value`. + This sets output PIDs property (4cc, built-in name or any name) to the given value. Value can be omitted for boolean (defaults to true, e.g. `:#Alpha`). + Non built-in properties are parsed as follows: ++ + - `file@FOO` will be declared as string with a value set to the content of `FOO`. + - `bxml@FOO` will be declared as data with a value set to the binarized content of `FOO`. + - `FOO` will be declared as string with a value set to `FOO`. + - `TYPE@FOO` will be parsed according to `TYPE`. If the type is not recognized, the entire value is copied as string. See `gpac -h props` for defined types. ++ + + User-assigned PID properties on filter `fA` will be inherited by all filters dynamically loaded to solve `fA -> fB` connection. + If `fB` also has user-assigned PID properties, these only apply starting from `fB` in the chain and are not inherited by filters between `fA` and `fB`. +@@ -509,7 +598,8 @@ __Warning: Properties are not filtered and override the properties of the filter + Example + ``` + gpac -i v1.mp4:#ServiceID=4 -i v2.mp4:#ServiceID=2 -o dump.ts +-``` ++``` ++ + This will multiplex the streams in `dump.ts`, using `ServiceID` 4 for PIDs from `v1.mp4` and `ServiceID` 2 for PIDs from `v2.mp4`. + + PID properties may be conditionally assigned by checking other PID properties. The syntax uses parenthesis (not configurable) after the property assignment sign: +@@ -526,33 +616,48 @@ _Note: When set, the default value (empty condition) always matches the PID, the + Example + ``` + gpac -i source.mp4:#MyProp=(audio)"Super Audio",(video)"Super Video" +-``` ++``` ++ + This will assign property `MyProp` to `Super Audio` for audio PIDs and to `Super Video` for video PIDs. + Example + ``` + gpac -i source.mp4:#MyProp=(audio1)"Super Audio" + ``` ++ + This will assign property `MyProp` to `Super Audio` for first audio PID declared. + Example + ``` + gpac -i source.mp4:#MyProp=(Width+1280)HD +-``` ++``` ++ + This will assign property `MyProp` to `HD` for PIDs with property `Width` greater than 1280. + + The property value can use templates with the following keywords: +-* $GINC(init[,inc]) or @GINC(...): replaced by integer for each new output PID of the filter (see specific filter options for details on syntax) +-* PROP (enclosed between `$` or `@`): replaced by serialized value of property `PROP` (name or 4CC) of the PID or with empty string if no such property ++ ++- $GINC(init[,inc]) or @GINC(...): replaced by integer for each new output PID of the filter (see specific filter options for details on syntax) ++- PROP (enclosed between `$` or `@`): replaced by serialized value of property `PROP` (name or 4CC) of the PID or with empty string if no such property ++ + Example + ``` + gpac -i source.ts:#ASID=$PID$ +-``` ++``` ++ + This will assign DASH AdaptationSet ID to the PID ID value. + Example + ``` + gpac -i source.ts:#RepresentationID=$ServiceID$ +-``` ++``` ++ + This will assign DASH Representation ID to the PID ServiceID value. + ++A property can also be removed by not specifying any value. Conditional removal is possible using the above syntax. ++Example ++``` ++gpac -i source.ts:#FOO= ++``` ++ ++This will remove the `FOO` property on the output PID. ++ + # Using option files + + It is possible to use a file to define options of a filter, by specifying the target file name as an option without value, i.e. `:myopts.txt`. +@@ -560,14 +665,17 @@ It is possible to use a file to define options of a filter, by specifying the ta + __Warning: Only local files are allowed.__ + + An option file is a simple text file containing one or more options or PID properties on one or more lines. ++ + - A line beginning with "//" is a comment and is ignored (not configurable). + - A line beginning with ":" indicates an escaped option (the entire line is parsed as a single option). ++ + Options in an option file may point to other option files, with a maximum redirection level of 5. + An option file declaration (`filter:myopts.txt`) follows the same inheritance rules as regular options. + Example + ``` + gpac -i source.mp4:myopts.txt:foo=bar -o dst + ``` ++ + Any filter loaded between `source.mp4` and `dst` will inherit both `myopts.txt` and `foo` options and will resolve options and PID properties given in `myopts.txt`. + + # Ignoring filters at run-time +@@ -578,17 +686,20 @@ When the PID codec ID matches one of the specified codec, the filter is replaced + Example + ``` + -i src c=avc:b=1m:ccp -o mux +-``` ++``` ++ + This will replace the encoder filter with a reframer if the input PID is in AVC|H264 format, or uses the encoder for other visual PIDs. + Example + ``` + -i src c=avc:b=1m:ccp=avc,hevc -o mux + ``` ++ + This will replace the encoder filter with a reframer if the input PID is in AVC|H264 or HEVC format, or uses the encoder for other visual PIDs. + Example + ``` + -i src cecrypt:cfile=drm.xml:ccp=aac -o mux +-``` ++``` ++ + This will replace the encryptor filter with a reframer if the input PID is in AAC format, or uses the encryptor for other PIDs. + + # Specific filter options +@@ -598,56 +709,67 @@ Some specific keywords are replaced when processing filter options. + __Warning: These keywords do not apply to PID properties. Multiple keywords cannot be defined for a single option.__ + + Defined keywords: +-* $GSHARE: replaced by system path to GPAC shared directory (e.g. /usr/share/gpac) +-* $GJS: replaced by the first path from global share directory and paths set through [-js-dirs](core_options/#js-dirs) that contains the file name following the macro, e.g. $GJS/source.js +-* $GDOCS: replaced by system path to: +- - application document directory for iOS +- - `EXTERNAL_STORAGE` environment variable if present or `/sdcard` otherwise for Android +- - user home directory for other platforms +-* $GLANG: replaced by the global config language option [-lang](core_options/#lang) +-* $GUA: replaced by the global config user agent option [-user-agent](core_options/#user-agent) +-* $GINC(init_val[,inc]): replaced by `init_val` and increment `init_val` by `inc` (positive or negative number, 1 if not specified) each time a new filter using this string is created. ++ ++- $GSHARE: replaced by system path to GPAC shared directory (e.g. /usr/share/gpac) ++- $GJS: replaced by the first path from global share directory and paths set through [-js-dirs](core_options/#js-dirs) that contains the file name following the macro, e.g. $GJS/source.js ++- $GDOCS: replaced by system path to: ++ ++ - application document directory for iOS ++ - `EXTERNAL_STORAGE` environment variable if present or `/sdcard` otherwise for Android ++ - user home directory for other platforms ++ ++- $GLANG: replaced by the global config language option [-lang](core_options/#lang) ++- $GUA: replaced by the global config user agent option [-user-agent](core_options/#user-agent) ++- $GINC(init_val[,inc]): replaced by `init_val` and increment `init_val` by `inc` (positive or negative number, 1 if not specified) each time a new filter using this string is created. ++ + + The `$GINC` construct can be used to dynamically assign numbers in filter chains: + Example + ``` + gpac -i source.ts tssplit @#ServiceID= -o dump_$GINC(10,2).ts +-``` ++``` ++ + This will dump first service in dump_10.ts, second service in dump_12.ts, etc... + + As seen previously, the following options may be set on any filter, but are not visible in individual filter help: +-* FID: filter identifier +-* SID: filter source(s) (string value) +-* N=NAME: filter name (string value) +-* FS: sub-session identifier (unsigned int value) +-* RSID: require sourceID to be present on target filters (no value) +-* TAG: filter tag (string value) +-* ITAG: filter inherited tag (string value) +-* FBT: buffer time in microseconds (unsigned int value) +-* FBU: buffer units (unsigned int value) +-* FBD: decode buffer time in microseconds (unsigned int value) +-* clone: explicitly enable/disable filter cloning flag (no value) +-* nomux: enable/disable direct file copy (no value) +-* gfreg: preferred filter registry names for link solving (string value) +-* gfloc: following options are local to filter declaration, not inherited (no value) +-* gfopt: following options are not tracked (no value) +-* gpac: argument separator for URLs (no value) +-* ccp: filter replacement control (string list value) +-* NCID: ID of netcap configuration to use (string) +-* LT: set additionnal log tools and levels for the filter usin same syntax as -logs, e.g. `:LT=filter@debug` (string value) +-* DBG: debug missing input PID property (`=pid`), missing input packet property (`=pck`) or both (`=all`) ++ ++- FID: filter identifier ++- SID: filter source(s) (string value) ++- N=NAME: filter name (string value) ++- FS: sub-session identifier (unsigned int value) ++- RSID: require sourceID to be present on target filters (no value) ++- TAG: filter tag (string value) ++- ITAG: filter inherited tag (string value) ++- FBT: buffer time in microseconds (unsigned int value) ++- FBU: buffer units (unsigned int value) ++- FBD: decode buffer time in microseconds (unsigned int value) ++- clone: explicitly enable/disable filter cloning flag (no value) ++- nomux: enable/disable direct file copy (no value) ++- gfreg: preferred filter registry names for link solving (string value) ++- gfloc: following options are local to filter declaration, not inherited (no value) ++- gfopt: following options are not tracked (no value) ++- gpac: argument separator for URLs (no value) ++- ccp: filter replacement control (string list value) ++- NCID: ID of netcap configuration to use (string) ++- LT: set additionnal log tools and levels for the filter usin same syntax as -logs, e.g. `:LT=filter@debug` (string value) ++- DBG: debug missing input PID property (`=pid`), missing input packet property (`=pck`) or both (`=all`) ++ + + The buffer control options are used to change the default buffering of PIDs of a filter: ++ + - `FBT` controls the maximum buffer time of output PIDs of a filter + - `FBU` controls the maximum number of packets in buffer of output PIDs of a filter when timing is not available + - `FBD` controls the maximum buffer time of input PIDs of a decoder filter, ignored for other filters ++ + + If another filter sends a buffer requirement messages, the maximum value of `FBT` (resp. `FBD`) and the user requested buffer time will be used for output buffer time (resp. decoding buffer time). + + The options `FBT`, `FBU`, `FBD` and `DBG` can be set: +-* per filter instance: `fA reframer:FBU=2` +-* per filter class for the run: `--reframer@FBU=2` +-* in the GPAC config file in a per-filter section: `[filter@reframer]FBU=2` ++ ++- per filter instance: `fA reframer:FBU=2` ++- per filter class for the run: `--reframer@FBU=2` ++- in the GPAC config file in a per-filter section: `[filter@reframer]FBU=2` ++ + + The default values are defined by the session default parameters `-buffer-gen`, `buffer-units` and `-buffer-dec`. + +diff --git a/docs/Filters/filters_properties.md b/docs/Filters/filters_properties.md +index a8b1f0e3..b8fc355e 100644 +--- a/docs/Filters/filters_properties.md ++++ b/docs/Filters/filters_properties.md +@@ -1,6 +1,6 @@ + + +-# GPAC Built-in properties ++# GPAC Built-in properties {:data-level="all"} + + + ## Built-in property types +@@ -41,8 +41,10 @@ alay | channel layout configuration, string or int value from ISO/IEC 23091-3 + ## Built-in properties for PIDs and packets, pixel formats and audio formats + + Flags can be: +-* D: droppable property, see [GSF multiplexer](gsfmx) filter help for more info +-* P: property applying to packet ++ ++- D: droppable property, see [GSF multiplexer](gsfmx) filter help for more info ++- P: property applying to packet ++ + + Name | type | Flags | Description | 4CC + --- | --- | --- | --- | --- +@@ -55,7 +57,8 @@ ServiceID | uint | D | ID of parent service | PSID + ClockID | uint | D | ID of clock reference PID | CKID + DependencyID | uint | | ID of layer depended on | DPID + SubLayer | bool | | PID is a sublayer of the stream depended on rather than an enhancement layer | DPSL +-PlaybackMode | uint | D | Playback mode supported:
    * 0: no time control
    * 1: play/pause/seek,speed=1
    * 2: play/pause/seek,speed>=0
    * 3: play/pause/seek, reverse playback | PBKM ++PlaybackMode | uint | D | Playback mode supported:
    ++- 0: no time control
    - 1: play/pause/seek,speed=1
    - 2: play/pause/seek,speed>=0
    - 3: play/pause/seek, reverse playback | PBKM + Scalable | bool | | Scalable stream | SCAL + TileBase | bool | | Tile base stream | SABT + TileID | uint | | ID of the tile for hvt1/hvt2 PIDs | PTID +@@ -79,7 +82,7 @@ TimeshiftDepth | frac | D | Depth of the timeshift buffer | PTSD + TimeshiftTime | dbl | D | Time in the timeshift buffer in seconds - changes are signaled through PID info (no reconfigure) | PTST + TimeshiftState | uint | D | State of timeshift buffer: 0 is OK, 1 is underflow, 2 is overflow - changes are signaled through PID info (no reconfigure) | PTSS + Timescale | uint | | Media timescale (a timestamp delta of N is N/timescale seconds) | TIMS +-ProfileLevel | uint | D | MPEG-4 profile and level | PRPL ++ProfileLevel | uint | D | Profile and level indication | PRPL + DecoderConfig | mem | | Decoder configuration data | DCFG + DecoderConfigEnhancement | mem | | Decoder configuration data of the enhancement layer(s). Also used by 3GPP/Apple text streams to give the full sample description table used in SDP. | ECFG + DSISuperset | bool | | Decoder config is a superset of previous decoder config | DCFS +@@ -104,7 +107,7 @@ BitDepthLuma | uint | | Bit depth for luma components | YBPS + BitDepthChroma | uint | | Bit depth for chroma components | CBPS + FPS | frac | | Video framerate | VFPF + Interlaced | bool | | Video is interlaced | VILC +-SAR | frac | | Sample (i.e. pixel) aspect ratio | PSAR ++SAR | frac | | Sample (i.e. pixel) aspect ratio (negative values mean no SAR and removal of info in containers) | PSAR + MaxWidth | uint | | Maximum width (video / text / graphics) of all enhancement layers | MWID + MaxHeight | uint | | Maximum height (video / text / graphics) of all enhancement layers | MHEI + ZOrder | sint | | Z-order of the video, from 0 (first) to max int (last) | VZIX +@@ -117,7 +120,8 @@ CropOrigin | v2di | | Position in source window, X,Y indicate coordinates in so + OriginalSize | v2di | | Original resolution of video | VOWH + SRD | v4di | | Position and size of the video in the referential given by SRDRef | SRD + SRDRef | v2di | | Width and Height of the SRD referential | SRDR +-SRDMap | uintl | | Mapping of input videos in reconstructed video, expressed as {Ox,Oy,Ow,Oh,Dx,Dy,Dw,Dh} per input, with:
    * Ox,Oy,Ow,Oh: position and size of the input video (usually matching its `SRD` property), expressed in the output referential given by `SRDRef`
    * Dx,Dy,Dw,Dh: Position and Size of the input video in the reconstructed output, expressed in the output referential given by `SRDRef` | SRDM ++SRDMap | uintl | | Mapping of input videos in reconstructed video, expressed as {Ox,Oy,Ow,Oh,Dx,Dy,Dw,Dh} per input, with:
    ++- Ox,Oy,Ow,Oh: position and size of the input video (usually matching its `SRD` property), expressed in the output referential given by `SRDRef`
    - Dx,Dy,Dw,Dh: Position and Size of the input video in the reconstructed output, expressed in the output referential given by `SRDRef` | SRDM + Alpha | bool | | Video in this PID is an alpha map | VALP + Mirror | uint | | Mirror mode (as bit mask with flags 0: no mirror, 1: along Y-axis, 2: along X-axis) | VMIR + Rotate | uint | | Video rotation as value*90 degree anti-clockwise | VROT +@@ -178,7 +182,8 @@ CENC_SAI | mem | P | CENC SAI for the packet, formatted as (char(IV_Size))IV(u16 + KeyInfo | mem | | Multi key info formatted as:
    `is_mkey(u8);`
    nb_keys(u16);
    [
    IV_size(u8);
    KID(bin128);
    if (!IV_size) {;
    const_IV_size(u8);
    constIV(const_IV_size);
    }
    ]
    ` | CBIV ` + CENCPattern | frac | | CENC crypt pattern, CENC pattern, skip as frac.num crypt as frac.den | CPTR + CENCStore | 4cc | | Storage location 4CC of SAI data | CSTR +-CENCstsdMode | uint | | Mode for CENC sample description when using clear samples:
    * 0: single sample description is used
    * 1: a clear clone of the sample description is created, inserted before the CENC sample description
    * 2: a clear clone of the sample description is created, inserted after the CENC sample description | CSTM ++CENCstsdMode | uint | | Mode for CENC sample description when using clear samples:
    ++- 0: single sample description is used
    - 1: a clear clone of the sample description is created, inserted before the CENC sample description
    - 2: a clear clone of the sample description is created, inserted after the CENC sample description | CSTM + AMRModeSet | uint | | ModeSet for AMR and AMR-WideBand | AMST + SubSampleInfo | mem | | Binary blob describing N subsamples of the sample, formatted as N [(u32)flags(u32)size(u32)codec_param(u8)priority(u8) discardable]. Subsamples for a given flag MUST appear in order, however flags can be interleaved | SUBS + NALUMaxSize | uint | | Max size of NAL units in stream - changes are signaled through PID info change (no reconfigure) | NALS +@@ -282,7 +287,8 @@ EQRClamp | v4di | D | Clamping of frame for EQR as 0.32 fixed point (x is top, y + SceneNode | bool | | PID is a scene node decoder (AFX BitWrapper in BIFS) | PSND + OrigCryptoScheme | 4cc | | Original crypto scheme on a decrypted PID | POCS + TSBSegs | uint | D | Time shift in number of segments for HAS streams, only set by dashin and dasher filters | PTSN +-IsManifest | uint | D | PID is a HAS manifest (MSB=1 if live)
    * 0: not a manifest
    * 1: DASH manifest
    * 2: HLS manifest
    * 3: GHI(X) manifest | PHSM ++IsManifest | uint | D | PID is a HAS manifest (MSB=1 if live)
    ++- 0: not a manifest
    - 1: DASH manifest
    - 2: HLS manifest
    - 3: GHI(X) manifest | PHSM + Sparse | bool | D | PID has potentially empty times between packets | PSPA + CharSet | str | D | Character set for input text PID | PCHS + ForcedSub | uint | D | PID or Packet is forced sub
    0: not forced
    1: forced frame
    2: all frames are forced (PID only) | PFCS +@@ -521,6 +527,7 @@ dvbs | DVB Subtitles + dvbs | DVB-TeleText + div3 | MS-MPEG4 V3 + caf | Apple Lossless Audio ++dnx | AViD DNxHD + + # Audio channel layout code points (ISO/IEC 23091-3) + +@@ -530,17 +537,17 @@ mono | 1 | 0x0000000000000004 + stereo | 2 | 0x0000000000000003 + 3/0.0 | 3 | 0x0000000000000007 + 3/1.0 | 4 | 0x0000000000000407 +-3/2.0 | 5 | 0x0000000000000307 +-3/2.1 | 6 | 0x000000000000030f +-5/2.1 | 7 | 0x000000000000030f ++3/2.0 | 5 | 0x0000000000000037 ++3/2.1 | 6 | 0x000000000000003f ++5/2.1 | 7 | 0x000000000000003f + 1+1 | 8 | 0x0000000000000003 + 2/1.0 | 9 | 0x0000000000000403 + 2/2.0 | 10 | 0x0000000000000033 + 3/3.1 | 11 | 0x000000000000043f + 3/4.1 | 12 | 0x000000000000033f + 11/11.2 | 13 | 0x000000003ffe67cf +-5/2.1 | 14 | 0x000000000006030f +-5/5.2 | 15 | 0x000000000606630f ++5/2.1 | 14 | 0x000000000006003f ++5/5.2 | 15 | 0x000000000606603f + 5/4.1 | 16 | 0x000000000036003f + 6/5.1 | 17 | 0x00000000023e003f + 6/7.1 | 18 | 0x00000600023e003f +@@ -838,12 +845,28 @@ gmem | fin httpin | n/a + gfio | fin | fout + http | httpin | httpout + https | httpin | httpout ++dict | httpin | n/a ++ftp | httpin | n/a ++ftps | httpin | n/a ++gopher | httpin | n/a ++gophers | httpin | n/a ++imap | httpin | n/a ++imaps | httpin | n/a ++mqtt | httpin | n/a ++pop3 | httpin | n/a ++pop3s | httpin | n/a ++rtsp | httpin rtpin | rtspout ++smb | httpin | n/a ++smbs | httpin | n/a ++smtp | httpin | n/a ++smtps | httpin | n/a ++telnet | httpin | n/a ++tftp | httpin | n/a + tcp | sockin | sockout + udp | sockin | sockout + tcpu | sockin | sockout + udpu | sockin | sockout + rtp | rtpin | rtpout +-rtsp | rtpin | rtspout + rtspu | rtpin | n/a + rtsph | rtpin | rtspout + satip | rtpin | n/a +diff --git a/docs/Filters/fin.md b/docs/Filters/fin.md +index 858c827d..ed2aa5e6 100644 +--- a/docs/Filters/fin.md ++++ b/docs/Filters/fin.md +@@ -1,6 +1,6 @@ + + +-# File input ++# File input {:data-level="all"} + + Register name used to load filter: __fin__ + This filter may be automatically loaded during graph resolution. +@@ -15,6 +15,15 @@ The special file name `randsc` is used to generate random data with `0x000001` s + + The filter handles both files and GF_FileIO objects as input URL. + ++## Packet Injecting ++The filter can be used to inject a single packet instead of a file using (-pck)[] option. ++No specific properties are attached, except a timescale if (-ptime)[] is set. ++Example ++``` ++gpac fin:pck=str@"My Sample Text":ptime=2500/100:#CodecID=stxt:#StreamType=text ++``` ++This will declare the PID as WebVTT and send a single packet with payload `My Sample Text` and a timestamp value of 25 second. ++ + + # Options + +@@ -24,4 +33,5 @@ The filter handles both files and GF_FileIO objects as input URL. + __ext__ (cstr): override file extension + __mime__ (cstr): set file mime type + __pck__ (mem): data to use instead of file ++__ptime__ (frac, default: _0/0_): timing for data packet, ignored if den is 0 + +diff --git a/docs/Filters/flist.md b/docs/Filters/flist.md +index 70ebfc6c..c17d9dd2 100644 +--- a/docs/Filters/flist.md ++++ b/docs/Filters/flist.md +@@ -1,6 +1,6 @@ + + +-# Sources concatenator ++# Sources concatenator {:data-level="all"} + + Register name used to load filter: __flist__ + This filter may be automatically loaded during graph resolution. +@@ -16,21 +16,27 @@ At each new source, the filter tries to remap input PIDs to already declared out + + The source list mode is activated by using `flist:srcs=f1[,f2]`, where f1 can be a file or a directory to enumerate. + The syntax for directory enumeration is: +-* dir, dir/ or dir/*: enumerates everything in directory `dir` +-* foo/*.png: enumerates all files with extension png in directory `foo` +-* foo/*.png;*.jpg: enumerates all files with extension png or jpg in directory `foo` ++ ++- dir, dir/ or dir/*: enumerates everything in directory `dir` ++- foo/*.png: enumerates all files with extension png in directory `foo` ++- foo/*.png;*.jpg: enumerates all files with extension png or jpg in directory `foo` ++ + + The resulting file list can be sorted using [fsort](#fsort). + If the sort mode is `datex` and source files are images or single frame files, the following applies: ++ + - options [floop](#floop), [revert](#revert) and [fdur](#fdur) are ignored + - the files are sorted by modification time + - the first frame is assigned a timestamp of 0 + - each frame (coming from each file) is assigned a duration equal to the difference of modification time between the file and the next file + - the last frame is assigned the same duration as the previous one ++ + + When sorting by names: ++ + - shorter filenames are inserted before longer filenames + - alphabetical sorting is used if same filename length ++ + + # Playlist mode + +@@ -43,32 +49,40 @@ If the last URL played cannot be found in the playlist, the first URL in the pla + + When [ka](#ka) is used to keep refreshing the playlist on regular basis, the playlist must end with a new line. + Playlist refreshing will abort: ++ + - if the input playlist has a line not ending with a LF `(\n)` character, in order to avoid asynchronous issues when reading the playlist. + - if the input playlist has not been modified for the [timeout](#timeout) option value (infinite by default). + ++ + ## Playlist directives + A playlist directive line can contain zero or more directives, separated with space. The following directives are supported: +-* repeat=N: repeats `N` times the content (hence played N+1). +-* start=T: tries to play the file from start time `T` seconds (double format only). This may not work with some files/formats not supporting seeking. +-* stop=T: stops source playback after `T` seconds (double format only). This works on any source (implemented independently from seek support). +-* cat: specifies that the following entry should be concatenated to the previous source rather than opening a new source. This can optionally specify a byte range if desired, otherwise the full file is concatenated. ++ ++- repeat=N: repeats `N` times the content (hence played N+1). ++- start=T: tries to play the file from start time `T` seconds (double format only). This may not work with some files/formats not supporting seeking. ++- stop=T: stops source playback after `T` seconds (double format only). This works on any source (implemented independently from seek support). ++- cat: specifies that the following entry should be concatenated to the previous source rather than opening a new source. This can optionally specify a byte range if desired, otherwise the full file is concatenated. ++ + _Note: When sources are ISOBMFF files or segments on local storage or GF_FileIO objects, the concatenation will be automatically detected._ +-* srange=T: when cat is set, indicates the start `T` (64 bit decimal, default 0) of the byte range from the next entry to concatenate. +-* send=T: when cat is set, indicates the end `T` (64 bit decimal, default 0) of the byte range from the next entry to concatenate. +-* props=STR: assigns properties described in `STR` to all PIDs coming from the listed sources on next line. `STR` is formatted according to `gpac -h doc` using the default parameter set. +-* del: specifies that the source file(s) must be deleted once processed, true by default if [fdel](#fdel) is set. +-* out=V: specifies splicing start time (cf below). +-* in=V: specifies splicing end time (cf below). +-* nosync: prevents timestamp adjustments when joining sources (implied if `cat` is set). +-* keep: keeps spliced period in output (cf below). +-* mark: only inject marker for the splice period and do not load any replacement content (cf below). +-* sprops=STR: assigns properties described in `STR` to all PIDs of the main content during a splice (cf below). `STR` is formatted according to `gpac -h doc` using the default parameter set. +-* chap=NAME: assigns chapter name at the start of next URL (filter always removes source chapter names). ++ ++- srange=T: when cat is set, indicates the start `T` (64 bit decimal, default 0) of the byte range from the next entry to concatenate. ++- send=T: when cat is set, indicates the end `T` (64 bit decimal, default 0) of the byte range from the next entry to concatenate. ++- props=STR: assigns properties described in `STR` to all PIDs coming from the listed sources on next line. `STR` is formatted according to `gpac -h doc` using the default parameter set. ++- del: specifies that the source file(s) must be deleted once processed, true by default if [fdel](#fdel) is set. ++- out=V: specifies splicing start time (cf below). ++- in=V: specifies splicing end time (cf below). ++- nosync: prevents timestamp adjustments when joining sources (implied if `cat` is set). ++- keep: keeps spliced period in output (cf below). ++- mark: only inject marker for the splice period and do not load any replacement content (cf below). ++- sprops=STR: assigns properties described in `STR` to all PIDs of the main content during a splice (cf below). `STR` is formatted according to `gpac -h doc` using the default parameter set. ++- chap=NAME: assigns chapter name at the start of next URL (filter always removes source chapter names). ++ + + The following global options (applying to the filter, not the sources) may also be set in the playlist: +-* ka=N: force [ka](#ka) option to `N` millisecond refresh. +-* floop=N: set [floop](#floop) option from within playlist. +-* raw: set [raw](#raw) option from within playlist. ++ ++- ka=N: force [ka](#ka) option to `N` millisecond refresh. ++- floop=N: set [floop](#floop) option from within playlist. ++- raw: set [raw](#raw) option from within playlist. ++ + + The default behavior when joining sources is to realign the timeline origin of the new source to the maximum time in all PIDs of the previous sources. + This may create gaps in the timeline in case previous source PIDs are not of equal duration (quite common with most audio codecs). +@@ -97,13 +111,15 @@ __Warning: There shall be a single character, with value space (' '), before and + Example + ``` + src.mp4 @ reframer:rt=on +-``` ++``` ++ + This will inject a reframer with real-time regulation between source and `flist` filter. + Example + ``` + src.mp4 @ reframer:saps=1 @1 reframer:saps=0,2,3 + src.mp4 @ reframer:saps=1 @-1 reframer:saps=0,2,3 + ``` ++ + This will inject a reframer filtering only SAP1 frames and a reframer filtering only non-SAP1 frames between source and `flist` filter + + Link options can be specified (see `gpac -h doc`). +@@ -111,6 +127,7 @@ Example + ``` + src.mp4 @#video reframer:rt=on + ``` ++ + This will inject a reframer with real-time regulation between video PID of source and `flist` filter. + + When using filter chains, the `flist` filter will only accept PIDs from the last declared filter in the chain. +@@ -119,13 +136,15 @@ Example + ``` + src.mp4 @#video reframer:rt=on @-1#audio + ``` ++ + This will inject a reframer with real-time regulation between video PID of source and `flist` filter, and will also allow audio PIDs from source to connect to `flist` filter. + + The empty link directive can also be used on the last declared filter + Example + ``` + src.mp4 @ reframer:rt=on @#audio +-``` ++``` ++ + This will inject a reframer with real-time regulation between source and `flist` filter and only connect audio PIDs to `flist` filter. + + ## Splicing +@@ -136,11 +155,13 @@ Directive can be used for the main media except concatenation directives. + The splicing operations do not alter media frames and do not perform uncompressed domain operations such as cross-fade or mixing. + + The `out` (resp. `in`) directive specifies the media splice start (resp. end) time. The value can be formatted as follows: +-* empty: the time is not yet assigned +-* `now`: the time is resolved to the next SAP point in the media +-* integer, float or fraction: set time in seconds +-* `+VAL`: used for `in` only, specify the end point as delta in seconds from the start point (`VAL` can be integer, float or fraction) +-* DATE: set splice time according to wall clock `DATE`, formatted as an `XSD dateTime` ++ ++- empty: the time is not yet assigned ++- `now`: the time is resolved to the next SAP point in the media ++- integer, float or fraction: set time in seconds ++- `+VAL`: used for `in` only, specify the end point as delta in seconds from the start point (`VAL` can be integer, float or fraction) ++- DATE: set splice time according to wall clock `DATE`, formatted as an `XSD dateTime` ++ + The splice times (except wall clock) are expressed in the source (main media) timing, not the reconstructed output timeline. + + When a splice begins (`out` time reached), the source items following the main media are played until the end of the splice or the end of the main media. +@@ -149,12 +170,16 @@ Sources used during the splice period can use directives such as `start`, `dur` + Once a splice is done (`in` time reached), the main media `out` splice time is reset to undefined. + + When the main media has undefined `out` or `in` splice times, the playlist is reloaded at each new main media packet to check for resolved values. ++ + - `out` can only be modified when no splice is active, otherwise it is ignored. If modified, it resets the next source to play to be the one following the modified main media. + - `in` can only be modified when a splice is active with an undefined end time, otherwise it is ignored. ++ + + When the main media is over: ++ + - if `repeat` directive is set, the main media is repeated, `in` and `out` set to their initial values and the next splicing content is the one following the main content, + - otherwise, the next source queued is the one following the last source played during the last splice period. ++ + + It is allowed to defined several main media in the playlist, but a main media is not allowed as media for a splice period. + +@@ -171,6 +196,7 @@ Example + #out=2 in=4 mark sprops=#xlink=http://foo.bar/ + src:#Period=main + ``` ++ + This will inject property xlink on the output PIDs in the splice zone (corresponding to period `main_2`) but not in the rest of the main media. + + Directives `mark`, `keep` and `sprops` are reset at the end of the splice period. +@@ -186,20 +212,22 @@ Directives `mark`, `keep` and `sprops` are reset at the end of the splice period + __ka__ (uint, default: _0_): keep playlist alive (disable loop), waiting for a new input to be added or `#end` directive to end playlist. The value specifies the refresh rate in ms + __timeout__ (luint, default: _-1_): timeout in ms after which the playlist is considered dead (`-1` means indefinitely) + __fsort__ (enum, default: _no_): sort list of files +-* no: no sorting, use default directory enumeration of OS +-* name: sort by alphabetical name +-* size: sort by increasing size +-* date: sort by increasing modification time +-* datex: sort by increasing modification time ++ ++- no: no sorting, use default directory enumeration of OS ++- name: sort by alphabetical name ++- size: sort by increasing size ++- date: sort by increasing modification time ++- datex: sort by increasing modification time + + __sigcues__ (bool, default: _false_): inject `CueStart` property at each source begin (new or repeated) for DASHing + __fdel__ (bool, default: _false_): delete source files after processing in playlist mode (does not delete the playlist) + __keepts__ (bool, default: _false_): keep initial timestamps unmodified (no reset to 0) + __raw__ (enum, default: _no_): force input AV streams to be in raw format +-* no: do not force decoding of inputs +-* av: force decoding of audio and video inputs +-* a: force decoding of audio inputs +-* v: force decoding of video inputs ++ ++- no: do not force decoding of inputs ++- av: force decoding of audio and video inputs ++- a: force decoding of audio inputs ++- v: force decoding of video inputs + + __flush__ (bool, default: _false_): send a flush signal once playlist is done before entering keepalive + +diff --git a/docs/Filters/fout.md b/docs/Filters/fout.md +index 1ddae11f..006551db 100644 +--- a/docs/Filters/fout.md ++++ b/docs/Filters/fout.md +@@ -1,6 +1,6 @@ + + +-# File output ++# File output {:data-level="all"} + + Register name used to load filter: __fout__ + This filter may be automatically loaded during graph resolution. +@@ -18,14 +18,17 @@ In this case it accepts ANY type of input PID, not just file ones. + # HTTP streaming recording + + When recording a DASH or HLS session, the number of segments to keep per quality can be set using [max_cache_segs](#max_cache_segs). ++ + - value `0` keeps everything (default behaviour) + - a negative value `N` will keep `-N` files regardless of the time-shift buffer value + - a positive value `N` will keep `MAX(N, time-shift buffer)` files ++ + + Example + ``` + gpac -i LIVE_MPD dashin:forward=file -o rec/$File$:max_cache_segs=3 +-``` ++``` ++ + This will force keeping a maximum of 3 media segments while recording the DASH session. + + +@@ -39,14 +42,16 @@ This will force keeping a maximum of 3 media segments while recording the DASH s + __ext__ (cstr): set extension for graph resolution, regardless of file extension + __mime__ (cstr): set mime type for graph resolution + __cat__ (enum, default: _none_): cat each file of input PID rather than creating one file per filename +-* none: never cat files +-* auto: only cat if files have same names +-* all: always cat regardless of file names ++ ++- none: never cat files ++- auto: only cat if files have same names ++- all: always cat regardless of file names + + __ow__ (enum, default: _yes_): overwrite output mode when concatenation is not used +-* yes: override file if existing +-* no: throw error if file existing +-* ask: interactive prompt ++ ++- yes: override file if existing ++- no: throw error if file existing ++- ask: interactive prompt + + __mvbk__ (uint, default: _8192_): block size used when moving parts of the file around in patch mode + __redund__ (bool, default: _false_): keep redundant packet in output file +diff --git a/docs/Filters/ghidmx.md b/docs/Filters/ghidmx.md +index 896b6a57..d7272f3c 100644 +--- a/docs/Filters/ghidmx.md ++++ b/docs/Filters/ghidmx.md +@@ -1,6 +1,6 @@ + + +-# GHI demultiplexer ++# GHI demultiplexer {:data-level="all"} + + Register name used to load filter: __ghidmx__ + This filter may be automatically loaded during graph resolution. +@@ -16,11 +16,13 @@ Example + ``` + gpac -i SRC [... -i SRCn] -o index.ghi:segdur=2 + ``` ++ + This constructs a binary index for the DASH session. + Example + ``` + gpac -i SRC -o index.ghix:segdur=2 +-``` ++``` ++ + This constructs an XML index for the DASH session. + + __Warning: XML indexes should only be used for debug purposes as they can take a significant amount of time to be parsed.__ +@@ -33,13 +35,15 @@ The index can be used to generate manifest, child variants for HLS, init segment + Example + ``` + gpac -i index.ghi:gm=all -o dash/vod.mpd +-``` ++``` ++ + This generates manifest(s) and init segment(s). + + Example + ``` + gpac -i index.ghi:rep=FOO:sn=10 -o dash/vod.mpd +-``` ++``` ++ + This generates the 10th segment of representation with ID `FOO`. + + _Note: The manifest file(s) and init segment(s) are not written when generating a segment. The manifest target (mpd or m3u8) is only used to setup the filter chain and target output path._ +@@ -47,25 +51,31 @@ _Note: The manifest file(s) and init segment(s) are not written when generating + Example + ``` + gpac -i index.ghi:gm=main -o dash/vod.m3u8 +-``` ++``` ++ + This generates main manifest only (MPD or master HLS playlist). + + Example + ``` + gpac -i index.ghi:gm=child:rep=FOO:out=BAR -o dash/vod.m3u8 +-``` ++``` ++ + This generates child manifest for representation `FOO` in file `BAR`. + + Example + ``` + gpac -i index.ghi:gm=init:rep=FOO:out=BAR2 -o dash/vod.m3u8 + ``` ++ + This generates init segment for representation `FOO` in file `BAR2`. + + The filter outputs are PIDs using framed packets marked with segment boundaries and can be chained to other filters before entering the dasher (e.g. for encryption, transcode...). + + If representation IDs are not assigned during index creation, they default to the 1-based index of the source. You can check them using: +-EX: `gpac -i src.ghi inspect:full` ++Example ++``` ++gpac -i src.ghi inspect:full ++``` + + # Muxed Representations + +@@ -73,42 +83,50 @@ The filter can be used to generate muxed representations, either at manifest gen + Example + ``` + gpac -i index.ghi:mux=A@V1@V2 -o dash/vod.mpd +-``` ++``` ++ + This will generate a manifest muxing representations `A` with representations `V1` and `V2`. + + Example + ``` + gpac -i index.ghi:mux=A@V1@V2,T@V1@V2 -o dash/vod.mpd +-``` ++``` ++ + This will generate a manifest muxing representations `A` and `T` with representations `V1` and `V2`. + + Example + ``` + gpac -i index.ghi:rep=V2:sn=5:mux=A@V2 -o dash/vod.mpd +-``` ++``` ++ + This will generate the 5th segment containing representations `A` and `V2`. + + The filter does not store any state, it is the user responsibility to use consistent information across calls: ++ + - do not change segment templates + - do not change muxed representations to configurations not advertised in the generated manifests ++ + + # Recommendations + + Indexing supports fragmented and non-fragmented MP4, MPEG-2 TS and seekable inputs. ++ + - It is recommended to use fragmented MP4 as input format since this greatly reduces file loading times. + - If non-fragmented MP4 are used, it is recommended to use single-track files to decrease the movie box size and speedup parsing. + - MPEG-2 TS sources will be slower since they require PES reframing and AU reformating, resulting in more IOs than with mp4. + - other seekable sources will likely be slower (seeking, reframing) and are not recommended. ++ + + + # Options + + __gm__ (enum, default: _main_): manifest generation mode +-* none: no manifest generation (implied if sn is not 0) +-* all: generate all manifests and init segments +-* main: generate main manifest (MPD or master M3U8) +-* child: generate child playlist for HLS +-* init: generate init segment ++ ++- none: no manifest generation (implied if sn is not 0) ++- all: generate all manifests and init segments ++- main: generate main manifest (MPD or master M3U8) ++- child: generate child playlist for HLS ++- init: generate init segment + + __force__ (bool, default: _false_): force loading sources in manifest generation for debug + __rep__ (str): representation to generate +diff --git a/docs/Filters/glpush.md b/docs/Filters/glpush.md +index 4cfb1c03..dcbcad0e 100644 +--- a/docs/Filters/glpush.md ++++ b/docs/Filters/glpush.md +@@ -1,9 +1,9 @@ + + +-# GPU texture uploader ++# GPU texture uploader {:data-level="all"} + + Register name used to load filter: __glpush__ +-This is a JavaScript filter, not checked during graph resolution and needs explicit loading. ++This is a JavaScript filter. It is not checked during graph resolution and needs explicit loading. + Author: GPAC team + + This filter pushes input video streams to GPU as OpenGL textures. It can be used to simulate hardware decoders dispatching OpenGL textures +diff --git a/docs/Filters/gpac_general.md b/docs/Filters/gpac_general.md +index 53726f93..a25583d0 100644 +--- a/docs/Filters/gpac_general.md ++++ b/docs/Filters/gpac_general.md +@@ -1,13 +1,15 @@ + +-# General Usage of gpac ++# General Usage of gpac {:data-level="all"} + Usage: gpac [options] FILTER [LINK] FILTER [...] + gpac is GPAC's command line tool for setting up and running filter chains. + + _FILTER_: a single filter declaration (e.g., `-i file`, `-o dump`, `inspect`, ...), see [gpac -h doc](filters_general#filter-declaration-filter). + _[LINK]_: a link instruction (e.g., `@`, `@2`, `@2#StreamType=Visual`, ...), see [gpac -h doc](filters_general#explicit-links-between-filters-link). + _[options]_: one or more option strings, each starting with a `-` character. +- - an option using a single `-` indicates an option of gpac (see [gpac -hx](gpac_general#h)) or of libgpac (see [gpac -hx core](core_options)) +- - an option using `--` indicates a global filter or meta-filter (e.g. FFmpeg) option, e.g. `--block_size=1000` or `--profile=Baseline` (see [gpac -h doc](core_config#global-filter-options)) ++ ++ - an option using a single `-` indicates an option of gpac (see [gpac -hx](gpac_general#h)) or of libgpac (see [gpac -hx core](core_options)) ++ - an option using `--` indicates a global filter or meta-filter (e.g. FFmpeg) option, e.g. `--block_size=1000` or `--profile=Baseline` (see [gpac -h doc](core_config#global-filter-options)) ++ + + Filter declaration order may impact the link resolver which will try linking in declaration order. Most of the time for simple graphs, this has no impact. However, for complex graphs with no link declarations, this can lead to different results. + Options do not require any specific order, and may be present anywhere, including between link statements or filter declarations. +@@ -17,6 +19,8 @@ The session can be interrupted at any time using `ctrl+c`, which can also be use + + The possible options for gpac are: + ++__-mem-track__: enable memory tracker ++__-mem-track-stack__: enable memory tracker with stack dumping + __-ltf__: load test-unit filters (used for for unit tests only) + __-sloop__ (int): loop execution of session, creating a session at each loop, mainly used for testing. If no value is given, loops forever + __-runfor__ (int): run for the given amount of milliseconds, exit with full session flush +@@ -29,11 +33,13 @@ The possible options for gpac are: + __-qe__: enable quick exit (no mem cleanup) + __-k__: enable keyboard interaction from command line + __-r__ (string): enable reporting +-* r: runtime reporting +-* r=FA[,FB]: runtime reporting but only print given filters, e.g. `r=mp4mx` for ISOBMFF multiplexer only +-* r=: only print final report ++ ++- r: runtime reporting ++- r=FA[,FB]: runtime reporting but only print given filters, e.g. `r=mp4mx` for ISOBMFF multiplexer only ++- r=: only print final report + + __-seps__ (string, default: __:=#,!@__): set the default character sets used to separate various arguments ++ + - the first char is used to separate argument names + - the second char, if present, is used to separate names and values + - the third char, if present, is used to separate fragments for PID sources +@@ -55,40 +61,45 @@ The possible options for gpac are: + __-step__: test step mode in non-blocking session + __-h__,__-help,-ha,-hx,-hh__ (string): print help. Use `-help` or `-h` for basic options, `-ha` for advanced options, `-hx` for expert options and `-hh` for all. + _Note: The `@` character can be used in place of the `*` character. String parameter can be:_ +-* empty: print command line options help +-* doc: print the general filter info +-* alias: print the gpac alias syntax +-* log: print the log system help +-* core: print the supported libgpac core options. Use -ha/-hx/-hh for advanced/expert options +-* cfg: print the GPAC configuration help +-* net: print network interfaces +-* prompt: print the GPAC prompt help when running in interactive mode (see [-k](gpac_general/#k) ) +-* modules: print available modules +-* module NAME: print info and options of module `NAME` +-* creds: print credential help +-* filters: print name of all available filters +-* filters:*: print name of all available filters, including meta filters +-* codecs: print the supported builtin codecs - use `-hx` to include unmapped codecs (ffmpeg, ...) +-* formats: print the supported formats (`-ha`: print filter names, `-hx`: include meta filters (ffmpeg,...), `-hh`: print mime types) +-* protocols: print the supported protocol schemes (`-ha`: print filter names, `-hx`: include meta filters (ffmpeg,...), `-hh`: print all) +-* props: print the supported builtin PID and packet properties +-* props PNAME: print the supported builtin PID and packet properties mentioning `PNAME` +-* colors: print the builtin color names and their values +-* layouts: print the builtin CICP audio channel layout names and their values +-* links: print possible connections between each supported filters (use -hx to view src->dst cap bundle detail) +-* links FNAME: print sources and sinks for filter `FNAME` (either builtin or JS filter) +-* defer: print defer mode help +-* FNAME: print filter `FNAME` info (multiple FNAME can be given) +- - For meta-filters, use `FNAME:INST`, e.g. `ffavin:avfoundation` +- - Use `*` to print info on all filters (_big output!_), `*:*` to print info on all filters including meta filter instances (_really big output!_) +- - By default only basic filter options and description are shown. Use `-ha` to show advanced options capabilities, `-hx` for expert options, `-hh` for all options and filter capabilities including on filters disabled in this build +-* FNAME.OPT: print option `OPT` in filter `FNAME` +-* OPT: look in filter names and options for `OPT` and suggest possible matches if none found. Use `-hx` to look for keyword in all option descriptions ++ ++- empty: print command line options help ++- doc: print the general filter info ++- alias: print the gpac alias syntax ++- log: print the log system help ++- core: print the supported libgpac core options. Use -ha/-hx/-hh for advanced/expert options ++- cfg: print the GPAC configuration help ++- net: print network interfaces ++- prompt: print the GPAC prompt help when running in interactive mode (see [-k](gpac_general/#k) ) ++- modules: print available modules ++- module NAME: print info and options of module `NAME` ++- creds: print credential help ++- filters: print name of all available filters ++- filters:*: print name of all available filters, including meta filters ++- codecs: print the supported builtin codecs - use `-hx` to include unmapped codecs (ffmpeg, ...) ++- formats: print the supported formats (`-ha`: print filter names, `-hx`: include meta filters (ffmpeg,...), `-hh`: print mime types) ++- protocols: print the supported protocol schemes (`-ha`: print filter names, `-hx`: include meta filters (ffmpeg,...), `-hh`: print all) ++- props: print the supported builtin PID and packet properties ++- props PNAME: print the supported builtin PID and packet properties mentioning `PNAME` ++- colors: print the builtin color names and their values ++- layouts: print the builtin CICP audio channel layout names and their values ++- links: print possible connections between each supported filters (use -hx to view src->dst cap bundle detail) ++- links FNAME: print sources and sinks for filter `FNAME` (either builtin or JS filter) ++- defer: print defer mode help ++- FNAME: print filter `FNAME` info (multiple FNAME can be given) ++ ++ - For meta-filters, use `FNAME:INST`, e.g. `ffavin:avfoundation` ++ - Use `*` to print info on all filters (_big output!_), `*:*` to print info on all filters including meta filter instances (_really big output!_) ++ - By default only basic filter options and description are shown. Use `-ha` to show advanced options capabilities, `-hx` for expert options, `-hh` for all options and filter capabilities including on filters disabled in this build ++ ++- FNAME.OPT: print option `OPT` in filter `FNAME` ++- OPT: look in filter names and options for `OPT` and suggest possible matches if none found. Use `-hx` to look for keyword in all option descriptions + + + __-p__ (string): use indicated profile for the global GPAC config. If not found, config file is created. If a file path is indicated, this will load profile from that file. Otherwise, this will create a directory of the specified name and store new config there. The following reserved names create a temporary profile (not stored on disk): +-* 0: full profile +-* n: null profile disabling shared modules/filters and system paths in config (may break GUI and other filters) ++ ++- 0: full profile ++- n: null profile disabling shared modules/filters and system paths in config (may break GUI and other filters) ++ + Appending `:reload` to the profile name will force recreating a new configuration file + + __-alias__ (string): assign a new alias or remove an alias. Can be specified several times. See [alias usage (-h alias)](#using-aliases) +@@ -102,6 +113,7 @@ Appending `:reload` to the profile name will force recreating a new configuratio + __-xopt__: unrecognized options and filters declaration following this option are ignored - used to pass arguments to GUI + + __-creds__ (string): setup credentials as used by servers ++__-rv__: return absolute value of GPAC internal error instead of 1 when error + + + The following libgpac core options allow customizing the filter session: +@@ -110,21 +122,24 @@ The following libgpac core options allow customizing the filter session: + __-full-link__: throw error if any PID in the filter graph cannot be linked + __-no-dynf__: disable dynamically loaded filters + __-no-block__ (Enum, default: __no__): disable blocking mode of filters +-* no: enable blocking mode +-* fanout: disable blocking on fan-out, unblocking the PID as soon as one of its destinations requires a packet +-* all: disable blocking ++ ++- no: enable blocking mode ++- fanout: disable blocking on fan-out, unblocking the PID as soon as one of its destinations requires a packet ++- all: disable blocking + + __-no-reg__: disable regulation (no sleep) in session + __-no-reassign__: disable source filter reassignment in PID graph resolution + __-sched__ (Enum, default: __free__): set scheduler mode +-* free: lock-free queues except for task list (default) +-* lock: mutexes for queues when several threads +-* freex: lock-free queues including for task lists (experimental) +-* flock: mutexes for queues even when no thread (debug mode) +-* direct: no threads and direct dispatch of tasks whenever possible (debug mode) ++ ++- free: lock-free queues except for task list (default) ++- lock: mutexes for queues when several threads ++- freex: lock-free queues including for task lists (experimental) ++- flock: mutexes for queues even when no thread (debug mode) ++- direct: no threads and direct dispatch of tasks whenever possible (debug mode) + + __-max-chain__ (int, default: __6__): set maximum chain length when resolving filter links. Default value covers for _[ in -> ] dmx -> reframe -> decode -> encode -> reframe -> mx [ -> out]_. Filter chains loaded for adaptation (e.g. pixel format change) are loaded after the link resolution. Setting the value to 0 disables dynamic link resolution. You will have to specify the entire chain manually + __-max-sleep__ (int, default: __50__): set maximum sleep time slot in milliseconds when regulation is enabled ++__-step-link__: load filters one by one when solvink a link instead of loading all filters for the solved path + __-threads__ (int): set N extra thread for the session. -1 means use all available cores + __-no-probe__: disable data probing on sources and relies on extension (faster load but more error-prone) + __-no-argchk__: disable tracking of argument usage (all arguments will be considered as used) +@@ -138,28 +153,35 @@ The following libgpac core options allow customizing the filter session: + The gpac command line can become quite complex when many sources or filters are used. In order to simplify this, an alias system is provided. + + To assign an alias, use the syntax `gpac -alias="NAME VALUE"`. +-* `NAME`: shall be a single string, with no space. +-* `VALUE`: the list of argument this alias replaces. If not set, the alias is destroyed ++ ++- `NAME`: shall be a single string, with no space. ++- `VALUE`: the list of argument this alias replaces. If not set, the alias is destroyed ++ + + When parsing arguments, the alias will be replace by its value. + Example + ``` + gpac -alias="output aout vout" +-``` ++``` ++ + This allows later audio and video playback using `gpac -i src.mp4 output` + + Aliases can use arguments from the command line. The allowed syntaxes are: +-* `@{a}`: replaced by the value of the argument with index `a` after the alias +-* `@{a,b}`: replaced by the value of the arguments with index `a` and `b` +-* `@{a:b}`: replaced by the value of the arguments between index `a` and `b` +-* `@{-a,b}`: replaced by the value of the arguments with index `a` and `b`, inserting a list separator (comma by default) between them +-* `@{-a:b}`: replaced by the value of the arguments between index `a` and `b`, inserting a list separator (comma by default) between them +-* `@{+a,b}`: clones the parent word in the alias for `a` and `b`, replacing this pattern in each clone by the corresponding argument +-* `@{+a:b}`: clones the parent word in the alias for each argument between index `a` and `b`, replacing this pattern in each clone by the corresponding argument ++ ++- `@{a}`: replaced by the value of the argument with index `a` after the alias ++- `@{a,b}`: replaced by the value of the arguments with index `a` and `b` ++- `@{a:b}`: replaced by the value of the arguments between index `a` and `b` ++- `@{-a,b}`: replaced by the value of the arguments with index `a` and `b`, inserting a list separator (comma by default) between them ++- `@{-a:b}`: replaced by the value of the arguments between index `a` and `b`, inserting a list separator (comma by default) between them ++- `@{+a,b}`: clones the parent word in the alias for `a` and `b`, replacing this pattern in each clone by the corresponding argument ++- `@{+a:b}`: clones the parent word in the alias for each argument between index `a` and `b`, replacing this pattern in each clone by the corresponding argument ++ + + The specified index can be: +-* forward index: a strictly positive integer, 1 being the first argument after the alias +-* backward index: the value 'n' (or 'N') to indicate the last argument on the command line. This can be followed by `-x` to rewind arguments (e.g. `@{n-1}` is the before last argument) ++ ++- forward index: a strictly positive integer, 1 being the first argument after the alias ++- backward index: the value 'n' (or 'N') to indicate the last argument on the command line. This can be followed by `-x` to rewind arguments (e.g. `@{n-1}` is the before last argument) ++ + + Before solving aliases, all option arguments are moved at the beginning of the command line. This implies that alias arguments cannot be options. + Arguments not used by any aliases are kept on the command line, other ones are removed +@@ -167,22 +189,26 @@ Arguments not used by any aliases are kept on the command line, other ones are r + Example + ``` + -alias="foo src=@{N} dst=test.mp4" +-``` ++``` ++ + The command `gpac foo f1 f2` expands to `gpac src=f2 dst=test.mp4 f1` + Example + ``` + -alias="list: inspect src=@{+:N}" + ``` ++ + The command `gpac list f1 f2 f3` expands to `gpac inspect src=f1 src=f2 src=f3` + Example + ``` + -alias="list inspect src=@{+2:N}" + ``` ++ + The command `gpac list f1 f2 f3` expands to `gpac inspect src=f2 src=f3 f1` + Example + ``` + -alias="plist aout vout flist:srcs=@{-,N}" + ``` ++ + The command `gpac plist f1 f2 f3` expands to `gpac aout vout flist:srcs="f1,f2,f3"` + + Alias documentation can be set using `gpac -aliasdoc="NAME VALUE"`, with `NAME` the alias name and `VALUE` the documentation. +@@ -198,17 +224,19 @@ The file can be overwritten using the [-users](core_options/#users) option. + By default, this file does not exist until at least one user has been configured. + + The [creds](#creds) option allows inspecting or modifying the users and groups information. The syntax for the option value is: +-* `show` or no value: prints the `users.cfg` file +-* `reset`: deletes the `users.cfg` file (i.e. deletes all users and groups) +-* `NAME`: show information of user `NAME` +-* `+NAME`: adds user `NAME` +-* `+NAME:I1=V1[,I2=V2]`: sets info `I1` with value `V1` to user `NAME`. The info name `password` resets password without prompt. +-* `-NAME`: removes user `NAME` +-* `_NAME`: force password change of user `NAME` +-* `@NAME`: show information of group `NAME` +-* `@+NAME[:u1[,u2]]`: adds group `NAME` if not existing and adds specified users to group +-* `@-NAME:u1[,u2]`: removes specified users from group `NAME` +-* `@-NAME`: removes group `NAME` ++ ++- `show` or no value: prints the `users.cfg` file ++- `reset`: deletes the `users.cfg` file (i.e. deletes all users and groups) ++- `NAME`: show information of user `NAME` ++- `+NAME`: adds user `NAME` ++- `+NAME:I1=V1[,I2=V2]`: sets info `I1` with value `V1` to user `NAME`. The info name `password` resets password without prompt. ++- `-NAME`: removes user `NAME` ++- `_NAME`: force password change of user `NAME` ++- `@NAME`: show information of group `NAME` ++- `@+NAME[:u1[,u2]]`: adds group `NAME` if not existing and adds specified users to group ++- `@-NAME:u1[,u2]`: removes specified users from group `NAME` ++- `@-NAME`: removes group `NAME` ++ + + By default all added users are members of the group `users`. + Passwords are not stored, only a SHA256 hash is stored. +@@ -223,12 +251,14 @@ rg=bar + ``` + + The following keys are defined per directory, but may be ignored by the server depending on its operation mode: +-* ru: comma-separated list of user names with read access to the directory +-* rg: comma-separated list of group names with read access to the directory +-* wu: comma-separated list of user names with write access to the directory +-* wg: comma-separated list of group names with write access to the directory +-* mcast: comma-separated list of user names with multicast creation rights (RTSP server only) +-* filters: comma-separated list of filter names for which the directory is valid. If not found or `all`, applies to all filters ++ ++- ru: comma-separated list of user names with read access to the directory ++- rg: comma-separated list of group names with read access to the directory ++- wu: comma-separated list of user names with write access to the directory ++- wg: comma-separated list of group names with write access to the directory ++- mcast: comma-separated list of user names with multicast creation rights (RTSP server only) ++- filters: comma-separated list of filter names for which the directory is valid. If not found or `all`, applies to all filters ++ + + Rights can be configured on sub-directories by adding sections for the desired directories. + Example +@@ -238,8 +268,74 @@ rg=bar + [d1/d2] + ru=foo + ``` ++ + With this configuration: ++ + - the directory `d1` will be readable by all members of group `bar` + - the directory `d1/d2` will be readable by user `foo` only ++ + + Servers in GPAC currently only support the `Basic` HTTP authentication scheme, and should preferably be run over TLS. ++ ++# Defer test mode ++ ++This mode can be used to test loading filters one by one and asking for link resolution explicitly. ++This is mostly used to reproduce how sessions are build in more complex applications. ++ ++The options `rl`, `pi`, `pl` and `pd` allow adressing a filter by index `F` in a list. ++ ++- if the option is suffixed with an `x` (e.g. `rlx=`), `F=0` means the last filter in the list of filters in the session ++- otherwise, `F=0` means the last filter declared before the option ++ ++ ++The relink options `-rl` and `-rlx` always flush the session (run until no more tasks are scheduled). ++The last run can be omitted. ++ ++Example ++``` ++gpac -dl -np -i SRC reframer -g -rl -g inspect -g -rl ++``` ++ ++This will load SRC and reframer, print the graph (no connection), relink SRC, print the graph (connection to reframer), insert inspect, print the graph (no connection), relink reframer and run. No play event is sent here. ++Example ++``` ++gpac -dl -np -i SRC reframer inspect:deep -g -rl=2 -g -rl -se ++``` ++ ++This will load SRC, reframer and inspect, print the graph (no connection), relink SRC, print the graph (connection to reframer), print the graph (no connection), relink reframer, send play and run. ++ ++Linking can be done once filters are loaded, using the syntax `@F@SRC` or `@@F@SRC`: ++ ++- `@F` indicates the destination filter using a 0-based index `F` starting from the last laoded filter, e.g. `@0` indicates the last loaded filter. ++- `@@F` indicates the target filter using a 0-based index `F` starting from the first laoded filter, e.g. `@@1` indicates the second loaded filter. ++- `@SRC`or `@@SRC`: same syntax as link directives ++ ++Sources MUST be set before relinking outputs using (-rl)[]. ++Example ++``` ++gpac -dl -i SRC F1 F2 [...] @1@2 @0@2 ++``` ++ ++This will set SRC as source to F1 and SRC as source to F2 after loading all filters. ++ ++The following options are used in defer mode: ++ ++__-dl__: enable defer linking mode for step-by-step graph building tests ++__-np__: prevent play event from sinks ++__-rl[=F]__ (string): relink outputs of filter `F` (default 1) ++__-wl[=F]__ (string): same as `-rl` but does not flush session) ++__-pi=[+|-][F[:i]]__ (string): print PID properties (all or of index `i`) of filter `F` (default 0) ++ ++- if prefixed with `-`: only list PIDs ++- if prefixed with `+`: also print PID info ++ ++__-pl=[+][F[:i]]@NAME__ (string): probe filter chain from filter `F` (default 0) to the given filter `NAME`: ++ ++- if prefixed with `+`: print all known chains and their priorities ++ ++__-pd=[F[:i]]__ (string): print possible PID destinations (all or of index `i`) of filter `F` (default 0) ++__-f__: flush session until no more tasks ++__-g__: print graph ++__-s__: print stats ++__-se__: send PLAY event from sinks (only done once) ++__-m__ (string): print message +diff --git a/docs/Filters/gsfdmx.md b/docs/Filters/gsfdmx.md +index fa3a6616..88a3408e 100644 +--- a/docs/Filters/gsfdmx.md ++++ b/docs/Filters/gsfdmx.md +@@ -1,6 +1,6 @@ + + +-# GSF demultiplexer ++# GSF demultiplexer {:data-level="all"} + + Register name used to load filter: __gsfdmx__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/gsfmx.md b/docs/Filters/gsfmx.md +index 6cbd3c9e..3a7520c1 100644 +--- a/docs/Filters/gsfmx.md ++++ b/docs/Filters/gsfmx.md +@@ -1,6 +1,6 @@ + + +-# GSF Multiplexer ++# GSF Multiplexer {:data-level="all"} + + Register name used to load filter: __gsfmx__ + This filter may be automatically loaded during graph resolution. +@@ -60,9 +60,10 @@ This will DASH the source, store the manifest file and the media streams with th + __sigbo__ (bool, default: _false_): signal byte offset + __sigdts__ (bool, default: _true_): signal decoding timestamp + __dbg__ (enum, default: _no_): set debug mode +-* no: disable debug +-* nodata: force packet size to 0 +-* nopck: skip packet ++ ++- no: disable debug ++- nodata: force packet size to 0 ++- nopck: skip packet + + __key__ (mem): encrypt packets using given key + __IV__ (mem): set IV for encryption - a constant IV is used to keep packet overhead small (cbcs-like) +diff --git a/docs/Filters/hevcmerge.md b/docs/Filters/hevcmerge.md +index 6ad04ac0..9323549e 100644 +--- a/docs/Filters/hevcmerge.md ++++ b/docs/Filters/hevcmerge.md +@@ -1,6 +1,6 @@ + + +-# HEVC Tile merger ++# HEVC Tile merger {:data-level="all"} + + Register name used to load filter: __hevcmerge__ + This filter may be automatically loaded during graph resolution. +@@ -22,8 +22,10 @@ If `CropOrigin` is used, it shall be set on all input sources. + If positive coordinates are used, they specify absolute positioning in pixels of the tiles. The coordinates are automatically adjusted to the next multiple of max CU width and height. + If negative coordinates are used, they specify relative positioning (e.g. `0x-1` indicates to place the tile below the tile 0x0). + In this mode, it is the caller responsibility to set coordinates so that all tiles in a column have the same width and only the last row/column uses non-multiple of max CU width/height values. The filter will complain and abort if this is not respected. ++ + - If an horizontal blank is detected in the layout, an empty column in the tiling grid will be inserted. + - If a vertical blank is detected in the layout, it is ignored. ++ + + + ## Spatial Relationship Description (SRD) +@@ -32,31 +34,39 @@ The filter will create an `SRDMap` property in the output PID if `SRDRef` and `S + The `SRDMap` allows forwarding the logical sources `SRD` in the merged PID. + The output PID `SRDRef` is set to the output video size. + The input `SRDRef` and `SRD` are usually specified in DASH MPD, but can be manually assigned to inputs. ++ + - `SRDRef` gives the size of the referential used for the input `SRD` (usually matches the original video size, but not always) + - `SRD` gives the size and position of the input in the original video, expressed in `SRDRef` referential of the input. ++ + The inputs do not need to have matching `SRDRef` + .EX src1:SRD=0x0x640x480:SRDRef=1280x720 + This indicates that `src1` contains a video located at 0,0, with a size of 640x480 pixels in a virtual source of 1280x720 pixels. + Example + ``` + src2:SRD=640x0x640x480:SRDRef=1280x720 +-``` ++``` ++ + This indicates that `src1` contains a video located at 640,0, with a size of 640x480 pixels in a virtual source of 1280x720 pixels. + + Each merged input is described by 8 integers in the output `SRDMap`: ++ + - the source `SRD` is rescaled in the output `SRDRef` to form the first part (4 integers) of the `SRDMap` (i.e. _where was the input ?_) + - the source location in the reconstructed video forms the second part (4 integers) of the `SRDMap` (i.e. _where are the input pixels in the output ?_) ++ + + Assuming the two sources are encoded at 320x240 and merged as src2 above src1, the output will be a 320x480 video with a `SRDMap` of {0,160,160,240,0,0,320,240,0,0,160,240,0,240,320,240} + _Note: merged inputs are always listed in `SRDMap` in their tile order in the output bitstream._ + + Alternatively to using `SRD` and `SRDRef`, it is possible to specify `CropOrigin` property on the inputs, in which case: ++ + - the `CropOrigin` gives the location in the source + - the input size gives the size in the source, and no rescaling of referential is done ++ + Example + ``` + src1:CropOrigin=0x0 src1:CropOrigin=640x0 +-``` ++``` ++ + Assuming the two sources are encoded at 320x240 and merged as src1 above src2, the output will be a 320x480 video with a `SRDMap` of `{0,0,320,240,0,0,320,240,640,0,320,240,0,240,320,240}` + + +diff --git a/docs/Filters/hevcsplit.md b/docs/Filters/hevcsplit.md +index b494b5fc..62e84792 100644 +--- a/docs/Filters/hevcsplit.md ++++ b/docs/Filters/hevcsplit.md +@@ -1,6 +1,6 @@ + + +-# HEVC tile splitter ++# HEVC tile splitter {:data-level="all"} + + Register name used to load filter: __hevcsplit__ + This filter is not checked during graph resolution and needs explicit loading. +diff --git a/docs/Filters/httpin.md b/docs/Filters/httpin.md +index 0a521eec..5e44cb52 100644 +--- a/docs/Filters/httpin.md ++++ b/docs/Filters/httpin.md +@@ -1,6 +1,6 @@ + + +-# HTTP input ++# HTTP input {:data-level="all"} + + Register name used to load filter: __httpin__ + This filter may be automatically loaded during graph resolution. +@@ -19,13 +19,14 @@ _Note: Unless disabled at session level (see [-no-probe](core_options/#no-probe) + __src__ (cstr): URL of source content + __block_size__ (uint, default: _100000_): block size used to read file + __cache__ (enum, default: _none_): set cache mode +-* auto: cache to disk if content length is known, no cache otherwise +-* disk: cache to disk, discard once session is no longer used +-* keep: cache to disk and keep +-* mem: stores to memory, discard once session is no longer used +-* mem_keep: stores to memory, keep after session is reassigned but move to `mem` after first download +-* none: no cache +-* none_keep: stores to memory, keep after session is reassigned but move to `none` after first download ++ ++- auto: cache to disk if content length is known, no cache otherwise ++- disk: cache to disk, discard once session is no longer used ++- keep: cache to disk and keep ++- mem: stores to memory, discard once session is no longer used ++- mem_keep: stores to memory, keep after session is reassigned but move to `mem` after first download ++- none: no cache ++- none_keep: stores to memory, keep after session is reassigned but move to `none` after first download + + __range__ (lfrac, default: _0-0_): set byte range, as fraction + __ext__ (cstr): override file extension +diff --git a/docs/Filters/httpout.md b/docs/Filters/httpout.md +index 52218a7c..05825afc 100644 +--- a/docs/Filters/httpout.md ++++ b/docs/Filters/httpout.md +@@ -1,21 +1,25 @@ + + +-# HTTP Server ++# HTTP Server {:data-level="all"} + + Register name used to load filter: __httpout__ + This filter may be automatically loaded during graph resolution. + + The HTTP output filter can act as: ++ + - a simple HTTP server + - an HTTP server sink + - an HTTP server file sink + - an HTTP _client_ sink + - an HTTP server _source_ ++ + + The server currently handles GET, HEAD, PUT, POST, DELETE methods, and basic OPTIONS support. + Single or multiple byte ranges are supported for both GET and PUT/POST methods, in all server modes. ++ + - for GET, the resulting body is a single-part body formed by the concatenated byte ranges as requested (no overlap checking). + - for PUT/POST, the received data is pushed to the target file according to the byte ranges specified in the client request. ++ + + + __Warning: the partial PUT request is RFC2616 compliant but not compliant with RFC7230. PATCH method is not yet implemented in GPAC.__ +@@ -26,14 +30,17 @@ When multiple read directories are specified, the server root `/` contains the l + When a write directory is specified, the upload resource name identifies a file in this directory (the write directory name is not present in the URL). + + A directory rule file (cf `gpac -h creds`) can be specified in [rdirs](#rdirs) but NOT in [wdir](#wdir). When rules are used: ++ + - if a directory has a `name` rule, it will be used in the URL + - otherwise, the directory is directly available under server root `/` + - read and write access rights are checked ++ + Example + ``` + [foodir] + name=bar +-``` ++``` ++ + Content `RES` of this directory is exposed as `http://SERVER/bar/RES`. + + Listing can be enabled on server using [dlist](#dlist). +@@ -53,18 +60,21 @@ Example + ``` + gpac httpout:rdirs=outcoming + ``` ++ + This sets up a read-only server. + + Example + ``` + gpac httpout:wdir=incoming +-``` ++``` ++ + This sets up a write-only server. + + Example + ``` + gpac httpout:rdirs=outcoming:wdir=incoming:port=8080 +-``` ++``` ++ + This sets up a read-write server running on [port](#port) 8080. + + +@@ -76,13 +86,15 @@ This mode is mostly useful to setup live HTTP streaming of media sessions such a + Example + ``` + gpac -i MP3_SOURCE -o http://localhost/live.mp3 --hold +-``` ++``` ++ + In this example, the server waits for client requests on `/live.mp3` and will then push each input packet to all connected clients. + If the source is not real-time, you can inject a reframer filter performing realtime regulation. + Example + ``` + gpac -i MP3_SOURCE reframer:rt=on -o http://localhost/live.mp3 +-``` ++``` ++ + In this example, the server will push each input packet to all connected clients, or trash the packet if no connected clients. + + In this mode, ICECast meta-data can be inserted using [ice](#ice). The default inserted values are `ice-audio-info`, `icy-br`, `icy-pub` (set to 1) and `icy-name` if input `ServiceName` property is set. +@@ -90,7 +102,8 @@ The server will also look for any property called `ice-*` on the input PID and i + Example + ``` + gpac -i source.mp3:#ice-Genre=CoolRock -o http://IP/live.mp3 --ice +-``` ++``` ++ + This will inject the header `ice-Genre: CoolRock` in the response. + Once one complete input file is sent, it is no longer available for download unless [reopen](#reopen) is set and input PID is not over. + +@@ -102,26 +115,33 @@ This mode should not be used with multiple files muxers such as DASH or HLS. + In this mode, the filter will write input PIDs to files in the first read directory specified, acting as a file output sink. + The filter uses a read directory in this mode, which must be writable. + Upon client GET request, the server will check if the requested URL matches the name of a file currently being written by the server. ++ + - If so, the server will: +- - send the content using HTTP chunk transfer mode, starting with what is already written on disk +- - push remaining data to the client as soon as received while writing it to disk, until source file is done ++ ++ - send the content using HTTP chunk transfer mode, starting with what is already written on disk ++ - push remaining data to the client as soon as received while writing it to disk, until source file is done ++ + - If not so, the server will simply send the file from the disk as a regular HTTP session, without chunk transfer. ++ + + This mode is typically used for origin server in HAS sessions where clients may request files while they are being produced (low latency DASH). + Example + ``` + gpac -i SOURCE reframer:rt=on -o http://localhost:8080/live.mpd --rdirs=temp --dmode=dynamic --cdur=0.1 +-``` ++``` ++ + In this example, a real-time dynamic DASH session with chunks of 100ms is created, writing files to `temp`. A client connecting to the live edge will receive segments as they are produced using HTTP chunk transfer. + + The server can store incoming files to memory mode by setting the read directory to `gmem`. + In this mode, [max_cache_segs](#max_cache_segs) is always at least 1. + + If [max_cache_segs](#max_cache_segs) value `N` is not 0, each incoming PID will store at most: ++ + - `MIN(N, time-shift depth)` files if stored in memory + - `-N` files if stored locally and `N` is negative + - `MAX(N, time-shift depth)` files if stored locally and `N` is positive + - unlimited otherwise (files stored locally, `N` is positive and no time-shift info) ++ + + + # HTTP client sink +@@ -132,7 +152,8 @@ The filter uses no read or write directories in this mode. + Example + ``` + gpac -i SOURCE -o http://targethost:8080/live.mpd:gpac:hmode=push +-``` ++``` ++ + In this example, the filter will send PUT methods to the server running on [port](#port) 8080 at `targethost` location (IP address or name). + + +@@ -144,7 +165,8 @@ The filter uses no read or write directories in this mode, and uploaded data is + Example + ``` + gpac httpout:hmode=source vout aout +-``` ++``` ++ + In this example, the filter will try to play uploaded files through video and audio output. + + +@@ -158,16 +180,21 @@ The server currently only operates in either HTTPS or HTTP mode and cannot run b + # Multiple destinations on single server + + When running in server mode, multiple HTTP outputs with same URL/port may be used: ++ + - the first loaded HTTP output filter with same URL/port will be reused + - all httpout options of subsequent httpout filters, except [dst](#dst) will be ignored, other options will be inherited as usual ++ + + Example + ``` + gpac -i dash.mpd dashin:forward=file:FID=D1 dashin:forward=segb:FID=D2 -o http://localhost:80/live.mpd:SID=D1:rdirs=dash -o http://localhost:80/live_rw.mpd:SID=D2:sigfrag + ``` ++ + This will: ++ + - load the HTTP server and forward (through `D1`) the dash session to this server using `live.mpd` as manifest name + - reuse the HTTP server and regenerate the manifest (through `D2` and `sigfrag` option), using `live_rw.mpd` as manifest name ++ + + + # Options +@@ -187,9 +214,10 @@ This will: + __cache_control__ (str): specify the `Cache-Control` string to add (`none` disable cache control and ETag) + __hold__ (bool, default: _false_): hold packets until one client connects + __hmode__ (enum, default: _default_): filter operation mode, ignored if [wdir](#wdir) is set +-* default: run in server mode +-* push: run in client mode using PUT or POST +-* source: use server as source filter on incoming PUT/POST ++ ++- default: run in server mode ++- push: run in client mode using PUT or POST ++- source: use server as source filter on incoming PUT/POST + + __timeout__ (uint, default: _30_): timeout in seconds for persistent connections (0 disable timeout) + __ext__ (cstr): set extension for graph resolution, regardless of file extension +@@ -199,9 +227,10 @@ This will: + __dlist__ (bool, default: _false_): enable HTML listing for GET requests on directories + __sutc__ (bool, default: _false_): insert server UTC in response headers as `Server-UTC: VAL_IN_MS` + __cors__ (enum, default: _auto_): insert CORS header allowing all domains +-* off: disable CORS +-* on: enable CORS +-* auto: enable CORS when `Origin` is found in request ++ ++- off: disable CORS ++- on: enable CORS ++- auto: enable CORS when `Origin` is found in request + + __reqlog__ (str): provide short log of the requests indicated in this option (comma separated list, `*` for all) regardless of HTTP log settings. Value `REC` logs file writing start/end. If prefix `-` is set, do not log request end + __ice__ (bool, default: _false_): insert ICE meta-data in response headers in sink mode +diff --git a/docs/Filters/imgdec.md b/docs/Filters/imgdec.md +index ff117c49..ef9c91fe 100644 +--- a/docs/Filters/imgdec.md ++++ b/docs/Filters/imgdec.md +@@ -1,6 +1,6 @@ + + +-# PNG/JPG decoder ++# PNG/JPG decoder {:data-level="all"} + + Register name used to load filter: __imgdec__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/inspect.md b/docs/Filters/inspect.md +index 5bfc9691..5394ab87 100644 +--- a/docs/Filters/inspect.md ++++ b/docs/Filters/inspect.md +@@ -1,6 +1,6 @@ + + +-# Inspect packets ++# Inspect packets {:data-level="all"} + + Register name used to load filter: __inspect__ + This filter is not checked during graph resolution and needs explicit loading. +@@ -16,55 +16,60 @@ _Note: specifying [xml](#xml), [analyze](#analyze), [fmt](#fmt) or using `-for-t + + The packet inspector can be configured to dump specific properties of packets using [fmt](#fmt). + When the option is not present, all properties are dumped. Otherwise, only properties identified by `$TOKEN$` are printed. You may use '$', '@' or '%' for `TOKEN` separator. `TOKEN` can be: +-* pn: packet (frame in framed mode) number +-* dts: decoding time stamp in stream timescale, N/A if not available +-* ddts: difference between current and previous packets decoding time stamp in stream timescale, N/A if not available +-* cts: composition time stamp in stream timescale, N/A if not available +-* dcts: difference between current and previous packets composition time stamp in stream timescale, N/A if not available +-* ctso: difference between composition time stamp and decoding time stamp in stream timescale, N/A if not available +-* dur: duration in stream timescale +-* frame: framing status +- * interface: complete AU, interface object (no size info). Typically a GL texture +- * frame_full: complete AU +- * frame_start: beginning of frame +- * frame_end: end of frame +- * frame_cont: frame continuation (not beginning, not end) +-* sap or rap: SAP type of the frame +-* ilace: interlacing flag (0: progressive, 1: top field, 2: bottom field) +-* corr: corrupted packet flag +-* seek: seek flag +-* bo: byte offset in source, N/A if not available +-* roll: roll info +-* crypt: crypt flag +-* vers: carousel version number +-* size: size of packet +-* csize: total size of packets received so far +-* crc: 32 bit CRC of packet +-* lf or n: insert new line +-* t: insert tab +-* data: hex dump of packet (_big output!_) or as string if legal UTF-8 +-* lp: leading picture flag +-* depo: depends on other packet flag +-* depf: is depended on other packet flag +-* red: redundant coding flag +-* start: packet composition time as HH:MM:SS.ms +-* startc: packet composition time as HH:MM:SS,ms +-* end: packet end time as HH:MM:SS.ms +-* endc: packet end time as HH:MM:SS,ms +-* ck: clock type used for PCR discontinuities +-* pcr: MPEG-2 TS last PCR, n/a if not available +-* pcrd: difference between last PCR and decoding time, n/a if no PCR available +-* pcrc: difference between last PCR and composition time, n/a if no PCR available +-* P4CC: 4CC of packet property +-* PropName: Name of packet property +-* pid.P4CC: 4CC of PID property +-* pid.PropName: Name of PID property +-* fn: Filter name ++ ++- pn: packet (frame in framed mode) number ++- dts: decoding time stamp in stream timescale, N/A if not available ++- ddts: difference between current and previous packets decoding time stamp in stream timescale, N/A if not available ++- cts: composition time stamp in stream timescale, N/A if not available ++- dcts: difference between current and previous packets composition time stamp in stream timescale, N/A if not available ++- ctso: difference between composition time stamp and decoding time stamp in stream timescale, N/A if not available ++- dur: duration in stream timescale ++- frame: framing status ++ ++ - interface: complete AU, interface object (no size info). Typically a GL texture ++ - frame_full: complete AU ++ - frame_start: beginning of frame ++ - frame_end: end of frame ++ - frame_cont: frame continuation (not beginning, not end) ++ ++- sap or rap: SAP type of the frame ++- ilace: interlacing flag (0: progressive, 1: top field, 2: bottom field) ++- corr: corrupted packet flag ++- seek: seek flag ++- bo: byte offset in source, N/A if not available ++- roll: roll info ++- crypt: crypt flag ++- vers: carousel version number ++- size: size of packet ++- csize: total size of packets received so far ++- crc: 32 bit CRC of packet ++- lf or n: insert new line ++- t: insert tab ++- data: hex dump of packet (_big output!_) or as string if legal UTF-8 ++- lp: leading picture flag ++- depo: depends on other packet flag ++- depf: is depended on other packet flag ++- red: redundant coding flag ++- start: packet composition time as HH:MM:SS.ms ++- startc: packet composition time as HH:MM:SS,ms ++- end: packet end time as HH:MM:SS.ms ++- endc: packet end time as HH:MM:SS,ms ++- ck: clock type used for PCR discontinuities ++- pcr: MPEG-2 TS last PCR, n/a if not available ++- pcrd: difference between last PCR and decoding time, n/a if no PCR available ++- pcrc: difference between last PCR and composition time, n/a if no PCR available ++- P4CC: 4CC of packet property ++- PropName: Name of packet property ++- pid.P4CC: 4CC of PID property ++- pid.PropName: Name of PID property ++- fn: Filter name ++ + + Example + ``` + fmt="PID $pid.ID$ packet $pn$ DTS $dts$ CTS $cts$ $lf$" +-``` ++``` ++ + This dumps packet number, cts and dts as follows: `PID 1 packet 10 DTS 100 CTS 108 \n` + + An unrecognized keyword or missing property will resolve to an empty string. +@@ -81,23 +86,27 @@ Example + ``` + gpac -i SRC reframer:rt=on inspect:buffer=10000:rbuffer=1000:mbuffer=30000:speed=2 + ``` ++ + This will play the session at 2x speed, using 30s of maximum buffering, consuming packets after 10s of media are ready and rebuffering if less than 1s of media. + + + # Options + +-__log__ (str, default: _stdout_, Enum: _any|stderr|stdout|GLOG|null): set probe log filename +-* _any: target file path and name +-* stderr: dump to stderr +-* stdout: dump to stdout +-* GLOG: use GPAC logs `app@info` +-* null: silent mode ++__log__ (str, default: _stdout_, Enum: _any|stderr|stdout|GLOG|TL|null): set probe log filename ++ ++- _any: target file path and name ++- stderr: dump to stderr ++- stdout: dump to stdout ++- GLOG: use GPAC logs `app@info` ++- TL: use GPAC log tool `TL` at level `info` ++- null: silent mode + + __mode__ (enum, default: _pck_): dump mode +-* pck: dump full packet +-* blk: dump packets before reconstruction +-* frame: force reframer +-* raw: dump source packets without demultiplexing ++ ++- pck: dump full packet ++- blk: dump packets before reconstruction ++- frame: force reframer ++- raw: dump source packets without demultiplexing + + __interleave__ (bool, default: _true_): dump packets as they are received on each PID. If false, logs are reported for each PID at end of session + __deep__ (bool, default: _false_, updatable): dump packets along with PID state change, implied when [fmt](#fmt) is set +@@ -113,10 +122,11 @@ This will play the session at 2x speed, using 30s of maximum buffering, consumin + __start__ (dbl, default: _0.0_): set playback start offset. A negative value means percent of media duration with -1 equal to duration + __dur__ (frac, default: _0/0_): set inspect duration + __analyze__ (enum, default: _off_, updatable): analyze sample content (NALU, OBU), similar to `-bsdbg` option of reframer filters +-* off: no analyzing +-* on: simple analyzing +-* bs: log bitstream syntax (all elements read from bitstream) +-* full: log bitstream syntax and bit sizes signaled as `(N)` after field value, except 1-bit fields (omitted) ++ ++- off: no analyzing ++- on: simple analyzing ++- bs: log bitstream syntax (all elements read from bitstream) ++- full: log bitstream syntax and bit sizes signaled as `(N)` after field value, except 1-bit fields (omitted) + + __xml__ (bool, default: _false_, updatable): use xml formatting (implied if (-analyze]() is set) and disable [fmt](#fmt) + __crc__ (bool, default: _false_, updatable): dump crc of samples of subsamples (NALU or OBU) when analyzing +@@ -125,14 +135,16 @@ This will play the session at 2x speed, using 30s of maximum buffering, consumin + __buffer__ (uint, default: _0_): set playback buffer in ms + __mbuffer__ (uint, default: _0_): set max buffer occupancy in ms. If less than buffer, use buffer + __rbuffer__ (uint, default: _0_, updatable): rebuffer trigger in ms. If 0 or more than buffer, disable rebuffering ++__stats__ (bool, default: _false_): compute statistics for PIDs + __test__ (enum, default: _no_, updatable): skip predefined set of properties, used for test mode +-* no: no properties skipped +-* noprop: all properties/info changes on PID are skipped, only packets are dumped +-* network: URL/path dump, cache state, file size properties skipped (used for hashing network results) +-* netx: same as network but skip track duration and templates (used for hashing progressive load of fmp4) +-* encode: same as network plus skip decoder config (used for hashing encoding results) +-* encx: same as encode and skip bitrates, media data size and co +-* nocrc: disable packet CRC dump +-* nobr: skip bitrate ++ ++- no: no properties skipped ++- noprop: all properties/info changes on PID are skipped, only packets are dumped ++- network: URL/path dump, cache state, file size properties skipped (used for hashing network results) ++- netx: same as network but skip track duration and templates (used for hashing progressive load of fmp4) ++- encode: same as network plus skip decoder config (used for hashing encoding results) ++- encx: same as encode and skip bitrates, media data size and co ++- nocrc: disable packet CRC dump ++- nobr: skip bitrate + + +diff --git a/docs/Filters/j2kdec.md b/docs/Filters/j2kdec.md +index cfba259c..794d820e 100644 +--- a/docs/Filters/j2kdec.md ++++ b/docs/Filters/j2kdec.md +@@ -1,6 +1,6 @@ + + +-# OpenJPEG2000 decoder ++# OpenJPEG2000 decoder {:data-level="all"} + + Register name used to load filter: __j2kdec__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/jpgenc.md b/docs/Filters/jpgenc.md +index fd87c2d6..2143f58b 100644 +--- a/docs/Filters/jpgenc.md ++++ b/docs/Filters/jpgenc.md +@@ -1,6 +1,6 @@ + + +-# JPG encoder ++# JPG encoder {:data-level="all"} + + Register name used to load filter: __jpgenc__ + This filter may be automatically loaded during graph resolution. +@@ -11,9 +11,10 @@ This filter encodes a single uncompressed video PID to JPEG using libjpeg. + # Options + + __dctmode__ (enum, default: _fast_): type of DCT used +-* slow: precise but slow integer DCT +-* fast: less precise but faster integer DCT +-* float: float DCT ++ ++- slow: precise but slow integer DCT ++- fast: less precise but faster integer DCT ++- float: float DCT + + __quality__ (uint, default: _100_, minmax: 0-100, updatable): compression quality + +diff --git a/docs/Filters/jsf.md b/docs/Filters/jsf.md +index 3802832f..7ce75d63 100644 +--- a/docs/Filters/jsf.md ++++ b/docs/Filters/jsf.md +@@ -1,6 +1,6 @@ + + +-# JavaScript filter ++# JavaScript filter {:data-level="all"} + + Register name used to load filter: __jsf__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/lsrdec.md b/docs/Filters/lsrdec.md +index b1003020..fd57a89f 100644 +--- a/docs/Filters/lsrdec.md ++++ b/docs/Filters/lsrdec.md +@@ -1,6 +1,6 @@ + + +-# MPEG-4 LASeR decoder ++# MPEG-4 LASeR decoder {:data-level="all"} + + Register name used to load filter: __lsrdec__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/m2psdmx.md b/docs/Filters/m2psdmx.md +index 5df6830e..9b64b735 100644 +--- a/docs/Filters/m2psdmx.md ++++ b/docs/Filters/m2psdmx.md +@@ -1,6 +1,6 @@ + + +-# MPEG PS demultiplexer ++# MPEG PS demultiplexer {:data-level="all"} + + Register name used to load filter: __m2psdmx__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/m2tsdmx.md b/docs/Filters/m2tsdmx.md +index 21cffbf5..f9d91a6e 100644 +--- a/docs/Filters/m2tsdmx.md ++++ b/docs/Filters/m2tsdmx.md +@@ -1,6 +1,6 @@ + + +-# MPEG-2 TS demultiplexer ++# MPEG-2 TS demultiplexer {:data-level="all"} + + Register name used to load filter: __m2tsdmx__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/m2tsmx.md b/docs/Filters/m2tsmx.md +index d21080fb..f06de81b 100644 +--- a/docs/Filters/m2tsmx.md ++++ b/docs/Filters/m2tsmx.md +@@ -1,6 +1,6 @@ + + +-# MPEG-2 TS multiplexer ++# MPEG-2 TS multiplexer {:data-level="all"} + + Register name used to load filter: __m2tsmx__ + This filter may be automatically loaded during graph resolution. +@@ -12,11 +12,13 @@ This filter multiplexes one or more input PIDs into a MPEG-2 Transport Stream mu + The MPEG-2 TS multiplexer assigns M2TS PID for media streams using the PID of the PMT plus the stream index. + For example, the default config creates the first program with a PMT PID 100, the first stream will have a PID of 101. + Streams are grouped in programs based on input PID property ServiceID if present. If absent, stream will go in the program with service ID as indicated by [sid](#sid) option. ++ + - [name](#name) option is overridden by input PID property `ServiceName`. + - [provider](#provider) option is overridden by input PID property `ServiceProvider`. + - [pcr_offset](#pcr_offset) option is overridden by input PID property `"tsmux:pcr_offset"` + - [first_pts](#first_pts) option is overridden by input PID property `"tsmux:force_pts"` + - [temi](#temi) option is overridden by input PID property `"tsmux:temi"` ++ + + # Time and External Media Information (TEMI) + +@@ -24,47 +26,58 @@ The [temi](#temi) option allows specifying a list of URLs or timeline IDs to ins + One or more TEMI timeline can be specified per PID. + The syntax is a comma-separated list of one or more TEMI description. + Each TEMI description is formatted as ID_OR_URL or #OPT1[#OPT2]#ID_OR_URL. Options are: +-* S`N`: indicate the target service with ID `N` +-* T`N`: set timescale to use (default: PID timescale) +-* D`N`: set delay in ms between two TEMI url descriptors (default 1000) +-* O`N`: set offset (max 64 bits) to add to TEMI timecodes (default 0). If timescale is not specified, offset value is in ms, otherwise in timescale units. +-* I`N`: set initial value (max 64 bits) of TEMI timecodes. If not set, initial value will match first packet CTS. If timescale is not specified, value is in PID timescale units, otherwise in specified timescale units. +-* P`N`: indicate target PID in program. Possible values are +- * `V`: only insert for video streams. +- * `A`: only insert for audio streams. +- * `T`: only insert for text streams. +- * N: only insert for stream with index `N` (0-based) in the program. +-* L`C`: set 64bit timecode signaling. Possible values for `C` are: +- * `A`: automatic switch between 32 and 64 bit depending on timecode value (default if not specified). +- * `Y`: use 64 bit signaling only. +- * `N`: use 32 bit signaling only and wrap around timecode value. +-* N: insert NTP timestamp in TEMI timeline descriptor +-* ID_OR_URL: If number, indicate the TEMI ID to use for external timeline. Otherwise, give the URL to insert ++ ++- S`N`: indicate the target service with ID `N` ++- T`N`: set timescale to use (default: PID timescale) ++- D`N`: set delay in ms between two TEMI url descriptors (default 1000) ++- O`N`: set offset (max 64 bits) to add to TEMI timecodes (default 0). If timescale is not specified, offset value is in ms, otherwise in timescale units. ++- I`N`: set initial value (max 64 bits) of TEMI timecodes. If not set, initial value will match first packet CTS. If timescale is not specified, value is in PID timescale units, otherwise in specified timescale units. ++- P`N`: indicate target PID in program. Possible values are ++ ++ - `V`: only insert for video streams. ++ - `A`: only insert for audio streams. ++ - `T`: only insert for text streams. ++ - N: only insert for stream with index `N` (0-based) in the program. ++ ++- L`C`: set 64bit timecode signaling. Possible values for `C` are: ++ ++ - `A`: automatic switch between 32 and 64 bit depending on timecode value (default if not specified). ++ - `Y`: use 64 bit signaling only. ++ - `N`: use 32 bit signaling only and wrap around timecode value. ++ ++- N: insert NTP timestamp in TEMI timeline descriptor ++- ID_OR_URL: If number, indicate the TEMI ID to use for external timeline. Otherwise, give the URL to insert ++ + + Example + ``` + temi="url" +-``` ++``` ++ + Inserts a TEMI URL+timecode in the each stream of each program. + Example + ``` + temi="#P0#url,#P1#4" + ``` ++ + Inserts a TEMI URL+timecode in the first stream of all programs and an external TEMI with ID 4 in the second stream of all programs. + Example + ``` + temi="#P0#2,#P0#url,#P1#4" +-``` ++``` ++ + Inserts a TEMI with ID 2 and a TEMI URL+timecode in the first stream of all programs, and an external TEMI with ID 4 in the second stream of all programs. + Example + ``` + temi="#S20#4,#S10#URL" +-``` ++``` ++ + Inserts an external TEMI with ID 4 in the each stream of program with ServiceID 20 and a TEMI URL in each stream of program with ServiceID 10. + Example + ``` + temi="#N#D500#PV#T30000#4" +-``` ++``` ++ + Inserts an external TEMI with ID 4 and timescale 30000, NTP injection and carousel of 500 ms in the video stream of all programs. + + __Warning: multipliers (k,m,g) are not supported in TEMI options.__ +@@ -72,9 +85,11 @@ __Warning: multipliers (k,m,g) are not supported in TEMI options.__ + # Adaptive Streaming + + In DASH and HLS mode: ++ + - the PCR is always initialized at 0, and [flush_rap](#flush_rap) is automatically set. + - unless `nb_pack` is specified, 200 TS packets will be used as pack output in DASH mode. + - `pes_pack=none` is forced since some demultiplexers have issues with non-aligned ADTS PES. ++ + + The filter watches the property `FileNumber` on incoming packets to create new files, or new segments in DASH mode. + The filter will look for property `M2TSRA` set on the input stream. +@@ -95,9 +110,10 @@ In LATM mux mode, the decoder configuration is inserted at the given [repeat_rat + __first_pts__ (luint, default: _0_): force PTS value of first packet, in 90kHz + __pcr_offset__ (luint, default: _-1_): offset all timestamps from PCR by V, in 90kHz (default value is computed based on input media) + __mpeg4__ (enum, default: _none_): force usage of MPEG-4 signaling (IOD and SL Config) +-* none: disables 4on2 +-* full: sends AUs as SL packets over section for OD, section/pes for scene (cf bifs_pes) +-* scene: sends only scene streams as 4on2 but uses regular PES without SL for audio and video ++ ++- none: disables 4on2 ++- full: sends AUs as SL packets over section for OD, section/pes for scene (cf bifs_pes) ++- scene: sends only scene streams as 4on2 but uses regular PES without SL for audio and video + + __pmt_version__ (uint, default: _200_): set version number of the PMT + __disc__ (bool, default: _false_): set the discontinuity marker for the first packet of each stream +@@ -106,15 +122,17 @@ In LATM mux mode, the decoder configuration is inserted at the given [repeat_rat + __max_pcr__ (uint, default: _100_): set max interval in ms between 2 PCR + __nb_pack__ (uint, default: _4_): pack N TS packets in output packets + __pes_pack__ (enum, default: _audio_): set AU to PES packing mode +-* audio: will pack only multiple audio AUs in a PES +-* none: make exactly one AU per PES +-* all: will pack multiple AUs per PES for all streams ++ ++- audio: will pack only multiple audio AUs in a PES ++- none: make exactly one AU per PES ++- all: will pack multiple AUs per PES for all streams + + __realtime__ (bool, default: _false_): use real-time output + __bifs_pes__ (enum, default: _off_): select BIFS streams packetization (PES vs sections) +-* on: uses BIFS PES +-* off: uses BIFS sections +-* copy: uses BIFS PES but removes timestamps in BIFS SL and only carries PES timestamps ++ ++- on: uses BIFS PES ++- off: uses BIFS sections ++- copy: uses BIFS PES but removes timestamps in BIFS SL and only carries PES timestamps + + __flush_rap__ (bool, default: _false_): force flushing mux program when RAP is found on video, and injects PAT and PMT before the next video PES begin + __pcr_only__ (bool, default: _false_): enable PCR-only TS packets +diff --git a/docs/Filters/maddec.md b/docs/Filters/maddec.md +index 65da8b7b..03a656c2 100644 +--- a/docs/Filters/maddec.md ++++ b/docs/Filters/maddec.md +@@ -1,6 +1,6 @@ + + +-# MAD decoder ++# MAD decoder {:data-level="all"} + + Register name used to load filter: __maddec__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/mcdec.md b/docs/Filters/mcdec.md +index bfd409e5..8ed51dd0 100644 +--- a/docs/Filters/mcdec.md ++++ b/docs/Filters/mcdec.md +@@ -1,6 +1,6 @@ + + +-# MediaCodec decoder ++# MediaCodec decoder {:data-level="all"} + + Register name used to load filter: __mcdec__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/mp4dmx.md b/docs/Filters/mp4dmx.md +index 94e5da06..667c71e9 100644 +--- a/docs/Filters/mp4dmx.md ++++ b/docs/Filters/mp4dmx.md +@@ -1,6 +1,6 @@ + + +-# ISOBMFF/QT demultiplexer ++# ISOBMFF/QT demultiplexer {:data-level="all"} + + Register name used to load filter: __mp4dmx__ + This filter may be automatically loaded during graph resolution. +@@ -11,24 +11,32 @@ Input ISOBMFF/QT can be regular or fragmented, and available as files or as raw + # Track Selection + + The filter can use fragment identifiers of source to select a single track for playback. The allowed fragments are: +- * #audio: only use the first audio track +- * #video: only use the first video track +- * #auxv: only use the first auxiliary video track +- * #pict: only use the first picture track +- * #text: only use the first text track +- * #trackID=VAL: only use the track with given ID +- * #itemID=VAL: only use the item with given ID +- * #ID=VAL: only use the track/item with given ID +- * #VAL: only use the track/item with given ID ++ ++ - #audio: only use the first audio track ++ - #video: only use the first video track ++ - #auxv: only use the first auxiliary video track ++ - #pict: only use the first picture track ++ - #text: only use the first text track ++ - #trackID=VAL: only use the track with given ID ++ - #itemID=VAL: only use the item with given ID ++ - #ID=VAL: only use the track/item with given ID ++ - #VAL: only use the track/item with given ID ++ + + # Scalable Tracks + + When scalable tracks are present in a file, the reader can operate in 3 modes using [smode](#smode) option: +-* smode=single: resolves all extractors to extract a single bitstream from a scalable set. The highest level is used ++ ++- smode=single: resolves all extractors to extract a single bitstream from a scalable set. The highest level is used ++ + In this mode, there is no enhancement decoder config, only a base one resulting from the merge of the layers configurations +-* smode=split: all extractors are removed and every track of the scalable set is declared. In this mode, each enhancement track has no base decoder config ++ ++- smode=split: all extractors are removed and every track of the scalable set is declared. In this mode, each enhancement track has no base decoder config ++ + and an enhancement decoder config. +-* smode=splitx: extractors are kept in the bitstream, and every track of the scalable set is declared. In this mode, each enhancement track has a base decoder config ++ ++- smode=splitx: extractors are kept in the bitstream, and every track of the scalable set is declared. In this mode, each enhancement track has a base decoder config ++ + (copied from base) and an enhancement decoder config. This is mostly used for DASHing content. + + __Warning: smode=splitx will result in extractor NAL units still present in the output bitstream, which shall only be true if the output is ISOBMFF based__ +@@ -39,26 +47,29 @@ __Warning: smode=splitx will result in extractor NAL units still present in the + __src__ (cstr): local file name of source content (only used when explicitly loading the filter) + __allt__ (bool, default: _false_): load all tracks even if unknown media type + __edits__ (enum, default: _auto_): do not use edit lists +-* auto: track delay and no edit list when possible +-* no: ignore edit list +-* strict: use edit list even if only signaling a delay ++ ++- auto: track delay and no edit list when possible ++- no: ignore edit list ++- strict: use edit list even if only signaling a delay + + __itt__ (bool, default: _false_): convert all items of root meta into a single PID + __itemid__ (bool, default: _true_): keep item IDs in PID properties + __smode__ (enum, default: _split_): load mode for scalable/tile tracks +-* split: each track is declared, extractors are removed +-* splitx: each track is declared, extractors are kept +-* single: a single track is declared (highest level for scalable, tile base for tiling) ++ ++- split: each track is declared, extractors are removed ++- splitx: each track is declared, extractors are kept ++- single: a single track is declared (highest level for scalable, tile base for tiling) + + __alltk__ (bool, default: _false_): declare disabled tracks + __frame_size__ (uint, default: _1024_): frame size for raw audio samples (dispatches frame_size samples per packet) + __expart__ (bool, default: _false_): expose cover art as a dedicated video PID + __sigfrag__ (bool, default: _false_): signal fragment and segment boundaries of source on output packets, fails if source is not fragmented + __tkid__ (str): declare only track based on given param +-* integer value: declares track with the given ID +-* audio: declares first audio track +-* video: declares first video track +-* 4CC: declares first track with matching 4CC for handler type ++ ++- integer value: declares track with the given ID ++- audio: declares first audio track ++- video: declares first video track ++- 4CC: declares first track with matching 4CC for handler type + + __stsd__ (uint, default: _0_): only extract sample mapped to the given sample description index (0 means extract all) + __nocrypt__ (bool): signal encrypted tracks as non encrypted (mostly used for export) +@@ -67,19 +78,24 @@ __Warning: smode=splitx will result in extractor NAL units still present in the + __mstore_samples__ (uint, default: _50_): minimum number of samples to be present before purging sample tables when reading from memory stream (pipe etc...), 0 means purge as soon as possible + __strtxt__ (bool, default: _false_): load text tracks (apple/tx3g) as MPEG-4 streaming text tracks + __xps_check__ (enum, default: _auto_): parameter sets extraction mode from AVC/HEVC/VVC samples +-* keep: do not inspect sample (assumes input file is compliant when generating DASH/HLS/CMAF) +-* rem: removes all inband xPS and notify configuration changes accordingly +-* auto: resolves to `keep` for `smode=splitx` (dasher mode), `rem` otherwise ++ ++- keep: do not inspect sample (assumes input file is compliant when generating DASH/HLS/CMAF) ++- rem: removes all inband xPS and notify configuration changes accordingly ++- auto: resolves to `keep` for `smode=splitx` (dasher mode), `rem` otherwise + + __nodata__ (enum, default: _no_): control sample data loading +-* no: regular load +-* yes: skip data loading +-* fake: allocate sample but no data copy ++ ++- no: regular load ++- yes: skip data loading ++- fake: allocate sample but no data copy + + __lightp__ (bool, default: _false_): load minimal set of properties + __initseg__ (str): local init segment name when input is a single ISOBMFF segment + __ctso__ (sint): value to add to CTS offset for tracks using negative ctts ++ + - set to `-1` to use the `cslg` box info or the minimum cts offset present in the track + - set to `-2` to use the minimum cts offset present in the track (`cslg` ignored) + ++__norw__ (bool, default: _false_): skip reformating of samples - should only be used when rewriting fragments ++__keepc__ (bool, default: _false_): keep corrupted samples - should only be used in multicast modes + +diff --git a/docs/Filters/mp4mx.md b/docs/Filters/mp4mx.md +index 3882a40f..fa25ec0e 100644 +--- a/docs/Filters/mp4mx.md ++++ b/docs/Filters/mp4mx.md +@@ -1,6 +1,6 @@ + + +-# ISOBMFF/QT multiplexer ++# ISOBMFF/QT multiplexer {:data-level="all"} + + Register name used to load filter: __mp4mx__ + This filter may be automatically loaded during graph resolution. +@@ -29,9 +29,11 @@ gpac -i source.jpg:#ItemID=1 -o file.mp4 + The [store](#store) option allows controlling if the file is fragmented or not, and when not fragmented, how interleaving is done. For cases where disk requirements are tight and fragmentation cannot be used, it is recommended to use either `flat` or `fstart` modes. + + The [vodcache](#vodcache) option allows controlling how DASH onDemand segments are generated: ++ + - If set to `on`, file data is stored to a temporary file on disk and flushed upon completion, no padding is present. + - If set to `insert`, SIDX/SSIX will be injected upon completion of the file by shifting bytes in file. In this case, no padding is required but this might not be compatible with all output sinks and will take longer to write the file. + - If set to `replace`, SIDX/SSIX size will be estimated based on duration and DASH segment length, and padding will be used in the file _before_ the final SIDX. If input PIDs have the properties `DSegs` set, this will used be as the number of segments. ++ + The `on` and `insert` modes will produce exactly the same file, while the mode `replace` may inject a `free` box before the sidx. + + +@@ -43,7 +45,8 @@ Per PID box patch can be specified through the PID property `boxpatch`. + Example + ``` + gpac -i source:#boxpatch=myfile.xml -o mux.mp4 +-``` ++``` ++ + Per Item box patch can be specified through the PID property `boxpatch`. + Example + ``` +@@ -60,19 +63,23 @@ When tagging is enabled, the filter will watch the property `CoverArt` and all c + The built-in tag names are indicated by `MP4Box -h tags`. + QT tags can be specified using `qtt_NAME` property names, and will be added using formatting specified in `MP4Box -h tags`. + Other tag class may be specified using `tag_NAME` property names, and will be added if [tags](#tags) is set to `all` using: ++ + - `NAME` as a box 4CC if `NAME` is four characters long + - `NAME` as a box 4CC if `NAME` is 3 characters long, and will be prefixed by 0xA9 + - the CRC32 of the `NAME` as a box 4CC if `NAME` is not four characters long ++ + + + # User data + + The filter will look for the following PID properties to create user data entries: +-* `udtab`: set the track user-data box to the property value which _must_ be a serialized box array blob +-* `mudtab`: set the movie user-data box to the property value which _must_ be a serialized box array blob +-* `udta_U4CC`: set track user-data box entry of type `U4CC` to property value +-* `mudta_U4CC`: set movie user-data box entry of type `U4CC` to property value +-* `tkgp_T4CC`: set/remove membership to track group with type `T4CC` and ID given by property value. A negative value N removes from track group with ID -N ++ ++- `udtab`: set the track user-data box to the property value which _must_ be a serialized box array blob ++- `mudtab`: set the movie user-data box to the property value which _must_ be a serialized box array blob ++- `udta_U4CC`: set track user-data box entry of type `U4CC` to property value ++- `mudta_U4CC`: set movie user-data box entry of type `U4CC` to property value ++- `tkgp_T4CC`: set/remove membership to track group with type `T4CC` and ID given by property value. A negative value N removes from track group with ID -N ++ + + Example + ``` +@@ -84,14 +91,18 @@ gpac -i src.mp4:#mudtab=data@box.bin -o tag.mp4 + # Custom sample group descriptions and sample auxiliary info + + The filter watches the following custom data properties on incoming packets: +-* `grp_A4CC`: maps packet to sample group description of type `A4CC` and entry set to property payload +-* `grp_A4CC_param`: same as above and sets sample to group `grouping_type_parameter` to `param` +-* `sai_A4CC`: adds property payload as sample auxiliary information of type `A4CC` +-* `sai_A4CC_param`: same as above and sets `aux_info_type_parameter`to `param` ++ ++- `grp_A4CC`: maps packet to sample group description of type `A4CC` and entry set to property payload ++- `grp_A4CC_param`: same as above and sets sample to group `grouping_type_parameter` to `param` ++- `sai_A4CC`: adds property payload as sample auxiliary information of type `A4CC` ++- `sai_A4CC_param`: same as above and sets `aux_info_type_parameter`to `param` ++ + + The property `grp_EMSG` consists in one or more `EventMessageBox` as defined in MPEG-DASH. ++ + - in fragmented mode, presence of this property in a packet will start a new fragment, with the boxes written before the `moof` + - in regular mode, an internal sample group of type `EMSG` is currently used for `emsg` box storage ++ + + + # Notes +@@ -103,25 +114,30 @@ Example + ``` + -i unkn.mkv:#ISOMSubtype=VIUK:#DSIWrap=cfgv -o t.mp4 + ``` ++ + This will wrap the unknown stream using `VIUK` code point in `stsd` and wrap any decoder configuration data in a `cfgv` box. + + If [pad_sparse](#pad_sparse) is set, the filter watches the property `Sparse` on incoming PID to decide whether empty packets should be injected to keep packet duration info. + Such packets are only injected when a whole in the timeline is detected. ++ + - if `Sparse` is absent, empty packet is inserted for unknown text and metadata streams + - if `Sparse` is true, empty packet is inserted for all stream types + - if `Sparse` is false, empty packet is never injected ++ + + The default media type used for a PID can be overriden using property `StreamSubtype`. + Example + ``` + -i src.srt:#StreamSubtype=sbtl [-i ...] -o test.mp4 + ``` ++ + This will force the text stream to use `sbtl` handler type instead of default `text` one. + Subtitle streams may be used as chapters by setting the property `IsChap` on the desired PID. + Example + ``` + -i src.srt:#IsChap [-i ...] -o test.mp4 + ``` ++ + This will force the text stream to be used as a QT chapter track. + + +@@ -130,44 +146,49 @@ This will force the text stream to be used as a QT chapter track. + __m4sys__ (bool, default: _false_): force MPEG-4 Systems signaling of tracks + __dref__ (bool, default: _false_): only reference data from source file - not compatible with all media sources + __ctmode__ (enum, default: _auto_): set composition offset mode for video tracks +-* auto: if fragmenting an ISOBMFF source, use source settings otherwise resolve to `edit` +-* edit: uses edit lists to shift first frame to presentation time 0 +-* noedit: ignore edit lists and does not shift timeline +-* negctts: uses ctts v1 with possibly negative offsets and no edit lists ++ ++- auto: if fragmenting an ISOBMFF source, use source settings otherwise resolve to `edit` ++- edit: uses edit lists to shift first frame to presentation time 0 ++- noedit: ignore edit lists and does not shift timeline ++- negctts: uses ctts v1 with possibly negative offsets and no edit lists + + __dur__ (frac, default: _0_): only import the specified duration. If negative, specify the number of coded frames to import + __pack3gp__ (uint, default: _1_): pack a given number of 3GPP audio frames in one sample + __importer__ (bool, default: _false_): compatibility with old importer, displays import progress + __pack_nal__ (bool, default: _false_): repack NALU size length to minimum possible size for NALU-based video (AVC/HEVC/...) + __xps_inband__ (enum, default: _no_): use inband (in sample data) parameter set for NALU-based video (AVC/HEVC/...) +-* no: parameter sets are not inband, several sample descriptions might be created +-* pps: picture parameter sets are inband, all other parameter sets are in sample description +-* all: parameter sets are inband, no parameter sets in sample description +-* both: parameter sets are inband, signaled as inband, and also first set is kept in sample description +-* mix: creates non-standard files using single sample entry with first PSs found, and moves other PS inband +-* auto: keep source config, or defaults to no if source is not ISOBMFF ++ ++- no: parameter sets are not inband, several sample descriptions might be created ++- pps: picture parameter sets are inband, all other parameter sets are in sample description ++- all: parameter sets are inband, no parameter sets in sample description ++- both: parameter sets are inband, signaled as inband, and also first set is kept in sample description ++- mix: creates non-standard files using single sample entry with first PSs found, and moves other PS inband ++- auto: keep source config, or defaults to no if source is not ISOBMFF + + __store__ (enum, default: _inter_): file storage mode +-* inter: perform precise interleave of the file using [cdur](#cdur) (requires temporary storage of all media) +-* flat: write samples as they arrive and `moov` at end (fastest mode) +-* fstart: write samples as they arrive and `moov` before `mdat` +-* tight: uses per-sample interleaving of all tracks (requires temporary storage of all media) +-* frag: fragments the file using cdur duration +-* sfrag: fragments the file using cdur duration but adjusting to start with SAP1/3 ++ ++- inter: perform precise interleave of the file using [cdur](#cdur) (requires temporary storage of all media) ++- flat: write samples as they arrive and `moov` at end (fastest mode) ++- fstart: write samples as they arrive and `moov` before `mdat` ++- tight: uses per-sample interleaving of all tracks (requires temporary storage of all media) ++- frag: fragments the file using cdur duration ++- sfrag: fragments the file using cdur duration but adjusting to start with SAP1/3 + + __cdur__ (frac, default: _-1/1_): chunk duration for flat and interleaving modes or fragment duration for fragmentation modes +-* 0: no specific interleaving but moov first +-* negative: defaults to 1.0 unless overridden by storage profile ++ ++- 0: no specific interleaving but moov first ++- negative: defaults to 1.0 unless overridden by storage profile + + __moovts__ (sint, default: _600_): timescale to use for movie. A negative value picks the media timescale of the first track added + __moof_first__ (bool, default: _true_): generate fragments starting with moof then mdat + __abs_offset__ (bool, default: _false_): use absolute file offset in fragments rather than offsets from moof + __fsap__ (bool, default: _true_): split truns in video fragments at SAPs to reduce file size + __subs_sidx__ (sint, default: _-1_): number of subsegments per sidx +-* 0: single sidx +-* >0: hierarchical or daisy-chained sidx +-* <0: disables sidx +-* -2: removes sidx if present in source PID ++ ++- 0: single sidx ++- >0: hierarchical or daisy-chained sidx ++- <0: disables sidx ++- -2: removes sidx if present in source PID + + __m4cc__ (str): 4 character code of empty box to append at the end of a segment (DASH mode) or of a fragment (non-DASH mode) + __chain_sidx__ (bool, default: _false_): use daisy-chaining of SIDX +@@ -178,34 +199,40 @@ This will force the text stream to be used as a QT chapter track. + __nofragdef__ (bool, default: _false_): disable default flags in fragments + __straf__ (bool, default: _false_): use a single traf per moof (smooth streaming and co) + __strun__ (bool, default: _false_): use a single trun per traf (smooth streaming and co) ++__prft__ (bool, default: _true_): set `prft` box at segment start, disabled if not fragmented mode + __psshs__ (enum, default: _moov_): set `pssh` boxes store mode +-* moof: in first moof of each segments +-* moov: in movie box +-* both: in movie box and in first moof of each segment +-* none: pssh is discarded ++ ++- moof: in first moof of each segments ++- moov: in movie box ++- both: in movie box and in first moof of each segment ++- none: pssh is discarded + + __sgpd_traf__ (bool, default: _false_): store sample group descriptions in traf (duplicated for each traf). If not used, sample group descriptions are stored in the movie box + __vodcache__ (enum, default: _replace_): enable temp storage for VoD dash modes +-* on: use temp storage of complete file for sidx and ssix injection +-* insert: insert sidx and ssix by shifting bytes in output file +-* replace: precompute pace requirements for sidx and ssix and rewrite file range at end ++ ++- on: use temp storage of complete file for sidx and ssix injection ++- insert: insert sidx and ssix by shifting bytes in output file ++- replace: precompute pace requirements for sidx and ssix and rewrite file range at end + + __noinit__ (bool, default: _false_): do not produce initial `moov, used for DASH bitstream switching mode` + __tktpl__ (enum, default: _yes_): use track box from input if any as a template to create new track +-* no: disables template +-* yes: clones the track (except edits and decoder config) +-* udta: only loads udta ++ ++- no: disables template ++- yes: clones the track (except edits and decoder config) ++- udta: only loads udta + + __mudta__ (enum, default: _yes_): use `udta` and other `moov` extension boxes from input if any +-* no: disables import +-* yes: clones all extension boxes +-* udta: only loads udta ++ ++- no: disables import ++- yes: clones all extension boxes ++- udta: only loads udta + + __mvex__ (bool, default: _false_): set `mvex` boxes after `trak` boxes + __sdtp_traf__ (enum, default: _no_): use `sdtp` box in `traf` box rather than using flags in trun sample entries +-* no: do not use `sdtp` +-* sdtp: use `sdtp` box to indicate sample dependencies and do not write info in `trun` sample flags +-* both: use `sdtp` box to indicate sample dependencies and also write info in `trun` sample flags ++ ++- no: do not use `sdtp` ++- sdtp: use `sdtp` box to indicate sample dependencies and do not write info in `trun` sample flags ++- both: use `sdtp` box to indicate sample dependencies and also write info in `trun` sample flags + + __trackid__ (uint, default: _0_): track ID of created track for single track. Default 0 uses next available trackID + __fragdur__ (bool, default: _false_): fragment based on fragment duration rather than CTS. Mostly used for `MP4Box -frag` option +@@ -213,11 +240,12 @@ This will force the text stream to be used as a QT chapter track. + __styp__ (str): set segment `styp` major brand (and optionally version) to the given 4CC[.version] + __mediats__ (sint, default: _0_): set media timescale. A value of 0 means inherit from PID, a value of -1 means derive from samplerate or frame rate + __ase__ (enum, default: _v0_): set audio sample entry mode for more than stereo layouts +-* v0: use v0 signaling but channel count from stream, recommended for backward compatibility +-* v0s: use v0 signaling and force channel count to 2 (stereo) if more than 2 channels +-* v1: use v1 signaling, ISOBMFF style (will mux raw PCM as ISOBMFF style) +-* v1qt: use v1 signaling, QTFF style +-* v2qt: use v2 signaling, QTFF style (lpcm entry type) ++ ++- v0: use v0 signaling but channel count from stream, recommended for backward compatibility ++- v0s: use v0 signaling and force channel count to 2 (stereo) if more than 2 channels ++- v1: use v1 signaling, ISOBMFF style (will mux raw PCM as ISOBMFF style) ++- v1qt: use v1 signaling, QTFF style ++- v2qt: use v2 signaling, QTFF style (lpcm entry type) + + __ssix__ (bool, default: _false_): create `ssix` box when `sidx` box is present, level 1 mapping I-frames byte ranges, level 0xFF mapping the rest + __ccst__ (bool, default: _false_): insert coding constraint box for video tracks +@@ -227,12 +255,13 @@ This will force the text stream to be used as a QT chapter track. + __saio32__ (bool, default: _false_): use 32 bit offset for side data location instead of 64 bit offset + __tfdt64__ (bool, default: _false_): use 64 bit tfdt and sidx even for 32 bits timestamps + __compress__ (enum, default: _no_): set top-level box compression mode +-* no: disable box compression +-* moov: compress only moov box (uses cmov for QT) +-* moof: compress only moof boxes +-* sidx: compress moof and sidx boxes +-* ssix: compress moof, sidx and ssix boxes +-* all: compress moov, moof, sidx and ssix boxes ++ ++- no: disable box compression ++- moov: compress only moov box (uses cmov for QT) ++- moof: compress only moof boxes ++- sidx: compress moof and sidx boxes ++- ssix: compress moof, sidx and ssix boxes ++- all: compress moov, moof, sidx and ssix boxes + + __fcomp__ (bool, default: _false_): force using compress box even when compressed size is larger than uncompressed + __otyp__ (bool, default: _false_): inject original file type when using compressed boxes +@@ -245,34 +274,38 @@ This will force the text stream to be used as a QT chapter track. + __forcesync__ (bool, default: _false_): force all SAP types to be considered sync samples (might produce non-compliant files) + __refrag__ (bool, default: _false_): use track fragment defaults from initial file if any rather than computing them from PID properties (used when processing standalone segments/fragments) + __itags__ (enum, default: _strict_): tag injection mode +-* none: do not inject tags +-* strict: only inject recognized itunes tags +-* all: inject all possible tags ++ ++- none: do not inject tags ++- strict: only inject recognized itunes tags ++- all: inject all possible tags + + __keep_utc__ (bool, default: _false_): force all new files and tracks to keep the source UTC creation and modification times + __pps_inband__ (bool, default: _no_): when [xps_inband](#xps_inband) is set, inject PPS in each non SAP 1/2/3 sample + __moovpad__ (uint, default: _0_): insert `free` box of given size after `moov` for future in-place editing + __cmaf__ (enum, default: _no_): use CMAF guidelines (turns on `mvex`, `truns_first`, `strun`, `straf`, `tfdt_traf`, `chain_sidx` and restricts `subs_sidx` to -1 or 0) +-* no: CMAF not enforced +-* cmfc: use CMAF `cmfc` guidelines +-* cmf2: use CMAF `cmf2` guidelines (turns on `nofragdef`) ++ ++- no: CMAF not enforced ++- cmfc: use CMAF `cmfc` guidelines ++- cmf2: use CMAF `cmf2` guidelines (turns on `nofragdef`) + + __pad_sparse__ (bool, default: _true_): inject sample with no data (size 0) to keep durations in unknown sparse text and metadata tracks + __force_dv__ (bool, default: _false_): force DV sample entry types even when AVC/HEVC compatibility is signaled + __dvsingle__ (bool, default: _false_): ignore DolbyVision profile 8 in xps inband mode if profile 5 is already set + __tsalign__ (bool, default: _true_): enable timeline realignment to 0 for first sample - if false, this will keep original timing with empty edit (possibly long) at begin) + __chapm__ (enum, default: _both_): chapter storage mode +-* off: disable chapters +-* tk: use chapter track (QT-style) +-* udta: use user-data box chapters +-* both: use both chapter tracks and udta ++ ++- off: disable chapters ++- tk: use chapter track (QT-style) ++- udta: use user-data box chapters ++- both: use both chapter tracks and udta + + __patch_dts__ (bool, default: _false_): patch previous samples duration when dts do not increase monotonically + __uncv__ (enum, default: _prof_): use uncv (ISO 23001-17) for raw video +-* off: disabled (always the case when muxing to QT) +-* gen: enabled, do not write profile +-* prof: enabled and write profile if known +-* tiny: enabled and write reduced version if profile known and compatible ++ ++- off: disabled (always the case when muxing to QT) ++- gen: enabled, do not write profile ++- prof: enabled and write profile if known ++- tiny: enabled and write reduced version if profile known and compatible + + __trunv1__ (bool, default: _false_): force using version 1 of trun regardless of media type or CMAF brand + __rsot__ (bool, default: _false_): inject redundant sample timing information when present +diff --git a/docs/Filters/mpeghdec.md b/docs/Filters/mpeghdec.md +index a1b2f271..df6f580b 100644 +--- a/docs/Filters/mpeghdec.md ++++ b/docs/Filters/mpeghdec.md +@@ -1,6 +1,6 @@ + + +-# MPEG-H Audio decoder ++# MPEG-H Audio decoder {:data-level="all"} + + Register name used to load filter: __mpeghdec__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/nhmlr.md b/docs/Filters/nhmlr.md +index 35daf657..c33d166e 100644 +--- a/docs/Filters/nhmlr.md ++++ b/docs/Filters/nhmlr.md +@@ -1,6 +1,6 @@ + + +-# NHML reader ++# NHML reader {:data-level="all"} + + Register name used to load filter: __nhmlr__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/nhmlw.md b/docs/Filters/nhmlw.md +index ca157206..34895651 100644 +--- a/docs/Filters/nhmlw.md ++++ b/docs/Filters/nhmlw.md +@@ -1,6 +1,6 @@ + + +-# NHML writer ++# NHML writer {:data-level="all"} + + Register name used to load filter: __nhmlw__ + This filter may be automatically loaded during graph resolution. +@@ -17,8 +17,9 @@ NHML documentation is available at https://wiki.gpac.io/xmlformats/NHML-Format + __nhmlonly__ (bool, default: _false_): only dump NHML info, not media + __pckp__ (bool, default: _false_): full NHML dump + __chksum__ (enum, default: _none_): insert frame checksum +-* none: no checksum +-* crc: CRC32 checksum +-* sha1: SHA1 checksum ++ ++- none: no checksum ++- crc: CRC32 checksum ++- sha1: SHA1 checksum + + +diff --git a/docs/Filters/nhntr.md b/docs/Filters/nhntr.md +index 902258b5..25fc2eb4 100644 +--- a/docs/Filters/nhntr.md ++++ b/docs/Filters/nhntr.md +@@ -1,6 +1,6 @@ + + +-# NHNT reader ++# NHNT reader {:data-level="all"} + + Register name used to load filter: __nhntr__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/nhntw.md b/docs/Filters/nhntw.md +index dcc9c220..23503a96 100644 +--- a/docs/Filters/nhntw.md ++++ b/docs/Filters/nhntw.md +@@ -1,6 +1,6 @@ + + +-# NHNT writer ++# NHNT writer {:data-level="all"} + + Register name used to load filter: __nhntw__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/nvdec.md b/docs/Filters/nvdec.md +index 4634a2f2..e140a63e 100644 +--- a/docs/Filters/nvdec.md ++++ b/docs/Filters/nvdec.md +@@ -1,6 +1,6 @@ + + +-# NVidia decoder ++# NVidia decoder {:data-level="all"} + + Register name used to load filter: __nvdec__ + This filter may be automatically loaded during graph resolution. +@@ -8,26 +8,29 @@ This filter may be automatically loaded during graph resolution. + This filter decodes MPEG-2, MPEG-4 Part 2, AVC|H264 and HEVC streams through NVidia decoder. It allows GPU frame dispatch or direct frame copy. + If the SDK is not available, the configuration key `nvdec@disabled` will be written in configuration file to avoid future load attempts. + +-The absolute path to cuda lib can be set using the `cuda_lib` option in `core` or `temp` section of the config file, e.g. `-cfg=temp:cuda_lib=PATH_TO_CUDA`. +-The absolute path to cuvid lib can be set using the `cuvid_lib` option in `core` or `temp` section of the config file, e.g. `-cfg=temp:cuvid_lib=PATH_TO_CUDA`. +- ++The absolute path to cuda lib can be set using the `cuda_lib` option in `core` or `temp` section of the config file, e.g. `-cfg=temp:cuda_lib=PATH_TO_CUDA` ++The absolute path to cuvid lib can be set using the `cuvid_lib` option in `core` or `temp` section of the config file, e.g. `-cfg=temp:cuvid_lib=PATH_TO_CUDA` ++ + + # Options + + __num_surfaces__ (uint, default: _20_): number of hardware surfaces to allocate + __unload__ (enum, default: _no_): decoder unload mode +-* no: keep inactive decoder alive +-* destroy: destroy inactive decoder +-* reuse: detach decoder from inactive PIDs and reattach to active ones ++ ++- no: keep inactive decoder alive ++- destroy: destroy inactive decoder ++- reuse: detach decoder from inactive PIDs and reattach to active ones + + __vmode__ (enum, default: _cuvid_): video decoder backend +-* cuvid: use dedicated video engines directly +-* cuda: use a CUDA-based decoder if faster than dedicated engines +-* dxva: go through DXVA internally if possible (requires D3D9) ++ ++- cuvid: use dedicated video engines directly ++- cuda: use a CUDA-based decoder if faster than dedicated engines ++- dxva: go through DXVA internally if possible (requires D3D9) + + __fmode__ (enum, default: _gl_): frame output mode +-* copy: each frame is copied and dispatched +-* single: frame data is only retrieved when used, single memory space for all frames (not safe if multiple consumers) +-* gl: frame data is mapped to an OpenGL texture ++ ++- copy: each frame is copied and dispatched ++- single: frame data is only retrieved when used, single memory space for all frames (not safe if multiple consumers) ++- gl: frame data is mapped to an OpenGL texture + + +diff --git a/docs/Filters/odfdec.md b/docs/Filters/odfdec.md +index d6072528..03d24248 100644 +--- a/docs/Filters/odfdec.md ++++ b/docs/Filters/odfdec.md +@@ -1,6 +1,6 @@ + + +-# MPEG-4 OD decoder ++# MPEG-4 OD decoder {:data-level="all"} + + Register name used to load filter: __odfdec__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/oggdmx.md b/docs/Filters/oggdmx.md +index 092e2621..cede61d9 100644 +--- a/docs/Filters/oggdmx.md ++++ b/docs/Filters/oggdmx.md +@@ -1,6 +1,6 @@ + + +-# OGG demultiplexer ++# OGG demultiplexer {:data-level="all"} + + Register name used to load filter: __oggdmx__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/oggmx.md b/docs/Filters/oggmx.md +index b547ee5a..ae0ece91 100644 +--- a/docs/Filters/oggmx.md ++++ b/docs/Filters/oggmx.md +@@ -1,6 +1,6 @@ + + +-# OGG multiplexer ++# OGG multiplexer {:data-level="all"} + + Register name used to load filter: __oggmx__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/ohevcdec.md b/docs/Filters/ohevcdec.md +index 61a4d4f3..85da0a28 100644 +--- a/docs/Filters/ohevcdec.md ++++ b/docs/Filters/ohevcdec.md +@@ -1,6 +1,6 @@ + + +-# OpenHEVC decoder ++# OpenHEVC decoder {:data-level="all"} + + Register name used to load filter: __ohevcdec__ + This filter may be automatically loaded during graph resolution. +@@ -11,9 +11,10 @@ This filter decodes HEVC and LHVC (HEVC scalable extensions) from one or more PI + # Options + + __threading__ (enum, default: _frame_): set threading mode +-* frameslice: parallel decoding of both frames and slices +-* frame: parallel decoding of frames +-* slice: parallel decoding of slices ++ ++- frameslice: parallel decoding of both frames and slices ++- frame: parallel decoding of frames ++- slice: parallel decoding of slices + + __nb_threads__ (uint, default: _0_): set number of threads (if 0, uses number of cores minus one) + __no_copy__ (bool, default: _false_): directly dispatch internal decoded frame without copy +diff --git a/docs/Filters/osvcdec.md b/docs/Filters/osvcdec.md +index 6cffa2f3..c7b26e2c 100644 +--- a/docs/Filters/osvcdec.md ++++ b/docs/Filters/osvcdec.md +@@ -1,6 +1,6 @@ + + +-# OpenSVC decoder ++# OpenSVC decoder {:data-level="all"} + + Register name used to load filter: __osvcdec__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/pin.md b/docs/Filters/pin.md +index ad1f29b4..9f58ebc5 100644 +--- a/docs/Filters/pin.md ++++ b/docs/Filters/pin.md +@@ -1,6 +1,6 @@ + + +-# pipe input ++# pipe input {:data-level="all"} + + Register name used to load filter: __pin__ + This filter may be automatically loaded during graph resolution. +@@ -35,13 +35,17 @@ The filter can create the pipe if not found using [mkp](#mkp). On windows hosts, + On non windows hosts, the created pipe will delete the pipe file upon filter destruction. + + Input pipes can be setup to run forever using [ka](#ka). In this case: ++ + - any potential pipe close on the writing side will be ignored + - pipeline flushing will be triggered upon pipe close if [sigflush](#sigflush) is set + - final end of stream will be triggered upon session close. ++ + + This can be useful to pipe raw streams from different process into gpac: +-* Receiver side: `gpac -i pipe://mypipe:ext=.264:mkp:ka` +-* Sender side: `cat raw1.264 > mypipe && gpac -i raw2.264 -o pipe://mypipe:ext=.264` ++ ++- Receiver side: `gpac -i pipe://mypipe:ext=.264:mkp:ka` ++- Sender side: `cat raw1.264 > mypipe && gpac -i raw2.264 -o pipe://mypipe:ext=.264` ++ + The pipeline flush is signaled as EOS while keeping the stream active. + This is typically needed for mux filters waiting for EOS to flush their data. + +diff --git a/docs/Filters/pngenc.md b/docs/Filters/pngenc.md +index 58151bf3..c7396f23 100644 +--- a/docs/Filters/pngenc.md ++++ b/docs/Filters/pngenc.md +@@ -1,6 +1,6 @@ + + +-# PNG encoder ++# PNG encoder {:data-level="all"} + + Register name used to load filter: __pngenc__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/pout.md b/docs/Filters/pout.md +index aace8f97..e318a4ea 100644 +--- a/docs/Filters/pout.md ++++ b/docs/Filters/pout.md +@@ -1,6 +1,6 @@ + + +-# pipe output ++# pipe output {:data-level="all"} + + Register name used to load filter: __pout__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/probe.md b/docs/Filters/probe.md +index 08eeac3b..463fe9cf 100644 +--- a/docs/Filters/probe.md ++++ b/docs/Filters/probe.md +@@ -1,6 +1,6 @@ + + +-# Probe source ++# Probe source {:data-level="all"} + + Register name used to load filter: __probe__ + This filter is not checked during graph resolution and needs explicit loading. +@@ -14,10 +14,11 @@ It is up to the app developer to query input PIDs of the prober and take appropr + # Options + + __log__ (str, default: _stdout_, Enum: _any|stderr|stdout|GLOG|null): set probe log filename to print number of streams +-* _any: target file path and name +-* stderr: dump to stderr +-* stdout: dump to stdout +-* GLOG: use GPAC logs `app@info` +-* null: silent mode ++ ++- _any: target file path and name ++- stderr: dump to stderr ++- stdout: dump to stdout ++- GLOG: use GPAC logs `app@info` ++- null: silent mode + + +diff --git a/docs/Filters/reframer.md b/docs/Filters/reframer.md +index d0a78723..4aac59df 100644 +--- a/docs/Filters/reframer.md ++++ b/docs/Filters/reframer.md +@@ -1,17 +1,19 @@ + + +-# Media Reframer ++# Media Reframer {:data-level="all"} + + Register name used to load filter: __reframer__ + This filter is not checked during graph resolution and needs explicit loading. + Filters of this class can connect to each-other. + + This filter provides various tools on inputs: ++ + - ensure reframing (1 packet = 1 Access Unit) + - optionally force decoding + - real-time regulation + - packet filtering based on SAP types or frame numbers + - time-range extraction and splitting ++ + + This filter forces input PIDs to be properly framed (1 packet = 1 Access Unit). + It is typically needed to force remultiplexing in file to file operations when source and destination files use the same format. +@@ -51,12 +53,14 @@ gpac -i m.mp4 reframer:rt=on -o live.mpd:dynamic + + The filter can perform time range extraction of the source using [xs](#xs) and [xe](#xe) options. + The formats allowed for times specifiers are: +-* 'T'H:M:S, 'T'M:S: specify time in hours, minutes, seconds +-* 'T'H:M:S.MS, 'T'M:S.MS, 'T'S.MS: specify time in hours, minutes, seconds and milliseconds +-* INT, FLOAT, NUM/DEN: specify time in seconds (number or fraction) +-* 'D'INT, 'D'FLOAT, 'D'NUM/DEN: specify end time as offset to start time in seconds (number or fraction) - only valid for [xe](#xe) +-* 'F'NUM: specify time as frame number, 1 being first +-* XML DateTime: specify absolute UTC time ++ ++- 'T'H:M:S, 'T'M:S: specify time in hours, minutes, seconds ++- 'T'H:M:S.MS, 'T'M:S.MS, 'T'S.MS: specify time in hours, minutes, seconds and milliseconds ++- INT, FLOAT, NUM/DEN: specify time in seconds (number or fraction) ++- 'D'INT, 'D'FLOAT, 'D'NUM/DEN: specify end time as offset to start time in seconds (number or fraction) - only valid for [xe](#xe) ++- 'F'NUM: specify time as frame number, 1 being first ++- XML DateTime: specify absolute UTC time ++ + + In this mode, the timestamps are rewritten to form a continuous timeline, unless [xots](#xots) is set. + When multiple ranges are given, the filter will try to seek if needed and supported by source. +@@ -65,32 +69,40 @@ Example + ``` + gpac -i m.mp4 reframer:xs=T00:00:10,T00:01:10,T00:02:00:xe=T00:00:20,T00:01:20 [dst] + ``` ++ + This will extract the time ranges [10s,20s], [1m10s,1m20s] and all media starting from 2m + + If no end range is found for a given start range: ++ + - if a following start range is set, the end range is set to this next start + - otherwise, the end range is open ++ + + Example + ``` + gpac -i m.mp4 reframer:xs=0,10,25:xe=5,20 [dst] +-``` ++``` ++ + This will extract the time ranges [0s,5s], [10s,20s] and all media starting from 25s + Example + ``` + gpac -i m.mp4 reframer:xs=0,10,25 [dst] +-``` ++``` ++ + This will extract the time ranges [0s,10s], [10s,25s] and all media starting from 25s + + It is possible to signal range boundaries in output packets using [splitrange](#splitrange). + This will expose on the first packet of each range in each PID the following properties: +-* `FileNumber`: starting at 1 for the first range, to be used as replacement for $num$ in templates +-* `FileSuffix`: corresponding to `StartRange_EndRange` or `StartRange` for open ranges, to be used as replacement for $FS$ in templates ++ ++- `FileNumber`: starting at 1 for the first range, to be used as replacement for $num$ in templates ++- `FileSuffix`: corresponding to `StartRange_EndRange` or `StartRange` for open ranges, to be used as replacement for $FS$ in templates ++ + + Example + ``` + gpac -i m.mp4 reframer:xs=T00:00:10,T00:01:10:xe=T00:00:20:splitrange -o dump_$FS$.264 [dst] + ``` ++ + This will create two output files dump_T00.00.10_T00.02.00.264 and dump_T00.01.10.264. + _Note: The `:` and `/` characters are replaced by `.` in `FileSuffix` property._ + +@@ -102,18 +114,23 @@ Example + ``` + gpac -i m.mp4 reframer:xs=0,30::props=#Period=P1,#Period=P2:#foo=bar [dst] + ``` ++ + This will assign to output PIDs +-* during the range [0,30]: property `Period` to `P1` +-* during the range [30, end]: properties `Period` to `P2` and property `foo` to `bar` ++ ++- during the range [0,30]: property `Period` to `P1` ++- during the range [30, end]: properties `Period` to `P2` and property `foo` to `bar` ++ + + For uncompressed audio PIDs, input frame will be split to closest audio sample number. + + When [xround](#xround) is set to `seek`, the following applies: ++ + - a single range shall be specified + - the first I-frame preceding or matching the range start is used as split point + - all packets before range start are marked as seek points + - packets overlapping range start are forwarded with a `SkipBegin` property set to the amount of media to skip + - packets overlapping range end are forwarded with an adjusted duration to match the range end ++ + This mode is typically used to extract a range in a frame/sample accurate way, rather than a GOP-aligned way. + + When [xround](#xround) is not set to `seek`, compressed audio streams will still use seek mode. +@@ -123,8 +140,10 @@ This can be avoided using [no_audio_seek](#no_audio_seek), but this will introdu + # UTC-based range extraction + + The filter can perform range extraction based on UTC time rather than media time. In this mode, the end time must be: +-* a UTC date: range extraction will stop after this date +-* a time in second: range extraction will stop after the specified duration ++ ++- a UTC date: range extraction will stop after this date ++- a time in second: range extraction will stop after the specified duration ++ + + The UTC reference is specified using [utc_ref](#utc_ref). + If UTC signal from media source is used, the filter will probe for [utc_probe](#utc_probe) before considering the source has no UTC signal. +@@ -135,10 +154,12 @@ The properties `SenderNTP` and, if absent, `UTC` of source packets are checked f + + The filter can perform splitting of the source using [xs](#xs) option. + The additional formats allowed for [xs](#xs) option are: +-* `SAP`: split source at each SAP/RAP +-* `D`VAL: split source by chunks of `VAL` seconds +-* `D`NUM/DEN: split source by chunks of `NUM/DEN` seconds +-* `S`VAL: split source by chunks of estimated size `VAL` bytes (can use property multipliers, e.g. `m`) ++ ++- `SAP`: split source at each SAP/RAP ++- `D`VAL: split source by chunks of `VAL` seconds ++- `D`NUM/DEN: split source by chunks of `NUM/DEN` seconds ++- `S`VAL: split source by chunks of estimated size `VAL` bytes (can use property multipliers, e.g. `m`) ++ + + _Note: In these modes, [splitrange](#splitrange) and [xadjust](#xadjust) are implicitly set._ + +@@ -147,28 +168,31 @@ _Note: In these modes, [splitrange](#splitrange) and [xadjust](#xadjust) are imp + + __exporter__ (bool, default: _false_): compatibility with old exporter, displays export results + __rt__ (enum, default: _off_, updatable): real-time regulation mode of input +-* off: disables real-time regulation +-* on: enables real-time regulation, one clock per PID +-* sync: enables real-time regulation one clock for all PIDs ++ ++- off: disables real-time regulation ++- on: enables real-time regulation, one clock per PID ++- sync: enables real-time regulation one clock for all PIDs + + __saps__ (uintl, Enum: 0|1|2|3|4, updatable): list of SAP types (0,1,2,3,4) to forward, other packets are dropped (forwarding only sap 0 will break the decoding) + + __refs__ (bool, default: _false_, updatable): forward only frames used as reference frames, if indicated in the input stream + __speed__ (dbl, default: _0.0_, updatable): speed for real-time regulation mode, a value of 0 uses speed from play commands + __raw__ (enum, default: _no_): force input AV streams to be in raw format +-* no: do not force decoding of inputs +-* av: force decoding of audio and video inputs +-* a: force decoding of audio inputs +-* v: force decoding of video inputs ++ ++- no: do not force decoding of inputs ++- av: force decoding of audio and video inputs ++- a: force decoding of audio inputs ++- v: force decoding of video inputs + + __frames__ (sintl, updatable): drop all except listed frames (first being 1). A negative value `-V` keeps only first frame every `V` frames + __xs__ (strl): extraction start time(s) + __xe__ (strl): extraction end time(s). If less values than start times, the last time interval extracted is an open range + __xround__ (enum, default: _before_): adjust start time of extraction range to I-frame +-* before: use first I-frame preceding or matching range start +-* seek: see filter help +-* after: use first I-frame (if any) following or matching range start +-* closest: use I-frame closest to range start ++ ++- before: use first I-frame preceding or matching range start ++- seek: see filter help ++- after: use first I-frame (if any) following or matching range start ++- closest: use I-frame closest to range start + + __xadjust__ (bool, default: _false_): adjust end time of extraction range to be before next I-frame + __xots__ (bool, default: _false_): keep original timestamps after extraction +@@ -180,16 +204,18 @@ _Note: In these modes, [splitrange](#splitrange) and [xadjust](#xadjust) are imp + __no_audio_seek__ (bool, default: _false_): disable seek mode on audio streams (no change of priming duration) + __probe_ref__ (bool, default: _false_): allow extracted range to be longer in case of B-frames with reference frames presented outside of range + __utc_ref__ (enum, default: _any_): set reference mode for UTC range extraction +-* local: use UTC of local host +-* any: use UTC of media, or UTC of local host if not found in media after probing time +-* media: use UTC of media (abort if none found) ++ ++- local: use UTC of local host ++- any: use UTC of media, or UTC of local host if not found in media after probing time ++- media: use UTC of media (abort if none found) + + __utc_probe__ (uint, default: _5000_): timeout in milliseconds to try to acquire UTC reference from media + __copy__ (bool, default: _false_, updatable): try copying frame interface into packets + __cues__ (enum, default: _no_, updatable): cue filtering mode +-* no: do no filter frames based on cue info +-* segs: only forward frames marked as segment start +-* frags: only forward frames marked as fragment start ++ ++- no: do no filter frames based on cue info ++- segs: only forward frames marked as segment start ++- frags: only forward frames marked as fragment start + + __rmseek__ (bool, default: _false_, updatable): remove seek flag of all sent packets + +diff --git a/docs/Filters/resample.md b/docs/Filters/resample.md +index ca1d2dfe..e5e3210a 100644 +--- a/docs/Filters/resample.md ++++ b/docs/Filters/resample.md +@@ -1,6 +1,6 @@ + + +-# Audio resampler ++# Audio resampler {:data-level="all"} + + Register name used to load filter: __resample__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/restamp.md b/docs/Filters/restamp.md +index e9a1a828..46ed0456 100644 +--- a/docs/Filters/restamp.md ++++ b/docs/Filters/restamp.md +@@ -1,6 +1,6 @@ + + +-# Packet timestamp rewriter ++# Packet timestamp rewriter {:data-level="all"} + + Register name used to load filter: __restamp__ + This filter is not checked during graph resolution and needs explicit loading. +@@ -11,13 +11,16 @@ This filter rewrites timing (offsets and rate) of packets. + The delays (global or per stream class) can be either positive (stream presented later) or negative (stream presented sooner). + + The specified [fps](#fps) can be either 0, positive or negative. ++ + - if 0 or if the stream is audio, stream rate is not modified. + - otherwise if negative, stream rate is multiplied by `-fps.num/fps.den`. + - otherwise if positive and the stream is not video, stream rate is not modified. + - otherwise (video PID), constant frame rate is assumed and: +- - if [rawv=no](#rawv=no), video frame rate is changed to the specified rate (speed-up or slow-down). +- - if [rawv=force](#rawv=force), input video stream is decoded and video frames are dropped/copied to match the new rate. +- - if [rawv=dyn](#rawv=dyn), input video stream is decoded if not all-intra and video frames are dropped/copied to match the new rate. ++ ++ - if [rawv=no](#rawv=no), video frame rate is changed to the specified rate (speed-up or slow-down). ++ - if [rawv=force](#rawv=force), input video stream is decoded and video frames are dropped/copied to match the new rate. ++ - if [rawv=dyn](#rawv=dyn), input video stream is decoded if not all-intra and video frames are dropped/copied to match the new rate. ++ + + _Note: frames are simply copied or dropped with no motion compensation._ + +@@ -34,9 +37,10 @@ is set to the last computed timestamp plus the minimum packet duration for the s + __delay_t__ (frac, default: _0/1_, updatable): delay to add to text streams + __delay_o__ (frac, default: _0/1_, updatable): delay to add to other streams + __rawv__ (enum, default: _no_): copy video frames +-* no: no raw frame copy/drop +-* force: force decoding all video streams +-* dyn: decoding video streams if not all intra ++ ++- no: no raw frame copy/drop ++- force: force decoding all video streams ++- dyn: decoding video streams if not all intra + + __tsinit__ (lfrac, default: _-1/1_): initial timestamp to resync to, negative values disables resync + __align__ (uint, default: _0_): timestamp alignment threshold (0 disables alignment) - see filter help +diff --git a/docs/Filters/rewind.md b/docs/Filters/rewind.md +index 7ec1b63a..73f6ff9f 100644 +--- a/docs/Filters/rewind.md ++++ b/docs/Filters/rewind.md +@@ -1,6 +1,6 @@ + + +-# Audio/Video rewinder ++# Audio/Video rewinder {:data-level="all"} + + Register name used to load filter: __rewind__ + This filter is not checked during graph resolution and needs explicit loading. +diff --git a/docs/Filters/rfac3.md b/docs/Filters/rfac3.md +index 3ff4332a..5a25aa95 100644 +--- a/docs/Filters/rfac3.md ++++ b/docs/Filters/rfac3.md +@@ -1,6 +1,6 @@ + + +-# AC3 reframer ++# AC3 reframer {:data-level="all"} + + Register name used to load filter: __rfac3__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/rfadts.md b/docs/Filters/rfadts.md +index b307f238..be969fd6 100644 +--- a/docs/Filters/rfadts.md ++++ b/docs/Filters/rfadts.md +@@ -1,6 +1,6 @@ + + +-# ADTS reframer ++# ADTS reframer {:data-level="all"} + + Register name used to load filter: __rfadts__ + This filter may be automatically loaded during graph resolution. +@@ -14,14 +14,16 @@ This filter parses AAC files/data and outputs corresponding audio PID and frames + __index__ (dbl, default: _1.0_): indexing window length + __ovsbr__ (bool, default: _false_): force oversampling SBR (does not multiply timescales by 2) + __sbr__ (enum, default: _no_): set SBR signaling +-* no: no SBR signaling at all +-* imp: backward-compatible SBR signaling (audio signaled as AAC-LC) +-* exp: explicit SBR signaling (audio signaled as AAC-SBR) ++ ++- no: no SBR signaling at all ++- imp: backward-compatible SBR signaling (audio signaled as AAC-LC) ++- exp: explicit SBR signaling (audio signaled as AAC-SBR) + + __ps__ (enum, default: _no_): set PS signaling +-* no: no PS signaling at all +-* imp: backward-compatible PS signaling (audio signaled as AAC-LC) +-* exp: explicit PS signaling (audio signaled as AAC-PS) ++ ++- no: no PS signaling at all ++- imp: backward-compatible PS signaling (audio signaled as AAC-LC) ++- exp: explicit PS signaling (audio signaled as AAC-PS) + + __expart__ (bool, default: _false_): expose pictures as a dedicated video PID + __aacchcfg__ (sint, default: _0_): set AAC channel configuration to this value if missing from ADTS header, use negative value to always override +diff --git a/docs/Filters/rfamr.md b/docs/Filters/rfamr.md +index 4bb6622e..607cf107 100644 +--- a/docs/Filters/rfamr.md ++++ b/docs/Filters/rfamr.md +@@ -1,6 +1,6 @@ + + +-# AMR/EVRC reframer ++# AMR/EVRC reframer {:data-level="all"} + + Register name used to load filter: __rfamr__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/rfav1.md b/docs/Filters/rfav1.md +index 84e170db..8d2b7fc9 100644 +--- a/docs/Filters/rfav1.md ++++ b/docs/Filters/rfav1.md +@@ -1,6 +1,6 @@ + + +-# AV1/IVF/VP9 reframer ++# AV1/IVF/VP9 reframer {:data-level="all"} + + Register name used to load filter: __rfav1__ + This filter may be automatically loaded during graph resolution. +@@ -17,8 +17,9 @@ This filter parses AV1 OBU, AV1 AnnexB or IVF with AV1 or VP9 files/data and out + __notime__ (bool, default: _false_): ignore input timestamps, rebuild from 0 + __temporal_delim__ (bool, default: _false_): keep temporal delimiters in reconstructed frames + __bsdbg__ (enum, default: _off_): debug OBU parsing in `media@debug logs` +-* off: not enabled +-* on: enabled +-* full: enable with number of bits dumped ++ ++- off: not enabled ++- on: enabled ++- full: enable with number of bits dumped + + +diff --git a/docs/Filters/rfflac.md b/docs/Filters/rfflac.md +index 93e2d465..1f167471 100644 +--- a/docs/Filters/rfflac.md ++++ b/docs/Filters/rfflac.md +@@ -1,6 +1,6 @@ + + +-# FLAC reframer ++# FLAC reframer {:data-level="all"} + + Register name used to load filter: __rfflac__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/rfh263.md b/docs/Filters/rfh263.md +index 090bfad4..68a72dc6 100644 +--- a/docs/Filters/rfh263.md ++++ b/docs/Filters/rfh263.md +@@ -1,6 +1,6 @@ + + +-# H263 reframer ++# H263 reframer {:data-level="all"} + + Register name used to load filter: __rfh263__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/rfimg.md b/docs/Filters/rfimg.md +index aafea614..7c602601 100644 +--- a/docs/Filters/rfimg.md ++++ b/docs/Filters/rfimg.md +@@ -1,6 +1,6 @@ + + +-# JPG/J2K/PNG/BMP reframer ++# JPG/J2K/PNG/BMP reframer {:data-level="all"} + + Register name used to load filter: __rfimg__ + This filter may be automatically loaded during graph resolution. +@@ -8,8 +8,10 @@ This filter may be automatically loaded during graph resolution. + This filter parses JPG/J2K/PNG/BMP files/data and outputs corresponding visual PID and frames. + + The following extensions for PNG change the pixel format for RGBA images: +-* pngd: use RGB+depth map pixel format +-* pngds: use RGB+depth(7bits)+shape(MSB of alpha channel) pixel format ++ ++- pngd: use RGB+depth map pixel format ++- pngds: use RGB+depth(7bits)+shape(MSB of alpha channel) pixel format ++ + + No options + +diff --git a/docs/Filters/rflatm.md b/docs/Filters/rflatm.md +index fa59a0a0..a3ac8754 100644 +--- a/docs/Filters/rflatm.md ++++ b/docs/Filters/rflatm.md +@@ -1,6 +1,6 @@ + + +-# LATM reframer ++# LATM reframer {:data-level="all"} + + Register name used to load filter: __rflatm__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/rfmhas.md b/docs/Filters/rfmhas.md +index 3194593f..cf49afc2 100644 +--- a/docs/Filters/rfmhas.md ++++ b/docs/Filters/rfmhas.md +@@ -1,6 +1,6 @@ + + +-# MPEH-H Audio Stream reframer ++# MPEH-H Audio Stream reframer {:data-level="all"} + + Register name used to load filter: __rfmhas__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/rfmp3.md b/docs/Filters/rfmp3.md +index a8455510..8f21fc25 100644 +--- a/docs/Filters/rfmp3.md ++++ b/docs/Filters/rfmp3.md +@@ -1,6 +1,6 @@ + + +-# MP3 reframer ++# MP3 reframer {:data-level="all"} + + Register name used to load filter: __rfmp3__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/rfmpgvid.md b/docs/Filters/rfmpgvid.md +index 72aff953..7359d26b 100644 +--- a/docs/Filters/rfmpgvid.md ++++ b/docs/Filters/rfmpgvid.md +@@ -1,6 +1,6 @@ + + +-# M1V/M2V/M4V reframer ++# M1V/M2V/M4V reframer {:data-level="all"} + + Register name used to load filter: __rfmpgvid__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/rfnalu.md b/docs/Filters/rfnalu.md +index ec642285..4febe4c8 100644 +--- a/docs/Filters/rfnalu.md ++++ b/docs/Filters/rfnalu.md +@@ -1,6 +1,6 @@ + + +-# AVC/HEVC reframer ++# AVC/HEVC reframer {:data-level="all"} + + Register name used to load filter: __rfnalu__ + This filter may be automatically loaded during graph resolution. +@@ -16,9 +16,10 @@ _Note: The filter uses negative CTS offsets: CTS is correct, but some frames may + __index__ (dbl, default: _-1.0_): indexing window length. If 0, bitstream is not probed for duration. A negative value skips the indexing if the source file is larger than 20M (slows down importers) unless a play with start range > 0 is issued + __explicit__ (bool, default: _false_): use explicit layered (SVC/LHVC) import + __strict_poc__ (enum, default: _off_): delay frame output of an entire GOP to ensure CTS info is correct when POC suddenly changes +-* off: disable GOP buffering +-* on: enable GOP buffering, assuming no error in POC +-* error: enable GOP buffering and try to detect lost frames ++ ++- off: disable GOP buffering ++- on: enable GOP buffering, assuming no error in POC ++- error: enable GOP buffering and try to detect lost frames + + __nosei__ (bool, default: _false_): remove all sei messages + __nosvc__ (bool, default: _false_): remove all SVC/MVC/LHVC data +@@ -31,25 +32,28 @@ _Note: The filter uses negative CTS offsets: CTS is correct, but some frames may + __audelim__ (bool, default: _false_): keep Access Unit delimiter in payload + __notime__ (bool, default: _false_): ignore input timestamps, rebuild from 0 + __dv_mode__ (enum, default: _auto_): signaling for DolbyVision +-* none: never signal DV profile +-* auto: signal DV profile if RPU or EL are found +-* clean: do not signal and remove RPU and EL NAL units +-* single: signal DV profile if RPU are found and remove EL NAL units ++ ++- none: never signal DV profile ++- auto: signal DV profile if RPU or EL are found ++- clean: do not signal and remove RPU and EL NAL units ++- single: signal DV profile if RPU are found and remove EL NAL units + + __dv_profile__ (uint, default: _0_): profile for DolbyVision (currently defined profiles are 4, 5, 7, 8, 9), 0 for auto-detect + __dv_compatid__ (enum, default: _auto_): cross-compatibility ID for DolbyVision +-* auto: auto-detect +-* none: no cross-compatibility +-* hdr10: CTA HDR10, as specified by EBU TR 03 +-* bt709: SDR BT.709 +-* hlg709: HLG BT.709 gamut in ITU-R BT.2020 +-* hlg2100: HLG BT.2100 gamut in ITU-R BT.2020 +-* bt2020: SDR BT.2020 +-* brd: Ultra HD Blu-ray Disc HDR ++ ++- auto: auto-detect ++- none: no cross-compatibility ++- hdr10: CTA HDR10, as specified by EBU TR 03 ++- bt709: SDR BT.709 ++- hlg709: HLG BT.709 gamut in ITU-R BT.2020 ++- hlg2100: HLG BT.2100 gamut in ITU-R BT.2020 ++- bt2020: SDR BT.2020 ++- brd: Ultra HD Blu-ray Disc HDR + + __bsdbg__ (enum, default: _off_): debug NAL parsing in `media@debug` logs +-* off: not enabled +-* on: enabled +-* full: enable with number of bits dumped ++ ++- off: not enabled ++- on: enabled ++- full: enable with number of bits dumped + + +diff --git a/docs/Filters/rfpcm.md b/docs/Filters/rfpcm.md +index 7d5d3b98..d8fe952b 100644 +--- a/docs/Filters/rfpcm.md ++++ b/docs/Filters/rfpcm.md +@@ -1,6 +1,6 @@ + + +-# PCM reframer ++# PCM reframer {:data-level="all"} + + Register name used to load filter: __rfpcm__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/rfprores.md b/docs/Filters/rfprores.md +index c0d985e2..c03e8366 100644 +--- a/docs/Filters/rfprores.md ++++ b/docs/Filters/rfprores.md +@@ -1,6 +1,6 @@ + + +-# ProRes reframer ++# ProRes reframer {:data-level="all"} + + Register name used to load filter: __rfprores__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/rfqcp.md b/docs/Filters/rfqcp.md +index 5da4e3f1..a36e713e 100644 +--- a/docs/Filters/rfqcp.md ++++ b/docs/Filters/rfqcp.md +@@ -1,6 +1,6 @@ + + +-# QCP reframer ++# QCP reframer {:data-level="all"} + + Register name used to load filter: __rfqcp__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/rfrawvid.md b/docs/Filters/rfrawvid.md +index b57c21d9..a74d8687 100644 +--- a/docs/Filters/rfrawvid.md ++++ b/docs/Filters/rfrawvid.md +@@ -1,6 +1,6 @@ + + +-# RAW video reframer ++# RAW video reframer {:data-level="all"} + + Register name used to load filter: __rfrawvid__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/rfsrt.md b/docs/Filters/rfsrt.md +index e486f993..b79d8eee 100644 +--- a/docs/Filters/rfsrt.md ++++ b/docs/Filters/rfsrt.md +@@ -1,6 +1,6 @@ + + +-# SRT reframer ++# SRT reframer {:data-level="all"} + + Register name used to load filter: __rfsrt__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/rftruehd.md b/docs/Filters/rftruehd.md +index a25072ca..a9f1f2d3 100644 +--- a/docs/Filters/rftruehd.md ++++ b/docs/Filters/rftruehd.md +@@ -1,6 +1,6 @@ + + +-# TrueHD reframer ++# TrueHD reframer {:data-level="all"} + + Register name used to load filter: __rftruehd__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/routein.md b/docs/Filters/routein.md +index 1e7b6832..8c395b94 100644 +--- a/docs/Filters/routein.md ++++ b/docs/Filters/routein.md +@@ -1,31 +1,37 @@ + + +-# ROUTE input ++# ROUTE input {:data-level="all"} + + Register name used to load filter: __routein__ + This filter may be automatically loaded during graph resolution. + +-This filter is a receiver for ROUTE sessions (ATSC 3.0 and generic ROUTE) and DVB-MABR flute sessions. ++This filter is a receiver for file delivery over multicast. It currently supports ATSC 3.0, generic ROUTE and DVB-MABR flute. ++ + - ATSC 3.0 mode is identified by the URL `atsc://`. + - Generic ROUTE mode is identified by the URL `route://IP:PORT`. + - DVB-MABR mode is identified by the URL `mabr://IP:PORT` pointing to the bootstrap FLUTE channel carrying the multicast gateway configuration. ++ + + The filter can work in cached mode, source mode or standalone mode. + + # Cached mode + +-The cached mode is the default filter behavior. It populates GPAC HTTP Cache with the received files, using `http://groute/serviceN/` as service root, `N being the ROUTE service ID.` ++The cached mode is the default filter behavior. It populates GPAC HTTP Cache with the received files, using `http://gmcast/serviceN/` as service root, `N being the multicast service ID.` + In cached mode, repeated files are always pushed to cache. + The maximum number of media segment objects in cache per service is defined by [nbcached](#nbcached); this is a safety used to force object removal in case DASH client timing is wrong and some files are never requested at cache level. + + The cached MPD is assigned the following headers: +-* `x-route`: integer value, indicates the ROUTE service ID. +-* `x-route-first-seg`: string value, indicates the name of the first segment (completely or currently being) retrieved from the broadcast. +-* `x-route-ll`: boolean value, if yes indicates that the indicated first segment is currently being received (low latency signaling). +-* `x-route-loop`: boolean value, if yes indicates a loop (e.g. pcap replay) in the service has been detected - only checked if [cloop](#cloop) is set. ++ ++- `x-mcast`: boolean value, if `yes` indicates the file comes from a multicast. ++- `x-mcast-first-seg`: string value, indicates the name of the first segment (completely or currently being) retrieved from the broadcast. ++- `x-mcast-ll`: boolean value, if yes indicates that the indicated first segment is currently being received (low latency signaling). ++- `x-mcast-loop`: boolean value, if yes indicates a loop (e.g. pcap replay) in the service has been detected - only checked if [cloop](#cloop) is set. ++ + + The cached files are assigned the following headers: +-* `x-route`: boolean value, if yes indicates the file comes from an ROUTE session. ++ ++- `x-mcast`: boolean value, if `yes` indicates the file comes from a multicast. ++ + + If [max_segs](#max_segs) is set, file deletion event will be triggered in the filter chain. + +@@ -55,8 +61,10 @@ If [max_segs](#max_segs) is set, old files will be deleted. + # File Repair + + In case of losses or incomplete segment reception (during tune-in), the files are patched as follows: +-* MPEG-2 TS: all lost ranges are adjusted to 188-bytes boundaries, and transformed into NULL TS packets. +-* ISOBMFF: all top-level boxes are scanned, and incomplete boxes are transformed in `free` boxes, except mdat kept as is if [repair](#repair) is set to simple. ++ ++- MPEG-2 TS: all lost ranges are adjusted to 188-bytes boundaries, and transformed into NULL TS packets. ++- ISOBMFF: all top-level boxes are scanned, and incomplete boxes are transformed in `free` boxes, except mdat kept as is if [repair](#repair) is set to simple. ++ + + If [kc](#kc) option is set, corrupted files will be kept. If [fullseg](#fullseg) is not set and files are only partially received, they will be kept. + +@@ -68,7 +76,7 @@ Example + ``` + route add -net 224.0.23.60/32 -interface vboxnet0 + ``` +-Then for each ROUTE service in the multicast: ++Then for each multicast service in the multicast: + Example + ``` + route add -net 239.255.1.4/32 -interface vboxnet0 +@@ -96,11 +104,14 @@ route add -net 239.255.1.4/32 -interface vboxnet0 + __rtimeout__ (uint, default: _1000_): default timeout in us to wait when gathering out-of-order packets + __fullseg__ (bool, default: _false_): only dispatch full segments in cache mode (always true for other modes) + __repair__ (enum, default: _simple_): repair mode for corrupted files +-* no: no repair is performed +-* simple: simple repair is performed (incomplete `mdat` boxes will be kept) +-* strict: incomplete mdat boxes will be lost as well as preceding `moof` boxes +-* full: HTTP-based repair of all lost packets ++ ++- no: no repair is performed ++- simple: simple repair is performed (incomplete `mdat` boxes will be kept) ++- strict: incomplete mdat boxes will be lost as well as preceding `moof` boxes ++- full: HTTP-based repair of all lost packets + + __repair_url__ (cstr): repair url + __max_sess__ (uint, default: _1_): max number of concurrent HTTP repair sessions ++__llmode__ (bool, default: _true_): enable low-latency access ++__dynsel__ (bool, default: _true_): dynamically enable and disable multicast groups based on their selection state + +diff --git a/docs/Filters/routeout.md b/docs/Filters/routeout.md +index 20519614..917f00b1 100644 +--- a/docs/Filters/routeout.md ++++ b/docs/Filters/routeout.md +@@ -1,6 +1,6 @@ + + +-# ROUTE output ++# ROUTE output {:data-level="all"} + + Register name used to load filter: __routeout__ + This filter may be automatically loaded during graph resolution. +@@ -9,19 +9,25 @@ The ROUTE output filter is used to distribute a live file-based session using RO + The filter supports DASH and HLS inputs, ATSC3.0 signaling and generic ROUTE or DVB-MABR signaling. + + The filter is identified using the following URL schemes: +-* `atsc://`: session is a full ATSC 3.0 session +-* `route://IP:port`: session is a ROUTE session running on given multicast IP and port +-* `mabr://IP:port`: session is a DVB-MABR session using FLUTE running on given multicast IP and port ++ ++- `atsc://`: session is a full ATSC 3.0 session ++- `route://IP:port`: session is a ROUTE session running on given multicast IP and port ++- `mabr://IP:port`: session is a DVB-MABR session using FLUTE running on given multicast IP and port ++ + + The filter only accepts input PIDs of type `FILE`. ++ + - HAS Manifests files are detected by file extension and/or MIME types, and sent as part of the signaling bundle or as LCT object files for HLS child playlists. + - HAS Media segments are detected using the `OrigStreamType` property, and send as LCT object files using the DASH template string. + - A PID without `OrigStreamType` property set is delivered as a regular LCT object file (called `raw` hereafter). ++ + + For `raw` file PIDs, the filter will look for the following properties: +-* `ROUTEName`: set resource name. If not found, uses basename of URL +-* `ROUTECarousel`: set repeat period. If not found, uses [carousel](#carousel). If 0, the file is only sent once +-* `ROUTEUpload`: set resource upload time. If not found, uses [carousel](#carousel). If 0, the file will be sent as fast as possible. ++ ++- `ROUTEName`: set resource name. If not found, uses basename of URL ++- `ROUTECarousel`: set repeat period. If not found, uses [carousel](#carousel). If 0, the file is only sent once ++- `ROUTEUpload`: set resource upload time. If not found, uses [carousel](#carousel). If 0, the file will be sent as fast as possible. ++ + + When DASHing for ROUTE, DVB-MABR or single service ATSC, a file extension, either in [dst](#dst) or in [ext](#ext), may be used to identify the HAS session type (DASH or HLS). + Example +@@ -34,6 +40,7 @@ Example + ``` + "atsc://:ext=mpd", "route://IP:PORT/manifest.mpd" + ``` ++ + If multiple services with different formats are needed, you will need to explicit your filters: + Example + ``` +@@ -45,9 +52,11 @@ __Warning: When forwarding an existing DASH/HLS session, do NOT set any extensio + + By default, all streams in a service are assigned to a single multicast session, and differentiated by TSI (see [splitlct](#splitlct)). + TSI are assigned as follows: ++ + - signaling TSI is always 0 for ROUTE, 1 for DVB+Flute + - raw files are assigned TSI 1 and increasing number of TOI + - otherwise, the first PID found is assigned TSI 10, the second TSI 20 etc ... ++ + + Init segments and HLS child playlists are sent before each new segment, independently of [carousel](#carousel). + +@@ -58,13 +67,15 @@ By default, a single multicast IP is used for route sessions, each service will + The filter will look for `ROUTEIP` and `ROUTEPort` properties on the incoming PID. If not found, the default [ip](#ip) and [port](#port) will be used. + + ATSC 3.0 attributes set by using the following PID properties: +-* ATSC3ShortServiceName: set the short service name, maxiumu of 7 characters. If not found, `ServiceName` is checked, otherwise default to `GPAC`. +-* ATSC3MajorChannel: set major channel number of service. Default to 2. This really should be set and should not use the default. +-* ATSC3MinorChannel: set minor channel number of service. Default of 1. +-* ATSC3ServiceCat: set service category, default to 1 if not found. 1=Linear a/v service. 2=Linear audio only service. 3=App-based service. 4=ESg service. 5=EA service. 6=DRM service. +-* ATSC3hidden: set if service is hidden. Boolean true or false. Default of false. +-* ATSC3hideInGuide: set if service is hidden in ESG. Boolean true or false. Default of false. +-* ATSC3configuration: set service configuration. Choices are Broadcast or Broadband. Default of Broadcast ++ ++- ATSC3ShortServiceName: set the short service name, maxiumu of 7 characters. If not found, `ServiceName` is checked, otherwise default to `GPAC`. ++- ATSC3MajorChannel: set major channel number of service. Default to 2. This really should be set and should not use the default. ++- ATSC3MinorChannel: set minor channel number of service. Default of 1. ++- ATSC3ServiceCat: set service category, default to 1 if not found. 1=Linear a/v service. 2=Linear audio only service. 3=App-based service. 4=ESg service. 5=EA service. 6=DRM service. ++- ATSC3hidden: set if service is hidden. Boolean true or false. Default of false. ++- ATSC3hideInGuide: set if service is hidden in ESG. Boolean true or false. Default of false. ++- ATSC3configuration: set service configuration. Choices are Broadcast or Broadband. Default of Broadcast ++ + + # ROUTE mode + +@@ -84,33 +95,41 @@ The FLUTE session always uses a symbol length of [mtu](#mtu) minus 44 bytes. + + # Low latency mode + +-When using low-latency mode, the input media segments are not re-assembled in a single packet but are instead sent as they are received. ++When using low-latency mode (-llmode)(), the input media segments are not re-assembled in a single packet but are instead sent as they are received. + In order for the real-time scheduling of data chunks to work, each fragment of the segment should have a CTS and timestamp describing its timing. + If this is not the case (typically when used with an existing DASH session in file mode), the scheduler will estimate CTS and duration based on the stream bitrate and segment duration. The indicated bitrate is increased by [brinc](#brinc) percent for safety. + If this fails, the filter will trigger warnings and send as fast as possible. + _Note: The LCT objects are sent with no length (TOL header) assigned until the final segment size is known, potentially leading to a final 0-size LCT fragment signaling only the final size._ + ++In this mode, init segments and manifests are sent at the frequency given by property `ROUTECarousel` of the source PID if set or by (-carousel)[] option. ++Indicating `ROUTECarousel=0` will disable mid-segment repeating of manifests and init segments. ++ + # Examples + + Since the ROUTE filter only consumes files, it is required to insert: ++ + - the dash demultiplexer in file forwarding mode when loading a DASH session + - the dash multiplexer when creating a DASH session ++ + + Multiplexing an existing DASH session in route: + Example + ``` + gpac -i source.mpd dashin:forward=file -o route://225.1.1.0:6000/ +-``` ++``` ++ + Multiplexing an existing DASH session in atsc: + Example + ``` + gpac -i source.mpd dashin:forward=file -o atsc:// + ``` ++ + Dashing and multiplexing in route: + Example + ``` + gpac -i source.mp4 dasher:profile=live -o route://225.1.1.0:6000/manifest.mpd +-``` ++``` ++ + Dashing and multiplexing in route Low Latency: + Example + ``` +@@ -128,19 +147,32 @@ Example + ``` + gpac -i source.mpd -o route://225.1.1.0:6000/ + ``` ++ + This will only send the manifest file as a regular object and will not load the dash session. + Example + ``` + gpac -i source.mpd dashin:forward=file -o route://225.1.1.0:6000/manifest.mpd + ``` ++ + This will force the ROUTE multiplexer to only accept .mpd files, and will drop all segment files (same if [ext](#ext) is used). + Example + ``` + gpac -i source.mpd dasher -o route://225.1.1.0:6000/ + gpac -i source.mpd dasher -o route://225.1.1.0:6000/manifest.mpd + ``` ++ + These will demultiplex the input, re-dash it and send the output of the dasher to ROUTE + ++# Error simulation ++ ++It is possible to simulate errors with (-errsim)(). In this mode the LCT network sender implements a 2-state Markov chain: ++Example ++``` ++gpac -i source.mpd dasher -o route://225.1.1.0:6000/:errsim=1.0x98.0 ++``` ++ ++for a 1.0 percent chance to transition to error (not sending data over the network) and 98.0 to transition from error back to OK. ++ + + # Options + +@@ -155,9 +187,10 @@ These will demultiplex the input, re-dash it and send the output of the dasher t + __bsid__ (uint, default: _800_): ID for ATSC broadcast stream + __mtu__ (uint, default: _1472_): size of LCT MTU in bytes + __splitlct__ (enum, default: _off_): split mode for LCT channels +-* off: all streams are in the same LCT channel +-* type: each new stream type results in a new LCT channel +-* all: all streams are in dedicated LCT channel, the first stream being used for STSID signaling ++ ++- off: all streams are in the same LCT channel ++- type: each new stream type results in a new LCT channel ++- all: all streams are in dedicated LCT channel, the first stream being used for STSID signaling + + __korean__ (bool, default: _false_): use Korean version of ATSC 3.0 spec instead of US + __llmode__ (bool, default: _false_): use low-latency mode +@@ -166,9 +199,13 @@ These will demultiplex the input, re-dash it and send the output of the dasher t + __runfor__ (uint, default: _0_): run for the given time in ms + __nozip__ (bool, default: _false_): do not zip signaling package (STSID+manifest) + __furl__ (bool, default: _false_): inject full URLs of source service in the signaling instead of stripped server path ++__flute__ (bool, default: _true_): use flute for DVB-MABR object delivery + __csum__ (enum, default: _meta_): send MD5 checksum for DVB flute +-* no: do not send checksum +-* meta: only send checksum for configuration files, manifests and init segments +-* all: send checksum for everything ++ ++- no: do not send checksum ++- meta: only send checksum for configuration files, manifests and init segments ++- all: send checksum for everything + ++__recv_obj_timeout__ (uint, default: _50_): timeout period in ms before resorting to unicast repair ++__errsim__ (v2d, default: _0.0x100.0_): simulate errors using a 2-state Markov chain. Value are percentages + +diff --git a/docs/Filters/rtpin.md b/docs/Filters/rtpin.md +index 171b06fa..6cdb4eef 100644 +--- a/docs/Filters/rtpin.md ++++ b/docs/Filters/rtpin.md +@@ -1,20 +1,24 @@ + + +-# RTP/RTSP/SDP input ++# RTP/RTSP/SDP input {:data-level="all"} + + Register name used to load filter: __rtpin__ + This filter may be automatically loaded during graph resolution. + + This filter handles SDP/RTSP/RTP input reading. It supports: ++ + - SDP file reading + - RTP direct url through `rtp://` protocol scheme + - RTSP session processing through `rtsp://` and `satip://` protocol schemes ++ + + The filter produces either PIDs with media frames, or file PIDs with multiplexed data (e.g. MPEG-2 TS). + The filter will use: ++ + - RTSP over HTTP tunnel if server port is 80 or 8080 or if protocol scheme is `rtsph://`. + - RTSP over TLS if server port is 322 or if protocol scheme is `rtsps://`. + - RTSP over HTTPS tunnel if server port is 443 and if protocol scheme is `rtsph://`. ++ + + The filter will attempt reconnecting in TLS mode after two consecutive initial connection failures. + +@@ -36,9 +40,10 @@ The filter will attempt reconnecting in TLS mode after two consecutive initial c + __default_port__ (uint, default: _554_, minmax: 0-65535): set default RTSP port + __satip_port__ (uint, default: _1400_, minmax: 0-65535): set default port for SATIP + __transport__ (enum, default: _auto_): set RTP over RTSP +-* auto: set interleave on if HTTP tunnel is used, off otherwise and retry in interleaved mode if UDP timeout +-* tcp: enable RTP over RTSP +-* udp: disable RTP over RTSP ++ ++- auto: set interleave on if HTTP tunnel is used, off otherwise and retry in interleaved mode if UDP timeout ++- tcp: enable RTP over RTSP ++- udp: disable RTP over RTSP + + __udp_timeout__ (uint, default: _10000_): default timeout before considering UDP is down + __rtcp_timeout__ (uint, default: _5000_): default timeout for RTCP traffic in ms. After this timeout, playback will start out of sync. If 0 always wait for RTCP +@@ -49,6 +54,7 @@ The filter will attempt reconnecting in TLS mode after two consecutive initial c + __languages__ (str, default: _$GLANG_): user languages, by default solved from GPAC preferences + __stats__ (uint, default: _500_): update statistics to the user every given MS (0 disables reporting) + __max_sleep__ (sint, default: _1000_): set max sleep in milliseconds: ++ + - a negative value `-N` means to always sleep for `N` ms + - a positive value `N` means to sleep at most `N` ms but will sleep less if frame duration is shorter + +diff --git a/docs/Filters/rtpout.md b/docs/Filters/rtpout.md +index b0f6da8e..4facd8ff 100644 +--- a/docs/Filters/rtpout.md ++++ b/docs/Filters/rtpout.md +@@ -1,6 +1,6 @@ + + +-# RTP Streamer ++# RTP Streamer {:data-level="all"} + + Register name used to load filter: __rtpout__ + This filter may be automatically loaded during graph resolution. +@@ -19,17 +19,22 @@ Example + ``` + gpac -i src -o rtp://localhost:1234/:ext=ts + ``` ++ + This will indicate that the RTP streamer expects a MPEG-2 TS mux as an input. + + # RTP Packets + + The RTP packets produced have a maximum payload set by the [mtu](#mtu) option (IP packet will be MTU + 40 bytes of IP+UDP+RTP headers). + The real-time scheduling algorithm works as follows: ++ + - first initialize the clock by: +- - computing the smallest timestamp for all input PIDs +- - mapping this media time to the system clock ++ ++ - computing the smallest timestamp for all input PIDs ++ - mapping this media time to the system clock ++ + - determine the earliest packet to send next on each input PID, adding [delay](#delay) if any + - finally compare the packet mapped timestamp _TS_ to the system clock _SC_. When _TS_ - _SC_ is less than [tt](#tt), the RTP packets for the source packet are sent ++ + + The filter does not check for RTCP timeout and will run until all input PIDs reach end of stream. + +diff --git a/docs/Filters/rtspout.md b/docs/Filters/rtspout.md +index e3c74c3a..3259390d 100644 +--- a/docs/Filters/rtspout.md ++++ b/docs/Filters/rtspout.md +@@ -1,6 +1,6 @@ + + +-# RTSP Server ++# RTSP Server {:data-level="all"} + + Register name used to load filter: __rtspout__ + This filter may be automatically loaded during graph resolution. +@@ -24,6 +24,7 @@ Example + gpac -i source -o rtsp://myip/sessionname + gpac -i source -o rtsp://myip/sessionname + ``` ++ + In this mode, only one session is possible. It is possible to [loop](#loop) the input source(s). + + # Server mode +@@ -32,18 +33,22 @@ The filter can work as a regular RTSP server by specifying the [mounts](#mounts) + Example + ``` + gpac rtspout:mounts=mydir1,mydir2 +-``` ++``` ++ + In this case, content `RES` from any of the specified directory is exposed as `rtsp://SERVER/RES` + + The [mounts](#mounts) option can also specify access rule file(s), see `gpac -h creds`. When rules are used: ++ + - if a directory has a `name` rule, it will be used in the URL + - otherwise, the directory is directly available under server root `/` + - only read access and multicast rights are checked ++ + Example + ``` + [foodir] + name=bar + ``` ++ + Content `RES` of this directory is exposed as `rtsp://SERVER/bar/RES`. + + +@@ -54,6 +59,7 @@ Example + ``` + gpac -i rtsp://localhost/?pipe://mynamepipe&myfile.mp4 [dst filters] + ``` ++ + The server will resolve this URL in a new session containing streams from `myfile.mp4` and streams from pipe `mynamepipe`. + When setting [runfor](#runfor) in server mode, the server will exit at the end of the last session being closed. + +@@ -109,16 +115,18 @@ The tunnel conforms to QT specification, and only HTTP 1.0 and 1.1 tunnels are s + __loop__ (bool, default: _true_): loop all streams in session (not always possible depending on source type) + __dynurl__ (bool, default: _false_): allow dynamic service assembly + __mcast__ (enum, default: _off_): control multicast setup of a session +-* off: clients are never allowed to create a multicast +-* on: clients can create multicast sessions +-* mirror: clients can create a multicast session. Any later request to the same URL will use that multicast session ++ ++- off: clients are never allowed to create a multicast ++- on: clients can create multicast sessions ++- mirror: clients can create a multicast session. Any later request to the same URL will use that multicast session + + __quit__ (bool, default: _false_): exit server once first session is over (for test purposes) + __htun__ (bool, default: _true_): enable RTSP over HTTP tunnel + __trp__ (enum, default: _both_): transport mode +-* both: allow TCP or UDP traffic +-* udp: only allow UDP traffic +-* tcp: only allow TCP traffic ++ ++- both: allow TCP or UDP traffic ++- udp: only allow UDP traffic ++- tcp: only allow TCP traffic + + __cert__ (str): certificate file in PEM format to use for TLS mode + __pkey__ (str): private key file in PEM format to use for TLS mode +diff --git a/docs/Filters/safdmx.md b/docs/Filters/safdmx.md +index 0c44ef6f..5a337b2a 100644 +--- a/docs/Filters/safdmx.md ++++ b/docs/Filters/safdmx.md +@@ -1,6 +1,6 @@ + + +-# SAF demultiplexer ++# SAF demultiplexer {:data-level="all"} + + Register name used to load filter: __safdmx__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/scte35dec.md b/docs/Filters/scte35dec.md +index 3708fcfb..abc3b4ba 100644 +--- a/docs/Filters/scte35dec.md ++++ b/docs/Filters/scte35dec.md +@@ -1,6 +1,6 @@ + + +-# SCTE35 decoder ++# SCTE35 decoder {:data-level="all"} + + Register name used to load filter: __scte35dec__ + This filter is not checked during graph resolution and needs explicit loading. +@@ -12,5 +12,10 @@ following segmentation as hinted by the graph. + + # Options + ++__mode__ (enum, default: _23001-18_): mode to operate in ++ ++- 23001-18: extract SCTE-35 markers as emib/emeb boxes for Event Tracks ++- passthrough: pass-through mode adding cue start property on splice points ++ + __segdur__ (frac, default: _1/1_): segmentation duration in seconds. 0/0 flushes immediately for each input packet (beware of the bitrate overhead) + +diff --git a/docs/Filters/sockin.md b/docs/Filters/sockin.md +index bdc1a47c..701444a8 100644 +--- a/docs/Filters/sockin.md ++++ b/docs/Filters/sockin.md +@@ -1,6 +1,6 @@ + + +-# UDP/TCP input ++# UDP/TCP input {:data-level="all"} + + Register name used to load filter: __sockin__ + This filter may be automatically loaded during graph resolution. +@@ -9,18 +9,26 @@ This filter handles generic TCP and UDP input sockets. It can also probe for MPE + + Data format can be specified by setting either [ext](#ext) or [mime](#mime) options. If not set, the format will be guessed by probing the first data packet + ++ + - UDP sockets are used for source URLs formatted as `udp://NAME` + - TCP sockets are used for source URLs formatted as `tcp://NAME` + - UDP unix domain sockets are used for source URLs formatted as `udpu://NAME` + - TCP unix domain sockets are used for source URLs formatted as `tcpu://NAME` ++ + + When ports are specified in the URL and the default option separators are used (see `gpac -h doc`), the URL must either: ++ + - have a trailing '/', e.g. `udp://localhost:1234/[:opts]` + - use `gpac` separator, e.g. `udp://localhost:1234[:gpac:opts]` ++ + + When the socket is listening in keep-alive [ka](#ka) mode: ++ + - a single connection is allowed and a single output PID will be produced + - each connection close event will triger a pipeline flush ++ ++ ++On OSX with VM packet replay you will need to force multicast routing, e.g. `route add -net 239.255.1.4/32 -interface vboxnet0` + + + # Options +diff --git a/docs/Filters/sockout.md b/docs/Filters/sockout.md +index 401fba74..a31356de 100644 +--- a/docs/Filters/sockout.md ++++ b/docs/Filters/sockout.md +@@ -1,6 +1,6 @@ + + +-# UDP/TCP output ++# UDP/TCP output {:data-level="all"} + + Register name used to load filter: __sockout__ + This filter may be automatically loaded during graph resolution. +@@ -11,14 +11,18 @@ In server mode, the filter can be instructed to keep running at the end of the s + In server mode, the default behavior is to keep input packets when no more clients are connected; this can be adjusted though the [kp](#kp) option, however there is no realtime regulation of how fast packets are dropped. + If your sources are not real time, consider adding a real-time scheduler in the chain (cf reframer filter), or set the send [rate](#rate) option. + ++ + - UDP sockets are used for destinations URLs formatted as `udp://NAME` + - TCP sockets are used for destinations URLs formatted as `tcp://NAME` + - UDP unix domain sockets are used for destinations URLs formatted as `udpu://NAME` + - TCP unix domain sockets are used for destinations URLs formatted as `tcpu://NAME` ++ + + When ports are specified in the URL and the default option separators are used (see `gpac -h doc`), the URL must either: ++ + - have a trailing '/', e.g. `udp://localhost:1234/[:opts]` + - use `gpac` escape, e.g. `udp://localhost:1234[:gpac:opts]` ++ + + The socket output can be configured to drop or revert packet order for test purposes. + A window size in packets is specified as the drop/revert fraction denominator, and the index of the packet to drop/revert is given as the numerator/ +@@ -26,12 +30,14 @@ If the numerator is 0, a packet is randomly chosen in that window. + Example + ``` + :pckd=4/10 +-``` ++``` ++ + This drops every 4th packet of each 10 packet window. + Example + ``` + :pckr=0/100 + ``` ++ + This reverts the send order of one random packet in each 100 packet window. + + +diff --git a/docs/Filters/svgplay.md b/docs/Filters/svgplay.md +index 261b50aa..b5c5098b 100644 +--- a/docs/Filters/svgplay.md ++++ b/docs/Filters/svgplay.md +@@ -1,6 +1,6 @@ + + +-# SVG loader ++# SVG loader {:data-level="all"} + + Register name used to load filter: __svgplay__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/theoradec.md b/docs/Filters/theoradec.md +index 64fa9f2f..de053436 100644 +--- a/docs/Filters/theoradec.md ++++ b/docs/Filters/theoradec.md +@@ -1,6 +1,6 @@ + + +-# Theora decoder ++# Theora decoder {:data-level="all"} + + Register name used to load filter: __theoradec__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/thumbs.md b/docs/Filters/thumbs.md +index 4441f925..46894f30 100644 +--- a/docs/Filters/thumbs.md ++++ b/docs/Filters/thumbs.md +@@ -1,9 +1,9 @@ + + +-# Thumbnail collection generator ++# Thumbnail collection generator {:data-level="all"} + + Register name used to load filter: __thumbs__ +-This is a JavaScript filter, not checked during graph resolution and needs explicit loading. ++This is a JavaScript filter. It is not checked during graph resolution and needs explicit loading. + Author: GPAC team + + This filter generates screenshots from a video stream. +@@ -21,27 +21,31 @@ If a single image per output frame is used, the default value for [snap](#snap) + Otherwise, the default value for [snap](#snap) is 1 second and for [scale](#scale) is 10. + + A single line of text can be inserted over each frame. Predefined keywords can be used in input text, identified as `$KEYWORD$`: +-* ts: replaced by packet timestamp +-* timescale: replaced by PID timescale +-* time: replaced by packet time as HH:MM:SS.ms +-* cpu: replaced by current CPU usage of process +-* mem: replaced by current memory usage of process +-* version: replaced by GPAC version +-* fversion: replaced by GPAC full version +-* mae: replaced by Mean Absolute Error with previous frame +-* mse: replaced by Mean Square Error with previous frame +-* P4CC, PropName: replaced by corresponding PID property ++ ++- ts: replaced by packet timestamp ++- timescale: replaced by PID timescale ++- time: replaced by packet time as HH:MM:SS.ms ++- cpu: replaced by current CPU usage of process ++- mem: replaced by current memory usage of process ++- version: replaced by GPAC version ++- fversion: replaced by GPAC full version ++- mae: replaced by Mean Absolute Error with previous frame ++- mse: replaced by Mean Square Error with previous frame ++- P4CC, PropName: replaced by corresponding PID property ++ + + Example + ``` + gpac -i src reframer:saps=1 thumbs:snap=30:grid=6x30 -o dump/$num$.png +-``` ++``` ++ + This will generate images from key-frames only, inserting one image every 30 seconds. Using key-frame filtering is much faster but may give unexpected results if there are not enough key-frames in the source. + + Example + ``` + gpac -i src thumbs:snap=0:grid=5x5 -o dump/$num$.png + ``` ++ + This will generate one image containing 25 frames every second at 25 fps. + + If a single image per output frame is used and the scaling factor is 1, the input packet is reused as input with text and graphics overlaid. +@@ -50,6 +54,7 @@ Example + ``` + gpac -i src thumbs:grid=1x1:txt='Frame $time$' -o dump/$num$.png + ``` ++ + This will inject text over each frame and keep timing and other packet properties. + + A json output can be specified in input [list](#list) to let applications retrieve frame position in output image from its timing. +@@ -63,8 +68,10 @@ If both [mae](#mae) and [mse](#mse) thresholds are not 0, the frame is added if + For both metrics, a value of 0 means all pixels are the same, a value of 100 means all pixels have 100% intensity difference (e.g. black versus white). + + The scene detection is performed after the [snap](#snap) filtering and uses: ++ + - the previous frame in the stream, whether it was added or not, if [scref](#scref) is not set, + - the last added frame otherwise. ++ + + Typical thresholds for scene cut detection are 14 to 20 for [mae](#mae) and 5 to 7 for [mse](#mse). + +diff --git a/docs/Filters/tileagg.md b/docs/Filters/tileagg.md +index 9f2cb7dc..1d8c07ed 100644 +--- a/docs/Filters/tileagg.md ++++ b/docs/Filters/tileagg.md +@@ -1,6 +1,6 @@ + + +-# HEVC tile aggregator ++# HEVC tile aggregator {:data-level="all"} + + Register name used to load filter: __tileagg__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/tilesplit.md b/docs/Filters/tilesplit.md +index 5fab36c7..ef4e934c 100644 +--- a/docs/Filters/tilesplit.md ++++ b/docs/Filters/tilesplit.md +@@ -1,6 +1,6 @@ + + +-# HEVC tile bitstream splitter ++# HEVC tile bitstream splitter {:data-level="all"} + + Register name used to load filter: __tilesplit__ + This filter is not checked during graph resolution and needs explicit loading. +@@ -10,8 +10,10 @@ The filter will move to passthrough mode if the bitstream is not tiled. + If the `Bitrate` property is set on the input PID, the output tile PIDs will have a bitrate set to `(Bitrate - 10k)/nb_opids`, 10 kbps being reserved for the base. + + Each tile PID will be assigned the following properties: +-* `ID`: equal to the base PID ID (same as input) plus the 1-based index of the tile in raster scan order. +-* `TileID`: equal to the 1-based index of the tile in raster scan order. ++ ++- `ID`: equal to the base PID ID (same as input) plus the 1-based index of the tile in raster scan order. ++- `TileID`: equal to the 1-based index of the tile in raster scan order. ++ + + __Warning: The filter does not check if tiles are independently-coded (MCTS) !__ + +diff --git a/docs/Filters/tssplit.md b/docs/Filters/tssplit.md +index 21cd73d0..adc3da25 100644 +--- a/docs/Filters/tssplit.md ++++ b/docs/Filters/tssplit.md +@@ -1,6 +1,6 @@ + + +-# MPEG Transport Stream splitter ++# MPEG Transport Stream splitter {:data-level="all"} + + Register name used to load filter: __tssplit__ + This filter is not checked during graph resolution and needs explicit loading. +diff --git a/docs/Filters/ttml2srt.md b/docs/Filters/ttml2srt.md +index 41d05316..a20fbf15 100644 +--- a/docs/Filters/ttml2srt.md ++++ b/docs/Filters/ttml2srt.md +@@ -1,6 +1,6 @@ + + +-# TTML to SRT ++# TTML to SRT {:data-level="all"} + + Register name used to load filter: __ttml2srt__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/ttml2vtt.md b/docs/Filters/ttml2vtt.md +index e8cff790..49d5db9b 100644 +--- a/docs/Filters/ttml2vtt.md ++++ b/docs/Filters/ttml2vtt.md +@@ -1,6 +1,6 @@ + + +-# TTML to WebVTT ++# TTML to WebVTT {:data-level="all"} + + Register name used to load filter: __ttml2vtt__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/ttmldec.md b/docs/Filters/ttmldec.md +index a531f92f..88cb1b85 100644 +--- a/docs/Filters/ttmldec.md ++++ b/docs/Filters/ttmldec.md +@@ -1,6 +1,6 @@ + + +-# TTML decoder ++# TTML decoder {:data-level="all"} + + Register name used to load filter: __ttmldec__ + This filter may be automatically loaded during graph resolution. +@@ -10,9 +10,11 @@ The scene graph creation is done through JavaScript. + The filter options are used to override the JS global variables of the TTML renderer. + + In stand-alone rendering (no associated video), the filter will use: ++ + - `Width` and `Height` properties of input pid if any + - otherwise, `osize` option of compositor if set + - otherwise, [txtw](#txtw) and [txth](#txth) ++ + + + # Options +@@ -22,9 +24,10 @@ In stand-alone rendering (no associated video), the filter will use: + __fontSize__ (flt, default: _20_, updatable): font size + __color__ (str, default: _white_, updatable): text color + __valign__ (enum, default: _bottom_, updatable): vertical alignment +-* bottom: align text at bottom of text area +-* center: align text at center of text area +-* top: align text at top of text area ++ ++- bottom: align text at bottom of text area ++- center: align text at center of text area ++- top: align text at top of text area + + __lineSpacing__ (flt, default: _1.0_, updatable): line spacing as scaling factor to font size + __txtw__ (uint, default: _400_): default width in standalone rendering +diff --git a/docs/Filters/ttmlmerge.md b/docs/Filters/ttmlmerge.md +new file mode 100644 +index 00000000..5d0341ac +--- /dev/null ++++ b/docs/Filters/ttmlmerge.md +@@ -0,0 +1,11 @@ ++ ++ ++# TTML sample merger {:data-level="all"} ++ ++Register name used to load filter: __ttmlmerge__ ++This filter may be automatically loaded during graph resolution. ++ ++Merge input samples into a single TTML sample. Merging restarts at the start of DASH segments. ++ ++No options ++ +diff --git a/docs/Filters/ttxtdec.md b/docs/Filters/ttxtdec.md +index 7e905b40..04e090a4 100644 +--- a/docs/Filters/ttxtdec.md ++++ b/docs/Filters/ttxtdec.md +@@ -1,6 +1,6 @@ + + +-# TTXT/TX3G decoder ++# TTXT/TX3G decoder {:data-level="all"} + + Register name used to load filter: __ttxtdec__ + This filter may be automatically loaded during graph resolution. +@@ -9,9 +9,11 @@ This filter decodes TTXT/TX3G streams into a BIFS scene graph of the compositor + The TTXT documentation is available at https://wiki.gpac.io/xmlformats/TTXT-Format-Documentation + + In stand-alone rendering (no associated video), the filter will use: ++ + - `Width` and `Height` properties of input pid if any + - otherwise, `osize` option of compositor if set + - otherwise, [txtw](#txtw) and [txth](#txth) ++ + + + # Options +diff --git a/docs/Filters/tx3g2srt.md b/docs/Filters/tx3g2srt.md +index a35e783b..33c5b5e2 100644 +--- a/docs/Filters/tx3g2srt.md ++++ b/docs/Filters/tx3g2srt.md +@@ -1,6 +1,6 @@ + + +-# TX3G to SRT ++# TX3G to SRT {:data-level="all"} + + Register name used to load filter: __tx3g2srt__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/tx3g2ttml.md b/docs/Filters/tx3g2ttml.md +index 696245bb..d0cc1676 100644 +--- a/docs/Filters/tx3g2ttml.md ++++ b/docs/Filters/tx3g2ttml.md +@@ -1,6 +1,6 @@ + + +-# TX3G to TTML ++# TX3G to TTML {:data-level="all"} + + Register name used to load filter: __tx3g2ttml__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/tx3g2vtt.md b/docs/Filters/tx3g2vtt.md +index 68a491da..0843b721 100644 +--- a/docs/Filters/tx3g2vtt.md ++++ b/docs/Filters/tx3g2vtt.md +@@ -1,6 +1,6 @@ + + +-# TX3G to WebVTT ++# TX3G to WebVTT {:data-level="all"} + + Register name used to load filter: __tx3g2vtt__ + This filter may be automatically loaded during graph resolution. +diff --git a/docs/Filters/txtin.md b/docs/Filters/txtin.md +index 12d0497e..950cc1b2 100644 +--- a/docs/Filters/txtin.md ++++ b/docs/Filters/txtin.md +@@ -1,24 +1,29 @@ + + +-# Subtitle loader ++# Subtitle loader {:data-level="all"} + + Register name used to load filter: __txtin__ + This filter may be automatically loaded during graph resolution. + + This filter reads subtitle data from input PID to produce subtitle frames on a single PID. + The filter supports the following formats: +-* SRT: https://en.wikipedia.org/wiki/SubRip +-* WebVTT: https://www.w3.org/TR/webvtt1/ +-* TTXT: https://wiki.gpac.io/xmlformats/TTXT-Format-Documentation +-* QT 3GPP Text XML (TexML): Apple QT6, likely deprecated +-* TTML: https://www.w3.org/TR/ttml2/ +-* SUB: one subtitle per line formatted as `{start_frame}{end_frame}text` +-* SSA (Substation Alpha): basic parsing support for common files ++ ++- SRT: https://en.wikipedia.org/wiki/SubRip ++- WebVTT: https://www.w3.org/TR/webvtt1/ ++- TTXT: https://wiki.gpac.io/xmlformats/TTXT-Format-Documentation ++- QT 3GPP Text XML (TexML): Apple QT6, likely deprecated ++- TTML: https://www.w3.org/TR/ttml2/ ++- SUB: one subtitle per line formatted as `{start_frame}{end_frame}text` ++- SSA (Substation Alpha): basic parsing support for common files ++ + + Input files must be in UTF-8 or UTF-16 format, with or without BOM. The internal frame format is: +-* WebVTT (and srt if desired): ISO/IEC 14496-30 VTT cues +-* TTML: ISO/IEC 14496-30 XML subtitles +-* Others: 3GPP/QT Timed Text ++ ++- WebVTT (and srt if desired): ISO/IEC 14496-30 VTT cues ++- TTML: ISO/IEC 14496-30 XML subtitles ++- stxt and sbtt: ISO/IEC 14496-30 text stream and text subtitles ++- Others: 3GPP/QT Timed Text ++ + + # TTML Support + +@@ -26,17 +31,21 @@ If [ttml_split](#ttml_split) option is set, the TTML document is split in indepe + Empty periods in TTML will result in empty TTML documents or will be skipped if [no_empty](#no_empty) option is set. + + The first sample has a CTS assigned as indicated by [ttml_cts](#ttml_cts): ++ + - a numerator of -2 indicates the first CTS is 0 + - a numerator of -1 indicates the first CTS is the first active time in document + - a numerator >= 0 indicates the CTS to use for first sample ++ + + When TTML splitting is disabled, the duration of the TTML sample is given by [ttml_dur](#ttml_dur) if not 0, or set to the document duration + + By default, media resources are kept as declared in TTML2 documents. + + [ttml_embed](#ttml_embed) can be used to embed inside the TTML sample the resources in `` or ``: ++ + - for ``, ``, `
    - - - - \ No newline at end of file From d9687efebf399682a8747b171da846a831f9d2d0 Mon Sep 17 00:00:00 2001 From: PierreDillard <7gaspard77@gmail.com> Date: Tue, 24 Sep 2024 11:38:29 +0200 Subject: [PATCH 21/28] fix nav-items display --- docs/stylesheets/levels.css | 4 ++-- mkdocs.yml | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/stylesheets/levels.css b/docs/stylesheets/levels.css index 31342bc6..0c716a70 100644 --- a/docs/stylesheets/levels.css +++ b/docs/stylesheets/levels.css @@ -95,6 +95,6 @@ margin-right: 5px; display: none; } -.md-nav__item--nested > .md-nav__link { +/* .md-nav__item--nested > .md-nav__link { display: inline-block; -} \ No newline at end of file +} */ \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 3fe6308b..7973485d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -70,7 +70,6 @@ theme: - search.highlight - search.share - search.suggest - - search repo_url: https://github.com/gpac/gpac/ plugins: - search @@ -216,6 +215,7 @@ nav: - bsrw: Filters/bsrw.md - bssplit: Filters/bssplit.md - btplay: Filters/btplay.md + - ccdec: Filters/ccdec.md - cdcrypt: Filters/cdcrypt.md - cecrypt: Filters/cecrypt.md - compositor: Filters/compositor.md @@ -229,6 +229,7 @@ nav: - faad: Filters/faad.md - ffavf: Filters/ffavf.md - ffavin: Filters/ffavin.md + - ffbsf: Filters/ffbsf.md - ffdec: Filters/ffdec.md - ffdmx: Filters/ffdmx.md - ffenc: Filters/ffenc.md @@ -258,6 +259,7 @@ nav: - mcdec: Filters/mcdec.md - mp4dmx: Filters/mp4dmx.md - mp4mx: Filters/mp4mx.md + - mpeghdec: Filters/mpeghdec.md - nhmlr: Filters/nhmlr.md - nhmlw: Filters/nhmlw.md - nhntr: Filters/nhntr.md @@ -266,6 +268,7 @@ nav: - odfdec: Filters/odfdec.md - oggdmx: Filters/oggdmx.md - oggmx: Filters/oggmx.md + - ohevcdec: Filters/ohevcdec.md - osvcdec: Filters/osvcdec.md - pin: Filters/pin.md - pngenc: Filters/pngenc.md @@ -334,6 +337,7 @@ nav: - vobsubdmx: Filters/vobsubdmx.md - vorbisdec: Filters/vorbisdec.md - vout: Filters/vout.md + - vtbdec: Filters/vtbdec.md - vtt2tx3g: Filters/vtt2tx3g.md - vttdec: Filters/vttdec.md - writegen: Filters/writegen.md @@ -392,6 +396,4 @@ nav: - XML Binary Format: xmlformats/XML-Binary.md - TTXT Format: xmlformats/TTXT-Format-Documentation.md - NHML format: xmlformats/NHML-Format.md - - NHNT format: xmlformats/NHNT-Format.md - - \ No newline at end of file + - NHNT format: xmlformats/NHNT-Format.md \ No newline at end of file From 38a16432bbd5f01b4b0207d7f69a092d4f53ab15 Mon Sep 17 00:00:00 2001 From: PierreDillard <7gaspard77@gmail.com> Date: Tue, 24 Sep 2024 12:24:39 +0200 Subject: [PATCH 22/28] Fix sidebar collapse icons for navigation - Resolved issues with the collapse icons on the left sidebar. - Improved the navigation behavior related to icon collapsing. --- docs/stylesheets/toggle.css | 56 +++++++------------------------------ mkdocs.yml | 1 - 2 files changed, 10 insertions(+), 47 deletions(-) diff --git a/docs/stylesheets/toggle.css b/docs/stylesheets/toggle.css index a6951a1d..588704e7 100644 --- a/docs/stylesheets/toggle.css +++ b/docs/stylesheets/toggle.css @@ -1,28 +1,26 @@ -@media screen and (max-width: 76.25em) { - .toggle-btn, - .toggle-btn i{ - display: none; - } -} @media screen and (min-width: 76.25em) { .md-nav--secondary .md-nav__title .md-nav__icon { display: block; } -} -@media screen and (min-width: 76.25em) { +} + @media screen and (min-width: 76.25em) { .md-nav__icon { transition: background-color .25s; - width: auto; + } -} - +} @media screen and (min-width: 76.25em) { +label[for="__toc"] .md-nav__icon.md-icon { + width:auto;} +} + + @media screen and (min-width: 76.25em) { .md-nav__icon:hover { background-color: transparent; } -} +} .toggle-btn { @@ -39,37 +37,3 @@ display:none; } - [data-md-color-scheme="slate"] .toggle-btn:hover{ - background-color: #3a3a3a; - - } - -.toggle-btn:hover { - - background-color: #dddcdc; - -} - -.toggle-btn i { - color: #d94412e0; - transition: transform 0.3s ease; - font-size: 18px; -} - -.hidden { - opacity: 0; - visibility: hidden; - transition: opacity 0.5s ease, visibility 0.5s ease; -} - -.visible { - opacity: 1; - visibility: visible; - transition: opacity 0.5s ease, visibility 0.5s ease; -} - - - -.span-toggle { - margin-right: 4px; -} \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 7973485d..f7571b64 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -65,7 +65,6 @@ theme: - navigation.tabs - navigation.tracking - navigation.indexes - - navigation.sections - toc.follow - search.highlight - search.share From 722d92b3ea06278b17705ccb334300ba4fbf9ff0 Mon Sep 17 00:00:00 2001 From: PierreDillard <7gaspard77@gmail.com> Date: Wed, 25 Sep 2024 15:45:42 +0200 Subject: [PATCH 23/28] Updated modal to open on hover instead of click - Changed the modal behavior to trigger on mouse hover rather than on click. --- docs/javascripts/fetchFunctions.js | 6 ++-- docs/javascripts/keywordsDisplay.js | 9 +++-- docs/javascripts/modalFunctions.js | 55 +++++++++++++++++++++-------- docs/stylesheets/modal.css | 11 +++++- mkdocs.yml | 3 +- overrides/base.html | 6 ++-- 6 files changed, 65 insertions(+), 25 deletions(-) diff --git a/docs/javascripts/fetchFunctions.js b/docs/javascripts/fetchFunctions.js index 7242954e..5250a26a 100644 --- a/docs/javascripts/fetchFunctions.js +++ b/docs/javascripts/fetchFunctions.js @@ -19,7 +19,8 @@ function fetchKeywords(currentPageMdPath, cachedKeywords, cachedDefinitions) { .catch(error => console.error('Error fetching keywords:', error)); } -function fetchDefinitions(keyword, cachedDefinitions) { +function fetchDefinitions(keyword, cachedDefinitions,event) { + fetch('/data/keywords.json') .then(response => response.json()) .then(data => { @@ -27,9 +28,10 @@ function fetchDefinitions(keyword, cachedDefinitions) { if (definition) { cachedDefinitions[keyword] = definition; setCache('definitionsCache', cachedDefinitions); - openModal(keyword, definition); + openModal(keyword, definition, event); } else { console.error('Definition not found for keyword:', keyword); + openModal(keyword, { description: 'Definition not found' }, event); } }) .catch(error => console.error('Error fetching definition:', error)); diff --git a/docs/javascripts/keywordsDisplay.js b/docs/javascripts/keywordsDisplay.js index e530fd69..d709b1ed 100644 --- a/docs/javascripts/keywordsDisplay.js +++ b/docs/javascripts/keywordsDisplay.js @@ -25,14 +25,17 @@ function displayKeywords(keywords, cachedDefinitions, allDefinitions, selectedLe a.textContent = keyword; a.className = sizes[index % sizes.length] + ' ' + colors[index % colors.length]; - a.addEventListener('click', function (event) { + a.addEventListener('mouseenter', function (event) { event.preventDefault(); + clearTimeout(closeModalTimer); if (cachedDefinitions[keyword]) { - openModal(keyword, cachedDefinitions[keyword]); + openModal(keyword, cachedDefinitions[keyword], event); } else { - fetchDefinitions(keyword, cachedDefinitions); + fetchDefinitions(keyword, cachedDefinitions, event); } }); + + a.addEventListener('mouseleave', startCloseModalTimer); li.appendChild(a); wordCloudList.appendChild(li); diff --git a/docs/javascripts/modalFunctions.js b/docs/javascripts/modalFunctions.js index 3c158205..32adf6a1 100644 --- a/docs/javascripts/modalFunctions.js +++ b/docs/javascripts/modalFunctions.js @@ -1,43 +1,69 @@ -function openModal(keyword, definition) { +let closeModalTimer; + +function keepModalOpen() { + clearTimeout(closeModalTimer); +} + +function startCloseModalTimer() { + closeModalTimer = setTimeout(closeModal, 300); +} +function openModal(keyword, definition, event = null) { const modal = document.getElementById("modal"); const modalTitle = document.getElementById("modal-title"); const modalDefinition = document.getElementById("modal-definition"); const modalLink = document.getElementById("modal-link"); - - + + if (!modalTitle || !modalDefinition || !modalLink) { + console.error('Modal elements not found'); + return; + } + if (modalTitle && modalDefinition && modalLink) { - let descriptionText; + let descriptionText = 'Definition not vailable'; if (typeof definition === 'string') { descriptionText = definition; } else if (definition && typeof definition === 'object' && definition.description) { descriptionText = definition.description; - } else { - descriptionText = 'Definition not available'; - } + } const glossaryPageUrl = `${ window.location.origin }/glossary/${keyword.toLowerCase()}/`; modalTitle.textContent = keyword; modalDefinition.textContent = descriptionText; - modalLink.href = `${window.location.origin}/glossary/${keyword.toLowerCase()}/`; + modalLink.href = glossaryPageUrl; + + modal.style.display = "block"; + modal.classList.remove("hidden"); modal.style.display = "block"; - modalLink.classList.remove("hidden"); - modalLink.href = `${window.location.origin}/glossary/${keyword.toLowerCase()}/`; - modalLink.href = glossaryPageUrl; - modal.classList.remove("hidden"); - modal.style.display = "block"; - modalLink.classList.remove("hidden"); + modalLink.classList.remove("hidden"); } else { console.error('Modal elements not found'); } + setTimeout(() => { + modal.classList.add("visible"); + }, 10); + + + modal.addEventListener('mouseenter', keepModalOpen); + modal.addEventListener('mouseleave', startCloseModalTimer); } +function closeModal() { + const modal = document.getElementById("modal"); + if (modal) { + modal.classList.remove("visible"); + + setTimeout(() => { + modal.style.display = "none"; + }, 300); + } +} document.addEventListener("DOMContentLoaded", function () { document.getElementById("close-modal").addEventListener("click", function () { const modal = document.getElementById("modal"); @@ -54,4 +80,5 @@ document.addEventListener("DOMContentLoaded", function () { }); window.openModal = openModal; + window.closeModal = closeModal; }); \ No newline at end of file diff --git a/docs/stylesheets/modal.css b/docs/stylesheets/modal.css index fe0804c6..42d08ef0 100644 --- a/docs/stylesheets/modal.css +++ b/docs/stylesheets/modal.css @@ -6,9 +6,18 @@ width: 100%; max-width: 400px; left: 50%; + opacity: 0; + visibility: hidden; + transition: opacity 0.3s ease, visibility 0.3s ease; + display: none; transform: translateX(-50%); } +.modal.visible { + opacity: 1; + visibility: visible; + display: block; +} .modal-content { background-color:#E0E0E0; border: 1px solid #e0e0e0; @@ -34,7 +43,7 @@ .modal-header h2 { margin: 0; - font-size: 1rem; + font-size: 0.8rem; font-weight: bold; color: #333; } diff --git a/mkdocs.yml b/mkdocs.yml index f7571b64..edd0ee2b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -13,7 +13,7 @@ extra_javascript: - javascripts/keywordsDisplay.js - javascripts/cache.js - javascripts/modalFunctions.js - - javascripts/search.js + @@ -27,7 +27,6 @@ extra_css: - stylesheets/collapse_section.css - stylesheets/feedback.css - stylesheets/levels.css - - stylesheets/search.css - stylesheets/settings.css - https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css theme: diff --git a/overrides/base.html b/overrides/base.html index 561f9c0f..ae49e175 100644 --- a/overrides/base.html +++ b/overrides/base.html @@ -44,7 +44,7 @@ {% endif %} {% endblock %} {% block styles %} - + {% if config.theme.palette %} {% set palette = config.theme.palette %} @@ -248,7 +248,7 @@ "base": base_url, "features": features, "translations": {}, - "search": "assets/javascripts/workers/search.b8dbb3d2.min.js" | url + "search": "assets/javascripts/workers/search.07f07601.min.js" | url } -%} {%- if config.extra.version -%} {%- set mike = config.plugins.get("mike") -%} @@ -279,7 +279,7 @@ {% endblock %} {% block scripts %} - + {% for script in config.extra_javascript %} From a7650d56f9a233a23f251ccc42cc1aba719072e5 Mon Sep 17 00:00:00 2001 From: PierreDillard <7gaspard77@gmail.com> Date: Thu, 26 Sep 2024 14:04:51 +0200 Subject: [PATCH 24/28] update modal position --- docs/images/ex_beginner.png | Bin 0 -> 129864 bytes docs/images/ex_expert.png | Bin 0 -> 90732 bytes docs/javascripts/modalFunctions.js | 2 +- docs/stylesheets/keyword-cloud.css | 12 ++++++------ docs/stylesheets/modal.css | 11 ++++++----- overrides/base.html | 6 +++--- 6 files changed, 16 insertions(+), 15 deletions(-) create mode 100644 docs/images/ex_beginner.png create mode 100644 docs/images/ex_expert.png diff --git a/docs/images/ex_beginner.png b/docs/images/ex_beginner.png new file mode 100644 index 0000000000000000000000000000000000000000..4cb8e86e3ad03f473d92abce440264a5c8549027 GIT binary patch literal 129864 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 literal 0 HcmV?d00001 diff --git a/docs/images/ex_expert.png b/docs/images/ex_expert.png new file mode 100644 index 0000000000000000000000000000000000000000..e8fec143a989261e7dedb4fbe2fbf0138d9fd382 GIT binary patch literal 90732 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 ZW literal 0 HcmV?d00001 diff --git a/docs/javascripts/modalFunctions.js b/docs/javascripts/modalFunctions.js index 32adf6a1..d7ff95e5 100644 --- a/docs/javascripts/modalFunctions.js +++ b/docs/javascripts/modalFunctions.js @@ -5,7 +5,7 @@ function keepModalOpen() { } function startCloseModalTimer() { - closeModalTimer = setTimeout(closeModal, 300); + closeModalTimer = setTimeout(closeModal, 400); } function openModal(keyword, definition, event = null) { diff --git a/docs/stylesheets/keyword-cloud.css b/docs/stylesheets/keyword-cloud.css index 792c22b6..b7e2f0f5 100644 --- a/docs/stylesheets/keyword-cloud.css +++ b/docs/stylesheets/keyword-cloud.css @@ -76,18 +76,18 @@ .words-cloud li { display: inline-block; padding: 0.25rem 0.5rem; - transition: transform 0.2s, color 0.2s; + transition: transform 0.3s, color 0.3s; } .words-cloud a { text-decoration: none; } -.words-cloud .size-1 { font-size: 10px; } -.words-cloud .size-2 { font-size: 12px; } -.words-cloud .size-3 { font-size: 14px; } -.words-cloud .size-4 { font-size: 16px; } -.words-cloud .size-5 { font-size: 18px; } +.words-cloud .size-1 { font-size: 8px; } +.words-cloud .size-2 { font-size: 10px; } +.words-cloud .size-3 { font-size: 12px; } +.words-cloud .size-4 { font-size: 14px; } +.words-cloud .size-5 { font-size: 16px; } .words-cloud .color-1 { color: var(--md-accent-fg-color); } .words-cloud .color-2 { color: #666; } diff --git a/docs/stylesheets/modal.css b/docs/stylesheets/modal.css index 42d08ef0..afd0beab 100644 --- a/docs/stylesheets/modal.css +++ b/docs/stylesheets/modal.css @@ -3,10 +3,11 @@ position: absolute; display: none; margin-top: 10px; - width: 100%; - max-width: 400px; - left: 50%; - opacity: 0; + width: 90%; + max-width: 300px; + left: 70%; + top:270%; + opacity: 0.5; visibility: hidden; transition: opacity 0.3s ease, visibility 0.3s ease; display: none; @@ -25,7 +26,7 @@ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); padding: 20px; width: 100%; - transition: opacity 0.25s ease; + transition: opacity 0.3s ease; } .modal-header { diff --git a/overrides/base.html b/overrides/base.html index ae49e175..561f9c0f 100644 --- a/overrides/base.html +++ b/overrides/base.html @@ -44,7 +44,7 @@ {% endif %} {% endblock %} {% block styles %} - + {% if config.theme.palette %} {% set palette = config.theme.palette %} @@ -248,7 +248,7 @@ "base": base_url, "features": features, "translations": {}, - "search": "assets/javascripts/workers/search.07f07601.min.js" | url + "search": "assets/javascripts/workers/search.b8dbb3d2.min.js" | url } -%} {%- if config.extra.version -%} {%- set mike = config.plugins.get("mike") -%} @@ -279,7 +279,7 @@ {% endblock %} {% block scripts %} - + {% for script in config.extra_javascript %} From 93a2acd9b7ef083e28242be6e01b390bc73f2157 Mon Sep 17 00:00:00 2001 From: PierreDillard <7gaspard77@gmail.com> Date: Fri, 27 Sep 2024 09:31:10 +0200 Subject: [PATCH 25/28] Adjust modal position for centered alignment - Moved the modal to the left of the screen. - Adjusted the modal to be horizontally centered as well. --- docs/stylesheets/modal.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/stylesheets/modal.css b/docs/stylesheets/modal.css index 42d08ef0..6731cb1f 100644 --- a/docs/stylesheets/modal.css +++ b/docs/stylesheets/modal.css @@ -5,7 +5,8 @@ margin-top: 10px; width: 100%; max-width: 400px; - left: 50%; + left: 100%; + top:145%; opacity: 0; visibility: hidden; transition: opacity 0.3s ease, visibility 0.3s ease; From 1f950234a10aedff718c032cc1715ff23db78caf Mon Sep 17 00:00:00 2001 From: PierreDillard <7gaspard77@gmail.com> Date: Mon, 30 Sep 2024 14:25:04 +0200 Subject: [PATCH 26/28] Add error handling to event listeners and modal functions - Wrap logic in try...catch blocks to handle unexpected errors --- docs/javascripts/domManipulation.js | 188 +++++++---- docs/javascripts/fetchFunctions.js | 131 +++++--- docs/javascripts/keywordsDisplay.js | 105 +++--- docs/javascripts/keywordsFinder.js | 45 ++- docs/javascripts/levels.js | 498 +++++++++++++++------------- docs/javascripts/main.js | 109 +++--- docs/javascripts/modalFunctions.js | 133 ++++---- 7 files changed, 686 insertions(+), 523 deletions(-) diff --git a/docs/javascripts/domManipulation.js b/docs/javascripts/domManipulation.js index 877476e7..a8c40ce9 100644 --- a/docs/javascripts/domManipulation.js +++ b/docs/javascripts/domManipulation.js @@ -23,120 +23,164 @@ function initializeGlossaryPage() { } function initializeCollapseSections() { - const articleInner = document.querySelector('.md-content__inner'); - const h1Element = articleInner.querySelector('h1'); - const feedbackForm = articleInner.querySelector('.md-feedback'); + try { + const articleInner = document.querySelector('.md-content__inner'); + if (!articleInner) throw new Error("Element with class 'md-content__inner' not found"); - if (h1Element && feedbackForm) { - createArticleContentDiv(h1Element, feedbackForm); - } + const h1Element = articleInner.querySelector('h1'); + const feedbackForm = articleInner.querySelector('.md-feedback'); + + if (h1Element && feedbackForm) { + createArticleContentDiv(h1Element, feedbackForm); + } + + const articleContent = document.querySelector('.article-content'); + if (!articleContent) throw new Error("Element with class 'article-content' not found"); - const articleContent = document.querySelector('.article-content'); - if (articleContent) { const h2Elements = articleContent.querySelectorAll('h2'); + if (!h2Elements.length) throw new Error("No

    elements found in 'article-content'"); + h2Elements.forEach(createCollapseSection); + } catch (error) { + console.error("Error initializing collapse sections:", error); } } function createArticleContentDiv(h1Element, feedbackForm) { - const articleContentDiv = document.createElement('div'); - articleContentDiv.classList.add('article-content'); - - const fragment = document.createDocumentFragment(); - let sibling = h1Element.nextElementSibling; - while (sibling && sibling !== feedbackForm) { - const nextSibling = sibling.nextElementSibling; - fragment.appendChild(sibling); - sibling = nextSibling; - } + try { + const articleContentDiv = document.createElement('div'); + articleContentDiv.classList.add('article-content'); + + const fragment = document.createDocumentFragment(); + let sibling = h1Element.nextElementSibling; + while (sibling && sibling !== feedbackForm) { + const nextSibling = sibling.nextElementSibling; + fragment.appendChild(sibling); + sibling = nextSibling; + } - articleContentDiv.appendChild(fragment); - h1Element.insertAdjacentElement('afterend', articleContentDiv); + articleContentDiv.appendChild(fragment); + h1Element.insertAdjacentElement('afterend', articleContentDiv); + } catch (error) { + console.error("Error creating article content div:", error); + } } function createCollapseSection(h2) { - const content = []; - let sibling = h2.nextElementSibling; - while (sibling && sibling.tagName !== 'H2') { - content.push(sibling); - sibling = sibling.nextElementSibling; - } - const noCollapse = h2.classList.contains('no-collapse'); - if (noCollapse) { - return; - } - const collapseSection = document.createElement('div'); - collapseSection.classList.add('collapse-section', 'active'); + try { + const content = []; + let sibling = h2.nextElementSibling; + while (sibling && sibling.tagName !== 'H2') { + content.push(sibling); + sibling = sibling.nextElementSibling; + } + if (content.length === 0) { + h2.classList.add('no-collapse'); + return; + } + const noCollapse = h2.classList.contains('no-collapse'); + if (noCollapse) { + return; + } + const collapseSection = document.createElement('div'); + collapseSection.classList.add('collapse-section', 'active'); - const collapseContent = document.createElement('div'); - collapseContent.classList.add('collapse-content'); - content.forEach(element => collapseContent.appendChild(element)); + const collapseContent = document.createElement('div'); + collapseContent.classList.add('collapse-content'); + content.forEach(element => collapseContent.appendChild(element)); - h2.parentNode.insertBefore(collapseSection, h2); - collapseSection.appendChild(h2); - collapseSection.appendChild(collapseContent); + h2.parentNode.insertBefore(collapseSection, h2); + collapseSection.appendChild(h2); + collapseSection.appendChild(collapseContent); - addCollapseIcon(h2); + addCollapseIcon(h2); - h2.addEventListener('click', function () { - toggleSection(collapseSection); - if (h2.dataset.level === 'all') { - handleAllSection(collapseSection, h2); - } - }); + h2.addEventListener('click', function () { + toggleSection(collapseSection); + if (h2.dataset.level === 'all') { + handleAllSection(collapseSection, h2); + } + }); - return collapseSection; + return collapseSection; + } catch (error) { + console.error("Error creating collapse section:", error); + } } function addCollapseIcon(h2) { - if (!h2.querySelector('.collapse-icon')) { - const collapseIcon = document.createElement('span'); - collapseIcon.classList.add('collapse-icon'); - collapseIcon.innerHTML = ''; - h2.appendChild(collapseIcon); + try { + if (!h2.querySelector('.collapse-icon')) { + const collapseIcon = document.createElement('span'); + collapseIcon.classList.add('collapse-icon'); + collapseIcon.innerHTML = ''; + h2.appendChild(collapseIcon); + } + } catch (error) { + console.error("Error adding collapse icon:", error); } } function toggleSection(section) { - section.classList.toggle('active'); + try { + section.classList.toggle('active'); + } catch (error) { + console.error("Error toggling section:", error); + } } function handleTOCLinks() { - const tocLinks = document.querySelectorAll('.md-nav__link'); - tocLinks.forEach(link => { - if (link && link.hasAttribute('href')) { - link.addEventListener('click', function(e) { - const hash = this.getAttribute('href'); - if (hash.startsWith('#')) { - openCollapsedSection(hash); + try { + const tocLinks = document.querySelectorAll('.md-nav__link'); + tocLinks.forEach(link => { + if (link && link.hasAttribute('href')) { + link.addEventListener('click', function(e) { + const hash = this.getAttribute('href'); + if (hash.startsWith('#')) { + openCollapsedSection(hash); + } + }); } }); + } catch (error) { + console.error("Error handling TOC links:", error); } - }); } function handleInitialHash() { - if (window.location.hash) { - openCollapsedSection(window.location.hash); + try { + if (window.location.hash) { + openCollapsedSection(window.location.hash); + } + } catch (error) { + console.error("Error handling initial hash:", error); } } function openCollapsedSection(hash) { - if (hash) { - const targetElement = document.querySelector(hash); - if (targetElement) { - const collapseSection = targetElement.closest('.collapse-section'); - if (collapseSection && !collapseSection.classList.contains('active')) { - collapseSection.classList.add('active'); + try { + if (hash) { + const targetElement = document.querySelector(hash); + if (targetElement) { + const collapseSection = targetElement.closest('.collapse-section'); + if (collapseSection && !collapseSection.classList.contains('active')) { + collapseSection.classList.add('active'); + } } } + } catch (error) { + console.error("Error opening collapsed section:", error); } } function handleAllSection(section, h2) { - if (section.classList.contains('active')) { - section.setAttribute('data-was-active', 'true'); - } else { - section.removeAttribute('data-was-active'); + try { + if (section.classList.contains('active')) { + section.setAttribute('data-was-active', 'true'); + } else { + section.removeAttribute('data-was-active'); + } + } catch (error) { + console.error("Error handling all section:", error); } } \ No newline at end of file diff --git a/docs/javascripts/fetchFunctions.js b/docs/javascripts/fetchFunctions.js index 5250a26a..0baf69af 100644 --- a/docs/javascripts/fetchFunctions.js +++ b/docs/javascripts/fetchFunctions.js @@ -1,61 +1,84 @@ function fetchKeywords(currentPageMdPath, cachedKeywords, cachedDefinitions) { - fetch('/data/keywords.json') - .then(response => { - if (!response.ok) { - throw new Error('Network response was not ok'); - } - return response.json(); - }) - .then(data => { - const allDefinitions = data.definitions; - const lexique = Object.keys(allDefinitions); - findKeywordsInContent(currentPageMdPath, lexique, (filteredKeywords) => { - cachedKeywords[currentPageMdPath] = filteredKeywords; - setCache('keywordsCache', cachedKeywords); - const selectedLevel = localStorage.getItem('userLevel') || 'beginner'; - displayKeywords(filteredKeywords, cachedDefinitions, allDefinitions, selectedLevel); - }); - }) - .catch(error => console.error('Error fetching keywords:', error)); + try { + fetch('/data/keywords.json') + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); + }) + .then(data => { + const allDefinitions = data.definitions; + const lexique = Object.keys(allDefinitions); + findKeywordsInContent(currentPageMdPath, lexique, (filteredKeywords) => { + cachedKeywords[currentPageMdPath] = filteredKeywords; + setCache('keywordsCache', cachedKeywords); + const selectedLevel = localStorage.getItem('userLevel') || 'beginner'; + displayKeywords(filteredKeywords, cachedDefinitions, allDefinitions, selectedLevel); + }); + }) + .catch(error => { + console.error('Error fetching keywords:', error); + }); + } catch (error) { + console.error('Unexpected error in fetchKeywords:', error); + } } -function fetchDefinitions(keyword, cachedDefinitions,event) { - - fetch('/data/keywords.json') - .then(response => response.json()) - .then(data => { - const definition = data.definitions[keyword]; - if (definition) { - cachedDefinitions[keyword] = definition; - setCache('definitionsCache', cachedDefinitions); - openModal(keyword, definition, event); - } else { - console.error('Definition not found for keyword:', keyword); - openModal(keyword, { description: 'Definition not found' }, event); - } - }) - .catch(error => console.error('Error fetching definition:', error)); +function fetchDefinitions(keyword, cachedDefinitions, event) { + try { + fetch('/data/keywords.json') + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); + }) + .then(data => { + const definition = data.definitions[keyword]; + if (definition) { + cachedDefinitions[keyword] = definition; + setCache('definitionsCache', cachedDefinitions); + openModal(keyword, definition, event); + } else { + console.error('Definition not found for keyword:', keyword); + openModal(keyword, { description: 'Definition not found' }, event); + } + }) + .catch(error => { + console.error('Error fetching definition:', error); + }); + } catch (error) { + console.error('Unexpected error in fetchDefinitions:', error); + } } -//Get the Markdown content function fetchMarkdownContent(currentPageMdPath) { - return fetch(currentPageMdPath) - .then(response => { - return response.text(); - }) - .then(htmlContent => { - const parser = new DOMParser(); - const doc = parser.parseFromString(htmlContent, 'text/html'); - const mdContent = doc.querySelector('.md-content[data-md-component="content"]'); - if (mdContent) { - return mdContent.textContent; - } else { - console.warn(`Content element not found in the parsed HTML`); - return ''; - } - }) - .catch(error => { - console.error('Error fetching markdown content:', error); - throw error; - }); + try { + return fetch(currentPageMdPath) + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.text(); + }) + .then(htmlContent => { + const parser = new DOMParser(); + const doc = parser.parseFromString(htmlContent, 'text/html'); + const mdContent = doc.querySelector('.md-content[data-md-component="content"]'); + if (mdContent) { + return mdContent.textContent; + } else { + console.warn('Content element not found in the parsed HTML'); + return ''; + } + }) + .catch(error => { + console.error('Error fetching markdown content:', error); + throw error; + }); + } catch (error) { + console.error('Unexpected error in fetchMarkdownContent:', error); + return Promise.reject(error); + } } \ No newline at end of file diff --git a/docs/javascripts/keywordsDisplay.js b/docs/javascripts/keywordsDisplay.js index d709b1ed..84183480 100644 --- a/docs/javascripts/keywordsDisplay.js +++ b/docs/javascripts/keywordsDisplay.js @@ -1,54 +1,65 @@ function displayKeywords(keywords, cachedDefinitions, allDefinitions, selectedLevel) { - const wordCloudElement = document.querySelector('.words-cloud'); - const wordCloudList = document.getElementById('dynamic-words-cloud'); - wordCloudList.innerHTML = ''; - - const sizes = ['size-1', 'size-2', 'size-3', 'size-4', 'size-5']; - const colors = ['color-1', 'color-2', 'color-3', 'color-4']; - - let displayedKeywordsCount = 0; - - const totalRelevantKeywords = keywords.filter(keyword => { - const definition = allDefinitions[keyword]; - return definition && (definition.level === selectedLevel || definition.level === 'all'); - }).length; - - keywords.forEach((keyword, index) => { - const definition = allDefinitions[keyword]; - - if (definition && (definition.level === selectedLevel || definition.level === 'all')) { - displayedKeywordsCount++; - - const li = document.createElement('li'); - const a = document.createElement('a'); - a.href = "#"; - a.textContent = keyword; - a.className = sizes[index % sizes.length] + ' ' + colors[index % colors.length]; - - a.addEventListener('mouseenter', function (event) { - event.preventDefault(); - clearTimeout(closeModalTimer); - if (cachedDefinitions[keyword]) { - openModal(keyword, cachedDefinitions[keyword], event); - } else { - fetchDefinitions(keyword, cachedDefinitions, event); + try { + const wordCloudElement = document.querySelector('.words-cloud'); + if (!wordCloudElement) throw new Error("Element with class 'words-cloud' not found"); + + const wordCloudList = document.getElementById('dynamic-words-cloud'); + if (!wordCloudList) throw new Error("Element with ID 'dynamic-words-cloud' not found"); + + wordCloudList.innerHTML = ''; + + const sizes = ['size-1', 'size-2', 'size-3', 'size-4', 'size-5']; + const colors = ['color-1', 'color-2', 'color-3', 'color-4']; + + let displayedKeywordsCount = 0; + + const totalRelevantKeywords = keywords.filter(keyword => { + const definition = allDefinitions[keyword]; + return definition && (definition.level === selectedLevel || definition.level === 'all'); + }).length; + + keywords.forEach((keyword, index) => { + try { + const definition = allDefinitions[keyword]; + if (definition && (definition.level === selectedLevel || definition.level === 'all')) { + displayedKeywordsCount++; + + const li = document.createElement('li'); + const a = document.createElement('a'); + a.href = "#"; + a.textContent = keyword; + a.className = sizes[index % sizes.length] + ' ' + colors[index % colors.length]; + + a.addEventListener('mouseenter', function (event) { + event.preventDefault(); + clearTimeout(closeModalTimer); + if (cachedDefinitions[keyword]) { + openModal(keyword, cachedDefinitions[keyword], event); + } else { + fetchDefinitions(keyword, cachedDefinitions, event); + } + }); + + a.addEventListener('mouseleave', startCloseModalTimer); + + li.appendChild(a); + wordCloudList.appendChild(li); } - }); - - a.addEventListener('mouseleave', startCloseModalTimer); + } catch (error) { + console.error(`Error processing keyword '${keyword}':`, error); + } + }); - li.appendChild(a); - wordCloudList.appendChild(li); + if (displayedKeywordsCount > 0) { + wordCloudElement.classList.remove('hidden'); + } else { + wordCloudElement.classList.add('hidden'); } - }); - if (displayedKeywordsCount > 0) { - wordCloudElement.classList.remove('hidden'); - } else { - wordCloudElement.classList.add('hidden'); - } - - if (displayedKeywordsCount < totalRelevantKeywords) { - console.warn(`Some relevant keywords (${totalRelevantKeywords - displayedKeywordsCount}) could not be displayed.`); + if (displayedKeywordsCount < totalRelevantKeywords) { + console.warn(`Some relevant keywords (${totalRelevantKeywords - displayedKeywordsCount}) could not be displayed.`); + } + } catch (error) { + console.error('Error displaying keywords:', error); } } \ No newline at end of file diff --git a/docs/javascripts/keywordsFinder.js b/docs/javascripts/keywordsFinder.js index 8a7c1a6b..3e1078bc 100644 --- a/docs/javascripts/keywordsFinder.js +++ b/docs/javascripts/keywordsFinder.js @@ -1,30 +1,49 @@ //delete links in markdown content function cleanMarkdownContent(content) { - return content.replace(/\[[^\]]*\]\([^)]*\)/g, ''); + try { + return content.replace(/\[[^\]]*\]\([^)]*\)/g, ''); + } catch (error) { + console.error('Error cleaning markdown content:', error); + return content; // Return the original content if an error occurs + } } function cleanWord(word) { - return word.replace(/[.,!?(){}[\]`-]/g, '').toUpperCase(); + try { + return word.replace(/[.,!?(){}[\]`-]/g, '').toUpperCase(); + } catch (error) { + console.error('Error cleaning word:', error); + return word; // Return the original word if an error occurs + } } function findKeywordsInContent(currentPageMdPath, lexique, callback) { fetchMarkdownContent(currentPageMdPath) .then(content => { - const cleanContent = cleanMarkdownContent(content); - const wordCounts = {}; + try { + const cleanContent = cleanMarkdownContent(content); + const wordCounts = {}; - const words = cleanContent.split(/\s+/); + const words = cleanContent.split(/\s+/); - words.forEach(word => { - const cleanedWord = cleanWord(word); - if (lexique.includes(cleanedWord)) { - wordCounts[cleanedWord] = (wordCounts[cleanedWord] || 0) + 1; - } - }); + words.forEach(word => { + try { + const cleanedWord = cleanWord(word); + if (lexique.includes(cleanedWord)) { + wordCounts[cleanedWord] = (wordCounts[cleanedWord] || 0) + 1; + } + } catch (error) { + console.error(`Error processing word '${word}':`, error); + } + }); - const filteredKeywords = Object.keys(wordCounts).filter(word => wordCounts[word] >= 2); + const filteredKeywords = Object.keys(wordCounts).filter(word => wordCounts[word] >= 2); - callback(filteredKeywords); + callback(filteredKeywords); + } catch (error) { + console.error('Error processing content:', error); + callback([]); // Return an empty array if an error occurs + } }) .catch(error => console.error('Error fetching markdown content:', error)); } \ No newline at end of file diff --git a/docs/javascripts/levels.js b/docs/javascripts/levels.js index bd2fb821..d254a3c9 100644 --- a/docs/javascripts/levels.js +++ b/docs/javascripts/levels.js @@ -1,4 +1,5 @@ -let levelSwitch, switchLabel, currentPageMdPath,collapseAllSwitch, settingsButton, settingsDropdown; +let levelSwitch, switchLabel, currentPageMdPath, collapseAllSwitch, settingsButton, settingsDropdown; + /** * Settings Management * This section contains functions related to initializing and managing user settings. @@ -8,22 +9,27 @@ let levelSwitch, switchLabel, currentPageMdPath,collapseAllSwitch, settingsButt * Initializes the settings functionality. * Sets up event listeners and loads saved preferences. */ - - function initializeSettings() { - collapseAllSwitch = document.getElementById('collapse-all-switch'); - settingsButton = document.querySelector('.md-header__settings-button'); - settingsDropdown = document.querySelector('.md-header__settings-dropdown'); + try { + collapseAllSwitch = document.getElementById('collapse-all-switch'); + settingsButton = document.querySelector('.md-header__settings-button'); + settingsDropdown = document.querySelector('.md-header__settings-dropdown'); - const savedLevel = localStorage.getItem('userLevel') || 'expert'; - const savedCollapseAll = localStorage.getItem('collapseAll') === 'true'; - collapseAllSwitch.checked = savedCollapseAll; - toggleAllSections(savedCollapseAll); + if (!collapseAllSwitch || !settingsButton || !settingsDropdown) { + throw new Error('Required elements for settings initialization not found'); + } - collapseAllSwitch.addEventListener('change', handleCollapseAllChange); - settingsButton.addEventListener('click', toggleSettingsDropdown); + const savedLevel = localStorage.getItem('userLevel') || 'expert'; + const savedCollapseAll = localStorage.getItem('collapseAll') === 'true'; + collapseAllSwitch.checked = savedCollapseAll; + toggleAllSections(savedCollapseAll); - document.addEventListener('click', closeSettingsDropdown); + collapseAllSwitch.addEventListener('change', handleCollapseAllChange); + settingsButton.addEventListener('click', toggleSettingsDropdown); + document.addEventListener('click', closeSettingsDropdown); + } catch (error) { + console.error('Error initializing settings:', error); + } } /** @@ -31,25 +37,37 @@ function initializeSettings() { * Updates localStorage and toggles all sections accordingly. */ function handleCollapseAllChange() { - const collapseAll = collapseAllSwitch.checked; - localStorage.setItem('collapseAll', collapseAll); - toggleAllSections(collapseAll); + try { + const collapseAll = collapseAllSwitch.checked; + localStorage.setItem('collapseAll', collapseAll); + toggleAllSections(collapseAll); + } catch (error) { + console.error('Error handling collapse all change:', error); + } } /** * Toggles the visibility of the settings dropdown. */ function toggleSettingsDropdown() { - settingsDropdown.classList.toggle('hidden'); + try { + settingsDropdown.classList.toggle('hidden'); + } catch (error) { + console.error('Error toggling settings dropdown:', error); + } } + /** * Closes the settings dropdown when clicking outside of it. * @param {Event} event - The click event */ - function closeSettingsDropdown(event) { - if (!settingsDropdown.contains(event.target) && !settingsButton.contains(event.target)) { - settingsDropdown.classList.add('hidden'); + try { + if (!settingsDropdown.contains(event.target) && !settingsButton.contains(event.target)) { + settingsDropdown.classList.add('hidden'); + } + } catch (error) { + console.error('Error closing settings dropdown:', error); } } @@ -58,14 +76,18 @@ function closeSettingsDropdown(event) { * @param {boolean} collapse - Whether to collapse or expand all sections */ function toggleAllSections(collapse) { - const sections = document.querySelectorAll('.collapse-section'); - sections.forEach(section => { - if (collapse) { - section.classList.remove('active'); - } else { - section.classList.add('active'); - } - }); + try { + const sections = document.querySelectorAll('.collapse-section'); + sections.forEach(section => { + if (collapse) { + section.classList.remove('active'); + } else { + section.classList.add('active'); + } + }); + } catch (error) { + console.error('Error toggling all sections:', error); + } } /** @@ -78,53 +100,58 @@ function toggleAllSections(collapse) { * Sets up event listeners and applies initial level settings. */ function initializeLevelManagement() { - levelSwitch = document.getElementById('level-switch'); - switchLabel = document.querySelector('.switch-label'); + try { + levelSwitch = document.getElementById('level-switch'); + switchLabel = document.querySelector('.switch-label'); - if (!levelSwitch || !switchLabel) { - console.error('Level switch or switch label not found'); - return; - } - const savedLevel = localStorage.getItem("userLevel") || "expert"; - - if (isSearchResultPage()) { - if (setTemporaryExpertMode()) { - filterContent('expert'); - updateTOCVisibility('expert'); - updateOptionsVisibility('expert'); + if (!levelSwitch || !switchLabel) { + throw new Error('Level switch or switch label not found'); } - } else { - if (revertFromTemporaryExpertMode()) { - filterContent('beginner'); - updateTOCVisibility('beginner'); - updateOptionsVisibility('beginner'); + + const savedLevel = localStorage.getItem("userLevel") || "expert"; + + if (isSearchResultPage()) { + if (setTemporaryExpertMode()) { + filterContent('expert'); + updateTOCVisibility('expert'); + updateOptionsVisibility('expert'); + } + } else { + if (revertFromTemporaryExpertMode()) { + filterContent('beginner'); + updateTOCVisibility('beginner'); + updateOptionsVisibility('beginner'); + } } - } - levelSwitch.checked = savedLevel === "expert"; - updateSwitchLabel(); - /* addLevelTags();*/ + levelSwitch.checked = savedLevel === "expert"; + updateSwitchLabel(); - filterContent(savedLevel); - updateTOCVisibility(savedLevel); - + filterContent(savedLevel); + updateTOCVisibility(savedLevel); - currentPageMdPath = getCurrentPageMdPath(); - levelSwitch.addEventListener("change", handleLevelChange); + currentPageMdPath = getCurrentPageMdPath(); + levelSwitch.addEventListener("change", handleLevelChange); + } catch (error) { + console.error('Error initializing level management:', error); + } } - /** * Gets the current page's Markdown path. * @returns {string} The current page's Markdown path */ - function getCurrentPageMdPath() { - let currentPagePath = window.location.pathname; - if (currentPagePath.endsWith("/")) { - currentPagePath = currentPagePath.slice(0, -1); + try { + let currentPagePath = window.location.pathname; + if (currentPagePath.endsWith("/")) { + currentPagePath = currentPagePath.slice(0, -1); + } + return currentPagePath.replace(".html", ".md"); + } catch (error) { + console.error('Error getting current page Markdown path:', error); + return ''; } - return currentPagePath.replace(".html", ".md"); } /** @@ -132,50 +159,38 @@ function getCurrentPageMdPath() { * Updates localStorage and applies new level settings. */ function handleLevelChange() { - const selectedLevel = levelSwitch.checked ? "expert" : "beginner"; - localStorage.setItem("userLevel", selectedLevel); - updateSwitchLabel(); - filterContent(selectedLevel); - updateTOCVisibility(selectedLevel); - updateOptionsVisibility(selectedLevel); // Add this line - - let cachedKeywords = getCache("keywordsCache"); - let cachedDefinitions = getCache("definitionsCache"); - fetchKeywords(currentPageMdPath, cachedKeywords, cachedDefinitions); - - document.dispatchEvent(new CustomEvent('userLevelChanged', { detail: selectedLevel })); + try { + const selectedLevel = levelSwitch.checked ? "expert" : "beginner"; + localStorage.setItem("userLevel", selectedLevel); + updateSwitchLabel(); + filterContent(selectedLevel); + updateTOCVisibility(selectedLevel); + updateOptionsVisibility(selectedLevel); + + let cachedKeywords = getCache("keywordsCache"); + let cachedDefinitions = getCache("definitionsCache"); + fetchKeywords(currentPageMdPath, cachedKeywords, cachedDefinitions); + + document.dispatchEvent(new CustomEvent('userLevelChanged', { detail: selectedLevel })); + } catch (error) { + console.error('Error handling level change:', error); + } } + /** * Updates the switch label text based on the current level. */ function updateSwitchLabel() { - if (switchLabel && levelSwitch) { // Vérification des variables - switchLabel.textContent = levelSwitch.checked ? "Expert" : "Beginner"; - } else { - console.error('switchLabel ou levelSwitch est undefined'); + try { + if (switchLabel && levelSwitch) { + switchLabel.textContent = levelSwitch.checked ? "Expert" : "Beginner"; + } else { + throw new Error('switchLabel or levelSwitch is undefined'); + } + } catch (error) { + console.error('Error updating switch label:', error); } } -/** - * Adds level tags to elements with data-level attribute. - */ - -/* function addLevelTags() { - const allContent = document.querySelectorAll("[data-level]"); - allContent.forEach((element) => { - const level = element.dataset.level; - if (level && level === 'basic') { - return} - if (level) { - const existingTag = element.querySelector(".level-tag"); - if (!existingTag) { - const tag = document.createElement("span"); - tag.className = `level-tag level-${level}`; - tag.textContent = level.charAt(0).toUpperCase() + level.slice(1); - element.insertBefore(tag, element.firstChild); - } - } - }); -} */ /** * Content Filtering @@ -186,197 +201,226 @@ function updateSwitchLabel() { * Checks if the current page is in the Howtos section. * @returns {boolean} True if the current page is in the Howtos section */ - function isHowtosSection() { - return window.location.pathname.includes('/Howtos/'); + try { + return window.location.pathname.includes('/Howtos/'); + } catch (error) { + console.error('Error checking if Howtos section:', error); + return false; + } } + /** * Filters content based on the user's level. * @param {string} level - The user's level ('expert' or 'beginner') */ function filterContent(level) { - const articleContent = document.querySelector('.article-content'); - if (!articleContent) return; - - const sections = articleContent.querySelectorAll('.collapse-section'); - - if (isHowtosSection()) { - const hasBeginnerSections = Array.from(sections).some(section => - section.querySelector('h2[data-level="beginner"]') + try { + const articleContent = document.querySelector('.article-content'); + if (!articleContent) return; + + const sections = articleContent.querySelectorAll('.collapse-section'); + + if (isHowtosSection()) { + const hasBeginnerSections = Array.from(sections).some(section => + section.querySelector('h2[data-level="beginner"]') ); sections.forEach(section => { - const h2Element = section.querySelector('h2'); - const sectionLevel = h2Element ? h2Element.dataset.level : null; - const isAllSection = sectionLevel === 'all'; - const span = section.querySelector('.level-tag'); - - if (level === 'expert' || isAllSection) { - handleExpertOrAllSection(section, span, level, isAllSection); - } else if (level === 'beginner') { - if (hasBeginnerSections) { - handleBeginnerSectionWithTags(section, sectionLevel, isAllSection, span); - } else { - handleBeginnerSectionWithoutTags(section, sectionLevel, isAllSection, span); - } + const h2Element = section.querySelector('h2'); + const sectionLevel = h2Element ? h2Element.dataset.level : null; + const isAllSection = sectionLevel === 'all'; + const span = section.querySelector('.level-tag'); + + if (level === 'expert' || isAllSection) { + handleExpertOrAllSection(section, span, level, isAllSection); + } else if (level === 'beginner') { + if (hasBeginnerSections) { + handleBeginnerSectionWithTags(section, sectionLevel, isAllSection, span); + } else { + handleBeginnerSectionWithoutTags(section, sectionLevel, isAllSection, span); } + } }); - } else { - // For sections that are not in "Howtos" section + } else { sections.forEach(section => { - section.classList.remove('hidden-level'); - const span = section.querySelector('.level-tag'); - if (span) span.style.display = 'none'; + section.classList.remove('hidden-level'); + const span = section.querySelector('.level-tag'); + if (span) span.style.display = 'none'; }); + } + } catch (error) { + console.error('Error filtering content:', error); } } -function handleBeginnerSectionWithTags( - section, - sectionLevel, - isAllSection, - span -) { - if (sectionLevel === "beginner" || isAllSection) { - section.classList.remove("hidden-level"); - if (span) { - span.style.display = sectionLevel === "beginner" ? "" : "none"; +function handleBeginnerSectionWithTags(section, sectionLevel, isAllSection, span) { + try { + if (sectionLevel === "beginner" || isAllSection) { + section.classList.remove("hidden-level"); + if (span) { + span.style.display = sectionLevel === "beginner" ? "" : "none"; + } + } else { + section.classList.add("hidden-level"); } - } else { - section.classList.add("hidden-level"); + } catch (error) { + console.error('Error handling beginner section with tags:', error); } } -function handleBeginnerSectionWithoutTags( - section, - sectionLevel, - isAllSection, - span -) { - section.classList.remove("hidden-level"); - if (span) { - span.style.display = "none"; +function handleBeginnerSectionWithoutTags(section, sectionLevel, isAllSection, span) { + try { + section.classList.remove("hidden-level"); + if (span) { + span.style.display = "none"; + } + } catch (error) { + console.error('Error handling beginner section without tags:', error); } } -//Remove tag "expert" and "all" function handleExpertOrAllSection(section, span, level, isAllSection) { - section.classList.remove("hidden-level"); - if (span && (level === "expert" || isAllSection)) { - span.style.display = "none"; + try { + section.classList.remove("hidden-level"); + if (span && (level === "expert" || isAllSection)) { + span.style.display = "none"; + } + } catch (error) { + console.error('Error handling expert or all section:', error); } } -function updateTOCVisibility(level) { - if (!document.body) { - console.warn('Document body not ready, deferring TOC update'); - return; - } - if (level !== 'beginner') { - +function updateTOCVisibility(level) { + try { + if (!document.body) { + console.warn('Document body not ready, deferring TOC update'); + return; + } + if (level !== 'beginner') { document.querySelectorAll('.md-nav__item').forEach(item => { - item.style.display = ''; + item.style.display = ''; }); return; - } - - + } - const h2Elements = document.querySelectorAll('h2[data-level]'); - const tocItems = document.querySelectorAll('.md-nav__item'); - + const h2Elements = document.querySelectorAll('h2[data-level]'); + const tocItems = document.querySelectorAll('.md-nav__item'); - const beginnerIds = new Set(); - const sectionWithSubsections = new Set(); - h2Elements.forEach(h2 => { + const beginnerIds = new Set(); + h2Elements.forEach(h2 => { if (h2.getAttribute('data-level') === 'beginner' && h2.id) { - beginnerIds.add(h2.id); + beginnerIds.add(h2.id); } - }); - if (beginnerIds.size === 0) { - // If no beginner sections, show all TOC items - tocItems.forEach(item => { - item.style.display = ''; }); - } else { - - // Hide non-beginner TOC items and show beginner ones. - tocItems.forEach(item => { - const link = item.querySelector('a.md-nav__link'); - if (link) { - const href = link.getAttribute('href'); - if (href && href.startsWith('#')) { - const id = href.slice(1); - if (!beginnerIds.has(id)) { - item.style.display = 'none'; - } else { - item.style.display = ''; + + if (beginnerIds.size === 0) { + tocItems.forEach(item => { + item.style.display = ''; + }); + } else { + tocItems.forEach(item => { + const link = item.querySelector('a.md-nav__link'); + if (link) { + const href = link.getAttribute('href'); + if (href && href.startsWith('#')) { + const id = href.slice(1); + if (!beginnerIds.has(id)) { + item.style.display = 'none'; + } else { + item.style.display = ''; + } + } } - } + }); } - }); -} - + } catch (error) { + console.error('Error updating TOC visibility:', error); + } } + function updateOptionsVisibility(level) { - const optionDivs = document.querySelectorAll('.option'); - optionDivs.forEach(div => { - const aElement = div.querySelector('a[id]'); - if (level === 'beginner') { - if (aElement && aElement.getAttribute('data-level') === 'basic') { - div.style.display = ''; + try { + const optionDivs = document.querySelectorAll('.option'); + optionDivs.forEach(div => { + const aElement = div.querySelector('a[id]'); + if (level === 'beginner') { + if (aElement && aElement.getAttribute('data-level') === 'basic') { + div.style.display = ''; + } else { + div.style.display = 'none'; + } } else { - div.style.display = 'none'; + div.style.display = ''; } - } else { - div.style.display = ''; - } - }); + }); + } catch (error) { + console.error('Error updating options visibility:', error); + } } -//Levels with search feature +// Levels with search feature function isSearchResultPage() { - return new URLSearchParams(window.location.search).has('h'); + try { + return new URLSearchParams(window.location.search).has('h'); + } catch (error) { + console.error('Error checking if search result page:', error); + return false; + } } function setTemporaryExpertMode() { -const currentLevel = localstroga.getItem('userLevel'); -if(currentLevel === 'beginner' && isSearchResultPage()) { - localStorage.setItem('tempExpertMode', 'true'); - localStorage.setItem('userLevel', 'expert'); - return true -} -return false + try { + const currentLevel = localStorage.getItem('userLevel'); + if (currentLevel === 'beginner' && isSearchResultPage()) { + localStorage.setItem('tempExpertMode', 'true'); + localStorage.setItem('userLevel', 'expert'); + return true; + } + return false; + } catch (error) { + console.error('Error setting temporary expert mode:', error); + return false; + } } function revertFromTemporaryExpertMode() { - if (localStorage.getItem('tempExpertMode') === 'true') { - localStorage.setItem('userLevel', 'beginner'); - localStorage.removeItem('tempExpertMode'); - return true + try { + if (localStorage.getItem('tempExpertMode') === 'true') { + localStorage.setItem('userLevel', 'beginner'); + localStorage.removeItem('tempExpertMode'); + return true; + } + return false; + } catch (error) { + console.error('Error reverting from temporary expert mode:', error); + return false; } - return false } + /** * Event Listeners * This section sets up the main event listeners when the DOM is loaded. */ document.addEventListener("DOMContentLoaded", function () { - - initializeSettings(); - initializeLevelManagement(); - const savedLevel = localStorage.getItem("userLevel") || "expert"; - updateTOCVisibility(savedLevel); - updateOptionsVisibility(savedLevel); + try { + initializeSettings(); + initializeLevelManagement(); + const savedLevel = localStorage.getItem("userLevel") || "expert"; + updateTOCVisibility(savedLevel); + updateOptionsVisibility(savedLevel); - let cachedKeywords = getCache("keywordsCache"); - let cachedDefinitions = getCache("definitionsCache"); + let cachedKeywords = getCache("keywordsCache"); + let cachedDefinitions = getCache("definitionsCache"); - fetchKeywords(currentPageMdPath, cachedKeywords, cachedDefinitions); + fetchKeywords(currentPageMdPath, cachedKeywords, cachedDefinitions); - const levelSwitch = document.getElementById('level-switch'); - if (levelSwitch) { + const levelSwitch = document.getElementById('level-switch'); + if (levelSwitch) { levelSwitch.addEventListener('change', handleLevelChange); + } + } catch (error) { + console.error('Error during DOMContentLoaded event:', error); } }); \ No newline at end of file diff --git a/docs/javascripts/main.js b/docs/javascripts/main.js index 7cfbc274..32c0379b 100644 --- a/docs/javascripts/main.js +++ b/docs/javascripts/main.js @@ -1,61 +1,64 @@ document.addEventListener('DOMContentLoaded', function () { + try { + initializeSettings(); + initializeLevelManagement(); - initializeSettings(); - initializeLevelManagement(); + const savedLevel = localStorage.getItem("userLevel") || "expert"; + updateTOCVisibility(savedLevel); + updateOptionsVisibility(savedLevel); - + let currentPagePath = window.location.pathname; - - const savedLevel = localStorage.getItem("userLevel") || "expert"; - updateTOCVisibility(savedLevel); - updateOptionsVisibility(savedLevel); + if (currentPagePath.endsWith('/')) { + currentPagePath = currentPagePath.slice(0, -1); + } - - let currentPagePath = window.location.pathname; - + currentPageMdPath = currentPagePath.replace('.html', '.md'); - if (currentPagePath.endsWith('/')) { - currentPagePath = currentPagePath.slice(0, -1); - } + let cachedKeywords = getCache('keywordsCache'); + let cachedDefinitions = getCache('definitionsCache'); + + fetchKeywords(currentPageMdPath, cachedKeywords, cachedDefinitions); + + document.body.addEventListener('click', function(event) { + try { + const target = event.target.closest('a'); + if (target && target.href && !target.href.startsWith('javascript:')) { + const currentUrl = new URL(window.location.href); + const targetUrl = new URL(target.href); + + if (currentUrl.pathname !== targetUrl.pathname || !targetUrl.searchParams.has('h')) { + if (localStorage.getItem('tempExpertMode') === 'true') { + event.preventDefault(); + revertFromTemporaryExpertMode(); + filterContent('beginner'); + updateTOCVisibility('beginner'); + updateOptionsVisibility('beginner'); - currentPageMdPath = currentPagePath.replace('.html', '.md'); - - - let cachedKeywords = getCache('keywordsCache'); - let cachedDefinitions = getCache('definitionsCache'); - - - fetchKeywords(currentPageMdPath, cachedKeywords, cachedDefinitions); - - - document.body.addEventListener('click', function(event) { - const target = event.target.closest('a'); - if (target && target.href && !target.href.startsWith('javascript:')) { - const currentUrl = new URL(window.location.href); - const targetUrl = new URL(target.href); - - if (currentUrl.pathname !== targetUrl.pathname || !targetUrl.searchParams.has('h')) { - if (localStorage.getItem('tempExpertMode') === 'true') { - event.preventDefault(); - revertFromTemporaryExpertMode(); - filterContent('beginner'); - updateTOCVisibility('beginner'); - updateOptionsVisibility('beginner'); - - setTimeout(() => { - window.location.href = target.href; - }, 0); + setTimeout(() => { + window.location.href = target.href; + }, 0); + } + } + } + } catch (error) { + console.error('Error handling click event:', error); } - } - } - }); - - // Handle navigation via browser back/forward buttons - window.addEventListener('popstate', function() { - if (!isSearchResultPage() && revertFromTemporaryExpertMode()) { - filterContent('beginner'); - updateTOCVisibility('beginner'); - updateOptionsVisibility('beginner'); - } - }); - }); \ No newline at end of file + }); + + // Handle navigation via browser back/forward buttons + window.addEventListener('popstate', function() { + try { + if (!isSearchResultPage() && revertFromTemporaryExpertMode()) { + filterContent('beginner'); + updateTOCVisibility('beginner'); + updateOptionsVisibility('beginner'); + } + } catch (error) { + console.error('Error handling popstate event:', error); + } + }); + } catch (error) { + console.error('Error during DOMContentLoaded event:', error); + } +}); \ No newline at end of file diff --git a/docs/javascripts/modalFunctions.js b/docs/javascripts/modalFunctions.js index 32adf6a1..41d1e929 100644 --- a/docs/javascripts/modalFunctions.js +++ b/docs/javascripts/modalFunctions.js @@ -1,84 +1,103 @@ let closeModalTimer; function keepModalOpen() { - clearTimeout(closeModalTimer); + try { + clearTimeout(closeModalTimer); + } catch (error) { + console.error('Error keeping modal open:', error); + } } function startCloseModalTimer() { - closeModalTimer = setTimeout(closeModal, 300); + try { + closeModalTimer = setTimeout(closeModal, 300); + } catch (error) { + console.error('Error starting close modal timer:', error); + } } -function openModal(keyword, definition, event = null) { - - const modal = document.getElementById("modal"); - const modalTitle = document.getElementById("modal-title"); - const modalDefinition = document.getElementById("modal-definition"); - const modalLink = document.getElementById("modal-link"); +function openModal(keyword, definition, event = null) { + try { + const modal = document.getElementById("modal"); + const modalTitle = document.getElementById("modal-title"); + const modalDefinition = document.getElementById("modal-definition"); + const modalLink = document.getElementById("modal-link"); + if (!modalTitle || !modalDefinition || !modalLink) { + console.error('Modal elements not found'); + return; + } - if (!modalTitle || !modalDefinition || !modalLink) { - console.error('Modal elements not found'); - return; - } - - if (modalTitle && modalDefinition && modalLink) { - let descriptionText = 'Definition not vailable'; + let descriptionText = 'Definition not available'; if (typeof definition === 'string') { descriptionText = definition; } else if (definition && typeof definition === 'object' && definition.description) { descriptionText = definition.description; - } - const glossaryPageUrl = `${ - window.location.origin - }/glossary/${keyword.toLowerCase()}/`; - - modalTitle.textContent = keyword; - modalDefinition.textContent = descriptionText; - modalLink.href = glossaryPageUrl; + } - modal.style.display = "block"; + const glossaryPageUrl = `${window.location.origin}/glossary/${keyword.toLowerCase()}/`; - modal.classList.remove("hidden"); - modal.style.display = "block"; - modalLink.classList.remove("hidden"); - - } else { - console.error('Modal elements not found'); - } - setTimeout(() => { - modal.classList.add("visible"); - }, 10); + modalTitle.textContent = keyword; + modalDefinition.textContent = descriptionText; + modalLink.href = glossaryPageUrl; + + modal.style.display = "block"; + modal.classList.remove("hidden"); + modalLink.classList.remove("hidden"); - - modal.addEventListener('mouseenter', keepModalOpen); - modal.addEventListener('mouseleave', startCloseModalTimer); + setTimeout(() => { + modal.classList.add("visible"); + }, 10); + + modal.addEventListener('mouseenter', keepModalOpen); + modal.addEventListener('mouseleave', startCloseModalTimer); + } catch (error) { + console.error('Error opening modal:', error); + } } function closeModal() { - const modal = document.getElementById("modal"); - if (modal) { - modal.classList.remove("visible"); + try { + const modal = document.getElementById("modal"); + if (modal) { + modal.classList.remove("visible"); - setTimeout(() => { - modal.style.display = "none"; - }, 300); + setTimeout(() => { + modal.style.display = "none"; + }, 300); + } + } catch (error) { + console.error('Error closing modal:', error); } } + document.addEventListener("DOMContentLoaded", function () { - document.getElementById("close-modal").addEventListener("click", function () { - const modal = document.getElementById("modal"); - modal.classList.add("hidden"); - modal.style.display = "none"; - }); + try { + document.getElementById("close-modal").addEventListener("click", function () { + try { + const modal = document.getElementById("modal"); + modal.classList.add("hidden"); + modal.style.display = "none"; + } catch (error) { + console.error('Error handling close-modal click event:', error); + } + }); - document.getElementById("modal").addEventListener("click", function (event) { - if (event.target === event.currentTarget) { - const modal = document.getElementById("modal"); - modal.classList.add("hidden"); - modal.style.display = "none"; - } - }); + document.getElementById("modal").addEventListener("click", function (event) { + try { + if (event.target === event.currentTarget) { + const modal = document.getElementById("modal"); + modal.classList.add("hidden"); + modal.style.display = "none"; + } + } catch (error) { + console.error('Error handling modal click event:', error); + } + }); - window.openModal = openModal; - window.closeModal = closeModal; + window.openModal = openModal; + window.closeModal = closeModal; + } catch (error) { + console.error('Error during DOMContentLoaded event:', error); + } }); \ No newline at end of file From b299e6667263bad9e332072bca49ce832ba1f5ce Mon Sep 17 00:00:00 2001 From: PierreDillard <110824159+PierreDillard@users.noreply.github.com> Date: Fri, 11 Oct 2024 15:57:29 +0200 Subject: [PATCH 27/28] Delete changements_diff_origin.txt --- changements_diff_origin.txt | 15446 ---------------------------------- 1 file changed, 15446 deletions(-) delete mode 100644 changements_diff_origin.txt diff --git a/changements_diff_origin.txt b/changements_diff_origin.txt deleted file mode 100644 index 38616311..00000000 --- a/changements_diff_origin.txt +++ /dev/null @@ -1,15446 +0,0 @@ -diff --git a/.gitignore b/.gitignore -index 8f997d18..ecb5b0f8 100644 ---- a/.gitignore -+++ b/.gitignore -@@ -1,6 +1,8 @@ - .ignore/ - site/ - .venv/ -+venv/ - __pycache__/ - site.* - .DS_Store -+node_modules/ -diff --git a/content-tagging-system.md b/content-tagging-system.md -new file mode 100644 -index 00000000..38130513 ---- /dev/null -+++ b/content-tagging-system.md -@@ -0,0 +1,136 @@ -+ -+ -+# GPAC Wiki Content Tagging and Level Switch System -+ -+## Overview -+ -+The GPAC wiki implements a content tagging and level switch system to provide a customized reading experience for users with different levels of expertise. This system allows users to toggle between "Beginner" and "Expert" modes, affecting the visibility of content sections and keywords displayed. -+ -+## Purpose -+ -+The main goals of this system are: -+1. To help users new to GPAC access simpler, more approachable content. -+2. To provide more in-depth, technical content for experienced users. -+3. To create a dynamic reading experience that adapts to the user's knowledge level. -+ -+## Implementation -+ -+### Content Tagging -+ -+Content tagging is implemented in Markdown files using the `attr_list` extension. Tags are applied to H1 headers (single `#` in Markdown) using the following syntax: -+ -+```markdown -+# Section Title {: data-level="beginner" } -+``` -+ -+Available levels: -+- `beginner`: Content suitable for newcomers to GPAC. -+- `expert`: More advanced content (default if not specified). -+- `all`: Content that appears in both beginner and expert modes. -+ -+### Level Switch -+ -+The level switch is a toggle located in the top-left corner of the user interface. It allows users to switch between "Beginner" and "Expert" modes. -+ -+Default setting: The default level is set to "Expert", displaying all documentation. -+ -+User preference storage: The selected level is stored in the browser's local storage, persisting between sessions. -+ -+## Functionality -+ -+### Content Visibility -+ -+When switching between levels: -+ -+1. Expert mode: -+ - All sections are visible. -+ - The full Table of Contents (TOC) is displayed, including all sections and subsections. -+ -+2. Beginner mode: -+ - Only sections tagged as "beginner" or "all" are visible. -+ - Sections not explicitly tagged may disappear. -+ - The TOC displays only visible sections without subsections. -+ -+### Keywords Cloud -+ -+The keywords cloud is dynamic and changes based on the selected level: -+ -+- In Expert mode: All keywords are displayed. -+- In Beginner mode: Only keywords tagged as "beginner" or "all" are shown. -+ -+Keywords are defined in the `data/keywords.json` file with the following structure: -+ -+```json -+{ -+ "KEYWORD": { -+ "description": "Keyword description", -+ "level": "beginner" -+ } -+} -+``` -+ -+When a user clicks on a keyword, a modal appears with the keyword's definition. -+ -+## Implementation Details -+ -+### Key Files -+ -+- `javascripts/levels.js`: Main logic for level switching and content filtering. -+- `javascripts/domManipulation.js`: Handles DOM manipulation for showing/hiding content. -+ -+### Howtos Section -+ -+The level switching functionality is currently implemented only in the "Howtos" section of the documentation. -+ -+ -+## Developer Guidelines -+ -+1. Tagging new content: -+ - Always tag H1 headers in Markdown files. -+ - Use the format: `# Title {: data-level="level" }` where `level` is "beginner", "expert", or "all". -+ - The "all" sections are visible to beginners and experts. -+ Any section tagged with "all" will have its collapse menu unfolded. -+ They are often used for "Overview" sections. -+ - Untagged sections are considered "expert" by default. -+ -+2. Adding new keywords: -+ - Add new keywords to the `data/keywords.json` file. -+ - Include a description and appropriate level tag. -+ -+3. Testing: -+ - Test new content in both Beginner and Expert modes to ensure proper visibility. -+ - Verify that the TOC updates correctly when switching levels. -+ - Check that the keywords cloud updates appropriately. -+ -+## Best Practices for Content Formatting -+ -+When adding or editing content in the GPAC wiki, it's crucial to follow certain formatting practices to ensure readability and proper rendering of the documentation. One key aspect to pay attention to is the spacing between code blocks and other elements. -+ -+### Spacing Between Code Blocks -+ -+Always add a blank line between code blocks and surrounding text. This practice prevents rendering issues and improves readability. -+ -+#### Good Practice: -+ -+Example: -+ -+![Code block formatting issue](docs/images/good_format.png) -+ -+#### Bad Practice: -+ -+Avoid this: -+Example: -+ -+![Code block formatting issue](docs/images/bad_format.png) -+ -+ -+## Known Limitations -+ -+1. The level switching feature is currently only available in the "Howtos" section. -+2. The visibility of untagged sections in Beginner mode may not be consistent across all pages. -+ -+## Future Enhancements -+ -+1. Implement direct linking to keyword pages (e.g., `/glossary/${keyword}`). -+2. Extend the level switching functionality to other sections of the documentation. -+3. Improve the visibility and user experience of the level switch toggle. -diff --git a/docs/Build/build/GPAC-Build-Guide-for-Linux.md b/docs/Build/build/GPAC-Build-Guide-for-Linux.md -index bc277d85..a1f03c91 100644 ---- a/docs/Build/build/GPAC-Build-Guide-for-Linux.md -+++ b/docs/Build/build/GPAC-Build-Guide-for-Linux.md -@@ -58,7 +58,7 @@ You can either: - Install the development packages for the third-party libraries GPAC is able to leverage: - - ```bash --sudo apt install zlib1g-dev libfreetype6-dev libjpeg62-dev libpng-dev libmad0-dev libfaad-dev libogg-dev libvorbis-dev libtheora-dev liba52-0.7.4-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavdevice-dev libnghttp2-dev libopenjp2-7-dev libcaca-dev libxv-dev x11proto-video-dev libgl1-mesa-dev libglu1-mesa-dev x11proto-gl-dev libxvidcore-dev libssl-dev libjack-jackd2-dev libasound2-dev libpulse-dev libsdl2-dev dvb-apps mesa-utils -+sudo apt install zlib1g-dev libfreetype6-dev libjpeg62-dev libpng-dev libmad0-dev libfaad-dev libogg-dev libvorbis-dev libtheora-dev liba52-0.7.4-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavdevice-dev libnghttp2-dev libopenjp2-7-dev libcaca-dev libxv-dev x11proto-video-dev libgl1-mesa-dev libglu1-mesa-dev x11proto-gl-dev libxvidcore-dev libssl-dev libjack-jackd2-dev libasound2-dev libpulse-dev libsdl2-dev dvb-apps mesa-utils libcurl4-openssl-dev - ``` - - This list should work on Ubuntu from 14.04 (`trusty`) to at least 22.04 (`jammy`). -diff --git a/docs/Build/build/GPAC-Build-Guide-for-OSX.md b/docs/Build/build/GPAC-Build-Guide-for-OSX.md -index 0ed7d555..78fbd2dd 100644 ---- a/docs/Build/build/GPAC-Build-Guide-for-OSX.md -+++ b/docs/Build/build/GPAC-Build-Guide-for-OSX.md -@@ -38,10 +38,10 @@ brew install freetype jpeg libpng openjpeg mad faad2 libogg libvorbis theora a52 - - ```bash - # install build tools --port install cmake scons coreutils gettext yasm git wget pkgconfig -+sudo port -N install cmake scons coreutils gettext yasm git wget pkgconfig - - # install dependencies --port install freetype jpeg libpng openjpeg libmad faad2 libogg libvorbis libtheora a52dec ffmpeg x264 aom xvid openssl libsdl2 -+sudo port -N install freetype jpeg libpng openjpeg libmad faad2 libogg libvorbis libtheora a52dec ffmpeg6 x264 aom xvid openssl libsdl2 - ``` - -
    -diff --git a/docs/Build/build/GPAC-Build-with-Docker.md b/docs/Build/build/GPAC-Build-with-Docker.md -index 90b369e0..a8c0c650 100644 ---- a/docs/Build/build/GPAC-Build-with-Docker.md -+++ b/docs/Build/build/GPAC-Build-with-Docker.md -@@ -25,10 +25,10 @@ cd gpac - ... - - # doubly optional: you can change and rebuild the base system containing the dependencies for gpac with --docker build -t gpac/ubuntu-deps -f build/docker/ubuntu-deps.DockerFile . -+docker build -t gpac/ubuntu-deps -f build/docker/ubuntu-deps.Dockerfile . - - # build the docker image --docker build -t myimages/gpac -f build/docker/ubuntu.DockerFile . -+docker build -t myimages/gpac -f build/docker/ubuntu.Dockerfile . - ``` - - ## Use an image -diff --git a/docs/Build/old/Compiling-GPAC-for-MacOS-X.md b/docs/Build/old/Compiling-GPAC-for-MacOS-X.md -index 51586109..df4b01e7 100644 ---- a/docs/Build/old/Compiling-GPAC-for-MacOS-X.md -+++ b/docs/Build/old/Compiling-GPAC-for-MacOS-X.md -@@ -13,9 +13,10 @@ git clone https://github.com/gpac/gpac.git - ## (Re-)Installing - - If macports is installed, [uninstall it](http://guide.macports.org/chunked/installing.macports.uninstalling.html): --``` --sudo port -f uninstall installed --sudo rm -rf /opt/local /Applications/DarwinPorts /Applications/MacPorts /Library/LaunchDaemons/org.macports.* /Library/Receipts/DarwinPorts*.pkg /Library/Receipts/MacPorts*.pkg /Library/StartupItems/DarwinPortsStartup /Library/Tcl/darwinports1.0 /Library/Tcl/macports1.0 ~/.macports -+ -+ ``` -+ sudo port -f uninstall installed -+sudo rm -rf /opt/local /Applications/DarwinPorts /Applications/MacPorts /Library/LaunchDaemons/org.macports.* /Library/Receipts/DarwinPorts*.pkg /Library/Receipts/MacPorts*.pkg Library/StartupItems/DarwinPortsStartup /Library/Tcl/darwinports1.0 /Library/Tcl/macports1.0 ~/.macports - ``` - - [Install macport](http://distfiles.macports.org/MacPorts/). You MUST install a version >=1.9.x -diff --git a/docs/Developers/index.md b/docs/Developers/index.md -index b8c7e6df..002fd12b 100644 ---- a/docs/Developers/index.md -+++ b/docs/Developers/index.md -@@ -1,8 +1,49 @@ -+--- -+title: GPAC developer guide -+--- - --## Developer resources -+## Contributing -+ -+A complex project like GPAC wouldn’t exist and persist without the support of its community. Please contribute: a nice message, supporting us in our communication, reporting issues when you see them … any gesture, even the smallest ones, counts. -+ -+## Getting support and reporting issues -+ -+Please use [github issues](https://github.com/gpac/gpac/issues) for feature requests and bug reports. When filing a request there, please tag it as feature-request. -+ -+ -+## API documentation -+ -+The [API documentation](https://doxygen.gpac.io/modules.html) provides information on the GPAC Filter API. -+ -+GPAC's core is writen in C, but it can be easily extended using [Javascipt Filters](/Developers/javascript), used in a [Python](/Howtos/python) or [NodeJS application](/Developers/javascript). -+ -+ -+## Tutorials -+ -+- [Intro to Filter Session](/Developers/tutorials/filter-session-intro) -+- [Writing a custom Filter](/Developers/tutorials/custom-filter) -+ -+ -+## Building -+ -+Detailed build [Build](/Build/Build-Introduction) instructions for MP4Box and GPAC on all supported platforms. -+ -+ -+## Testing -+ -+Learn how to [build and run GPAC's test suite](/Build/tests/GPAC_tests). -+ -+The
    testsuite scripts is always a good place to understand GPAC tools usage. -+ -+ -+## Continuous integration -+ -+GPAC is continuously built and tested through a buildbot server: -+ -+* Build status of GPAC: buildbot.gpac.io -+* Tests status of GPAC: tests.gpac.io -+ -+ -+## Archives - --* current build status of GPAC: buildbot.gpac.io --* current test suite status of GPAC: tests.gpac.io --* C API documentation: doxygen.gpac.io --* the testsuite scripts might give you additional info on how to use GPAC tools - * tips and tricks in our [github discussions](https://github.com/gpac/gpac/issues?utf8=%E2%9C%93&q=) and our old [sourceforge forums](https://sourceforge.net/p/gpac/discussion/) -diff --git a/docs/Developers/javascript.md b/docs/Developers/javascript.md -new file mode 100644 -index 00000000..21050662 ---- /dev/null -+++ b/docs/Developers/javascript.md -@@ -0,0 +1,16 @@ -+ -+Javascript developers have two options to work with GPAC, the most appropriate solution depends on the project's goal: -+ -+## Javascript Filters -+ -+The JS filter API makes it easy to **extend gpac** using the internal QuickJS runtime, giving access to the Filter API for frame and packet processing, but also APIs for adaptative streaming, compositing, storage, ... -+ -+Some of the gpac built-in filters - eg. [avgen](Filters/avgen), [avmix](Filters/avmix) - are actualy implemented as custom javascript filters. Their source code can be found under the[`share/scripts/jsf`](https://github.com/gpac/gpac/tree/master/share/scripts/jsf) directory. -+ -+[JSF documentation](/Howtos/jsf/jsfilter){ .md-button } -+ -+## NodeJS -+ -+GPAC's NodeJS bindings allow **writing custom NodeJS applications**. It differs slightly from the Javascript Filters API available in the QuickJS runtime. -+ -+[NodeJS documentation](/Howtos/nodejs){ .md-button } -diff --git a/docs/Developers/tutorials/GPAC-concepts.md b/docs/Developers/tutorials/GPAC-concepts.md -new file mode 100644 -index 00000000..5b9a0b9a ---- /dev/null -+++ b/docs/Developers/tutorials/GPAC-concepts.md -@@ -0,0 +1,40 @@ -+ -+## What is GPAC ? -+ -+The **GPAC Filter API** is at the core of the [MP4Box and GPAC](Howtos/gpac-mp4box) applications. -+ -+The `gpac` application allows building media pipelines by conveniently [combining and configuring Filters](Filters/filters_general) from the command line. -+ -+Filters are configurable processing units consuming and producing data packets. -+ -+GPAC provides a wide range of Filters supporting advanced media protocols, formats, codecs, for input, output and processing tasks. -+ -+It defines the infrastructure and helper classes to develop applications with advanced media capabilities. -+ -+ -+### Concepts -+ -+**Filter Session** -+ -+- [API documentation](https://doxygen.gpac.io/group__fs__grp.html#details) -+- [tutorial](https://git.gpac-licensing.com/slarbi/API_FIlters_tutos/src/branch/master/T0_Filters_session/simple%20gpac%20session.md) -+ -+**Filter** -+- [API documentation](https://doxygen.gpac.io/group__fs__filter.html#details) -+ -+**Filter Properties** -+- [API documentation](https://doxygen.gpac.io/group__fs__props.html#details) -+- [Built in Properties](https://wiki.gpac.io/Filters/filters_properties/?h=properties) -+ -+**Filter Events** -+- [API documentation](https://doxygen.gpac.io/group__fs__evt.html#details) -+ -+**Filter PIDs & Capabilities** -+- [API documentation](https://doxygen.gpac.io/group__fs__pid.html) -+ -+**Filter Packet** -+- [API documentation](https://doxygen.gpac.io/group__fs__pck.html#details) -+ -+**Custom Filter** -+- [doxygen](https://doxygen.gpac.io/group__filters____cust__grp.html#details) -+- [writing a custom Filter]() -diff --git a/docs/Developers/tutorials/custom-filter.md b/docs/Developers/tutorials/custom-filter.md -new file mode 100644 -index 00000000..7881d592 ---- /dev/null -+++ b/docs/Developers/tutorials/custom-filter.md -@@ -0,0 +1,383 @@ -+## Creating a custom GPAC filter -+ -+Custom filters are filters created by the app with no associated registry. Therefore there is no internal representation for the custom filter (No filter registry). So capabilities and different behaviors of the custom filter must be specified by the app with the helper callback functions (listed below). -+ -+The app is responsible for assigning capabilities to the filter, and setting callback functions. Each callback is optional, but a custom filter should at least have a process callback, and a configure_pid callback if not a source filter. -+ -+ -+ -+**Custom filter limitations:** -+ -+* Custom filters do not have any arguments exposed. -+* The filter cannot be used as source of filters loading a source filter graph dynamically, such as the dashin filter. -+* The filter cannot be used as destination of filters loading a destination filter graph dynamically, such as the dasher filter. -+* The filter cannot be cloned. -+ -+ -+## Callback functions -+ -+### [gf_fs_new_filter()](https://doxygen.gpac.io/group__filters____cust__grp.html#ga45d6cd5535614e43d6ebb77d8cb1a4bc) -+ -+This function is responsible for loading a custom filter into the specified filter session. It allows the application to create filters without associated registries, providing the flexibility to assign capabilities and set callback functions. The function parameters include the filter session, the name of the filter (optional), flags for filter registry, and an optional error code parameter. -+ -+### [gf_filter_push_caps()](https://doxygen.gpac.io/group__filters____cust__grp.html#ga878516ab46d5bfc96692a91f3ba6b124) -+ -+This function facilitates the addition of new capabilities to a custom filter. Parameters such as capability code, value, name, flags, and priority are specified to define the capabilities. This function returns an error if any issues occur during the process. -+ -+An alternative way is to push the caps to a specific PID within the filter callbacks function, typically inside the process callback , or the configure callback for non source filters. -+ -+### [gf_filter_set_process_ckb()](https://doxygen.gpac.io/group__filters____cust__grp.html#gad1f20440c6de9b009a4fde85d429ad39) -+ -+This function is employed to set the process callback function for a custom filter. Typically callback defines the processing logic of the filter. The function takes the target filter and the process callback as parameters. -+ -+This one callback is mandatory in order to use the custom filter. -+ -+### [gf_filter_set_configure_ckb()](https://doxygen.gpac.io/group__filters____cust__grp.html#gab21f4169d1de1a0876f8b6856301ff28) -+ -+For custom filters that are not source filters, the **gf_filter_set_configure_ckb()** function is utilized to set the **PID** configuration callback. The assigned function enables and manages the configuration of the PID(s) for the filter. -+ -+### [gf_filter_set_process_event_ckb()](https://doxygen.gpac.io/group__filters____cust__grp.html#gadefa24ff7508d8838334d381f7c9004c) -+ -+Set the process event callback for a custom filter. This callback handles events related to the filter. for example specifying what happens if a Play or a Stop event is received. -+ -+### [gf_filter_set_reconfigure_output_ckb()](https://doxygen.gpac.io/group__filters____cust__grp.html#gae22ecc90654524434483f204713989fb) -+ -+The reconfigure_output callback may be needed to reconfigure the output PID(s) of a filter during the execution of a filters session. -+ -+### [gf_filter_set_probe_data_cbk()](%28https://doxygen.gpac.io/group__filters____cust__grp.html#ga3b067ec9d2067d683ea5d3fdb4d32833%29) -+ -+Set the data prober function for a custom filter. -+ -+ -+## Pushing application data to a GPAC filter session using a custom filter -+ -+We saw previously that source filters generaly are file (or pipe/sockets/memory files) access objects. -+ -+But sometimes when integrating gpac with other pieces of software, the data may be available directly in the memory. so it can be beneficial to access data from memory within a gpac filters session. -+ -+The following is an example of a custom filter named “**mem_in**” which is source filter that provides a way to create a PID where the raw data is located in memory (ex audio/video frames located in memory). Allowing for the creation of a filter chain to process this data ( feeding the PID to other filters). -+ -+ -+### Defining the process callback function -+ -+We start by defining a process callback, which the main logic will execute whenever our custom filter is called for a process execution by the the filters session: -+ -+```C -+GF_Err mem_in_process_ckb(GF_Filter *filter) { -+ GF_Err gf_err = GF_OK; -+ GF_FilterPid *opid = NULL; -+ -+ opid = gf_filter_get_opid(filter, 0); -+ if (!opid) { -+ opid = gf_filter_pid_new(filter); -+ Properties properties[] = { -+ {.prop_4cc = GF_PROP_PID_CODECID, -+ .val = {.type = GF_PROP_UINT, .value.uint = GF_CODECID_AVC}, -+ .flags = GF_CAPS_INPUT}, -+ {0}, -+ }; -+ push_props(opid, properties); -+ } -+ -+ const u8 *data = NULL; -+ u32 data_size = 0; -+ u64 dts = 0, pts = 0; -+ MemInCtx *ctx = (MemInCtx *)gf_filter_get_rt_udta(filter); -+ if (!ctx) -+ return GF_BAD_PARAM; -+ -+ ctx->parent = (void *)ctx; -+ ctx->getData = &inputGetData; -+ if (!ctx->getData(ctx->parent, &data, &data_size, &dts, &pts)){ -+ gf_filter_pid_set_eos(opid); -+ return GF_EOS; -+ } -+ -+ if (!data) { -+ gf_filter_ask_rt_reschedule(filter, 1); -+ return GF_OK; -+ } -+ -+ GF_FilterPacket *pck =gf_filter_pck_new_shared(opid, data, data_size, mem_in_pck_destructor); -+ if (!pck) { -+ gf_err = GF_OUT_OF_MEM; -+ goto exit; -+ } -+ -+ gf_filter_pck_set_dts(pck, dts); -+ gf_filter_pck_set_cts(pck, pts); -+ gf_filter_pck_set_duration(pck, 1); -+ gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1); -+ gf_filter_pck_set_framing(pck, GF_TRUE, GF_TRUE); -+ gf_filter_pck_send(pck); -+ -+ exit: -+ return gf_err; -+} -+``` -+ -+**Code explanation** -+ -+First thing we declare a PID that will serve us as an output PID, than we add the properties to this PID. Here for example we are adding the property **GF_PROP_PID_CODECID** with the value **GF_CODECID_AVC** to indicate that w are sending an AVC frame through this PID. -+ -+```C -+opid = gf_filter_get_opid(filter, 0); -+if (!opid) { -+ opid = gf_filter_pid_new(filter); -+ Properties properties[] = { -+ {.prop_4cc = GF_PROP_PID_CODECID, -+ .val = {.type = GF_PROP_UINT, .value.uint = GF_CODECID_AVC}, -+ .flags = GF_CAPS_INPUT}, -+ {0}, -+}; -+ -+ push_props(opid, properties); -+} -+``` -+ -+The push props function is defined below: -+ -+```C -+static GF_Err push_props(GF_FilterPid *PID, Properties pid_props[]) -+{ -+GF_Err gf_err = GF_OK; -+int i = 0; -+while(pid_props[i].prop_4cc) { -+ if(pid_props[i].flags == GF_CAPS_INPUT) { -+ gf_filter_pid_set_property(PID, pid_props[i].prop_4cc, &pid_props[i].val); -+ } -+ i++; -+}} -+``` -+ -+We loop through the props that we want to add (defined here through a specific struct). And add them through the [gf_filter_pid_set_property](https://doxygen.gpac.io/group__fs__pid.html#gaa9d532d9ca4c10a19973bcbd5e8af4fd) function. -+ -+An alternative way is to use the [gf_filter_push_caps()](https://doxygen.gpac.io/group__filters____cust__grp.html#ga878516ab46d5bfc96692a91f3ba6b124) function. -+ -+Our output PID and its properties are now configured, Next order of business is to fetch the data from memory. We keep a reference to the internal data of the filter through the following struct. This allows us to pass references to the getData() and freeData() functions from the main program to the filters session internals, we also keep some metadata like the current decoding / presentation timestamps and the number of max frames we will process. -+ -+```C -+typedef struct { -+ void *parent; -+ Bool (*getData)(void *parent, const u8 **data, u32 *data_size, u64 *dts, -+ u64 *pts); -+ void (*freeData)(void *parent, const u8 *data); -+ int max_frames; -+ u64 dts; -+ u64 pts; -+} MemInCtx; -+``` -+ -+With that in mind , lets return to our mem_in process callback: -+ -+```C -+const u8 *data = NULL; -+u32 data_size = 0; -+u64 dts = 0, pts = 0; -+MemInCtx *ctx = (MemInCtx *)gf_filter_get_rt_udta(filter); -+if (!ctx) -+ return GF_BAD_PARAM; -+ctx->parent = (void *)ctx; -+ctx->getData = &inputGetData; -+if (!ctx->getData(ctx->parent, &data, &data_size, &dts, &pts)){ -+ gf_filter_pid_set_eos(opid); -+ return GF_EOS; -+} -+if (!data) { -+ gf_filter_ask_rt_reschedule(filter, 1); -+ return GF_OK; -+} -+``` -+ -+After initialisation of local variables, we use the [gf_filter_get_rt_udta()](https://doxygen.gpac.io/group__fs__filter.html#ga47b46ae728e700f983f53fdc069032f3) function to retrieve the user data that we set from the main function(see code bellow). This function is typically used by bindings and custom filters to share runtime data. -+ -+Now we can access the data in memory using the getData(), in case the function is not available we send an End Of Stream signal through our output pid. -+ -+If there is no data available we ask for rescheduling with [gf_filter_ask_rt_reschedule()](https://doxygen.gpac.io/group__fs__filter.html#ga36bb988aa964b3c6220aae11773d7c9e). -+ -+ -+Otherwise, we create a new packet to be shared, we set some packets properties and we send the packet upstream. -+ -+```C -+GF_FilterPacket *pck = gf_filter_pck_new_shared(opid, data, data_size, mem_in_pck_destructor); -+if(!pck) { gf_err = GF_OUT_OF_MEM; goto exit; } -+gf_filter_pck_set_dts(pck, dts); -+gf_filter_pck_set_cts(pck, pts); -+gf_filter_pck_set_duration(pck, 1); -+gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1); -+gf_filter_pck_set_framing(pck, GF_TRUE, GF_TRUE); -+gf_filter_pck_send(pck); -+``` -+ -+### Instantiation of the process callback -+ -+Once we defined our process callback we need to instantiate it and assign it to our filter. so the logic of the filter will be executed each time the filter process is called by the gpac filters session. -+ -+```C -+// create a new GF_filter pointer called src_filter -+GF_Filter *src_filter = gf_fs_new_filter(session, "mem_in", 0, &gf_err); -+// declare the mem_in process callback function if not defined in the same file -+GF_Err mem_in_process_ckb(GF_Filter * filter); -+// assign the callback to the filter -+gf_filter_set_process_ckb(src_filter, mem_in_process_ckb); -+``` -+ -+Our source filter is ready to be used by the filter session. -+ -+ -+## Example 2 - Getting data from a custom filter to an application -+ -+ -+Alternatively to using custom filters we can create a filter class and add it to the session using [gf_fs_add_filter_register()](https://doxygen.gpac.io/group__fs__grp.html#ga4ae302f59379de2544c17b374eae3516) -+The following is the definition of the mem_out filter register with simply two functions process() and configure_pid(). (This very minimalistic more options are possible ) -+ -+```C -+ GF_FilterRegister memOutRegister = { -+ .name = "mem_out", -+ .private_size = sizeof(MemOutCtx), -+ .process = mem_out_process, -+ .configure_pid = mem_out_configure_pid, -+ }; -+``` -+ -+The mem_out_process logic is also straight forward. -+ -+```C -+MemOutCtx *ctx = (MemOutCtx *)gf_filter_get_udta(filter); -+static GF_Err mem_out_process(GF_Filter *filter) { -+ if (!ctx) -+ return GF_BAD_PARAM; -+ ctx->pushData = &outputPushData; -+ ctx->pushDsi = &outputPushMetadata; -+ -+ GF_FilterPid *ipid = gf_filter_get_ipid(filter, 0); -+ -+ const GF_PropertyValue *prop = -+ gf_filter_pid_get_property(ipid, GF_PROP_PID_DECODER_CONFIG); -+ -+ if (prop && prop->value.data.ptr && prop->value.data.size) { -+ ctx->pushDsi(ctx->parent, prop->value.data.ptr, prop->value.data.size); -+ } -+ -+ GF_FilterPacket *pck = gf_filter_pid_get_packet(ipid); -+ if (pck) { -+ u32 data_size = 0; -+ u64 dts = gf_filter_pck_get_dts(pck); -+ u64 pts = gf_filter_pck_get_cts(pck); -+ const u8 *data = gf_filter_pck_get_data(pck, &data_size); -+ -+ ctx->pushData(ctx->parent, data, data_size, dts, pts); -+ gf_filter_pid_drop_packet(ipid); -+ } -+ -+ return GF_OK; -+} -+``` -+ -+we note here: -+ -+* The use of [gf_filter_get_ipid()](https://doxygen.gpac.io/group__fs__filter.html#ga03a9a73e8d044d7737b4dd9d74e23a79) to retrieve the input pid. -+* The use of [gf_filter_pid_get_property()](https://doxygen.gpac.io/group__fs__pid.html#ga1e28b43fba75976755ef3004edfe2a2a) to get the properties of the input pid. -+* The use of [gf_filter_pid_get_packet()](https://doxygen.gpac.io/group__fs__pid.html#gaf373afc8a944b4a5b20952e8a3121d2f) to retrieve the packet from the input pid. -+* The use of [gf_filter_pck_get_data()](https://doxygen.gpac.io/group__fs__pck.html#ga6727b4c6fa4a4366ccc23244c6191570) to retrieve the data from the packet of the input pid. -+ -+ -+### Registry and loading of the filter to the session -+ -+```C -+// register and load destination filter -+GF_Filter *dst_filter = NULL; -+const GF_FilterRegister *mem_out_register(GF_FilterSession *); -+gf_fs_add_filter_register(session, mem_out_register(session)); -+dst_filter = gf_fs_load_filter(session, "mem_out", &gf_err); -+``` -+ -+The memory output filter is now ready to be used inside the filters session. -+ -+## Building the filter session graph using the reframer filter -+ -+Here is an example of a filters session using both filters examples provided before that we build using two different approaches ( custom filter & class filter). sandwiching the reframer as an example of a filters chain. -+ -+```C -+int main() { -+ -+ GF_Err gf_err = GF_OK; -+ -+ GF_FilterSession *session = gf_fs_new_defaults(0u); -+ if (session == NULL) { -+ fprintf(stderr, "Failed to create GPAC session\n"); -+ goto exit; -+ } -+ -+ // adding custom input filter -+ GF_Filter *src_filter = gf_fs_new_filter(session, "mem_in", 0, &gf_err); -+ MemInCtx *ctxIn = gf_malloc(sizeof(MemInCtx)); -+ ctxIn->dts = 0; -+ ctxIn->pts = 0; -+ ctxIn->max_frames = 3; -+ gf_err = gf_filter_set_rt_udta(src_filter, (void *)ctxIn); -+ gf_err = gf_filter_set_process_ckb(src_filter, mem_in_process_ckb); -+ GF_Filter *reframer_filter = gf_fs_load_filter(session, "reframer", &gf_err); -+ if (gf_err != GF_OK) { -+ fprintf(stderr, "Failed to load filter reframer: %s \n", -+ gf_error_to_string(gf_err)); -+ goto exit; -+ } -+ gf_filter_set_source(reframer_filter, src_filter, NULL); -+ -+ // register and load destination filter -+ GF_Filter *dst_filter = NULL; -+ const GF_FilterRegister *mem_out_register(GF_FilterSession *); -+ gf_fs_add_filter_register(session, mem_out_register(session)); -+ dst_filter = gf_fs_load_filter(session, "mem_out", &gf_err); -+ if (gf_err != GF_OK) { -+ fprintf(stderr, "Failed to load filter mem_out: %s \n", -+ gf_error_to_string(gf_err)); -+ goto exit; -+ } -+ -+ // finalize graph connections -+ gf_filter_set_source(dst_filter, reframer_filter, NULL); -+ -+ // run -+ gf_filter_post_process_task(src_filter); -+ gf_fs_run(session); -+ -+ // error handling -+ if (gf_err >= GF_OK) { -+ gf_err = gf_fs_get_last_connect_error(session); -+ if (gf_err >= GF_OK) -+ gf_err = gf_fs_get_last_process_error(session); -+ } -+ -+ // print connections -+ gf_fs_print_debug_info(session, 0); -+ gf_fs_print_connections(session); -+ gf_fs_print_stats(session); -+ exit: -+ gf_fs_del(session); -+ session = NULL -+ -+ return gf_err == GF_OK ? EXIT_SUCCESS : EXIT_FAILURE; -+} -+``` -+ -+Here we note: -+ -+* The use of [gf_filter_set_rt_udta()](https://doxygen.gpac.io/group__fs__filter.html#gac2f040600796f000ac4189fda1c76bc0) to set our runtime data of the mem_in filter. -+* We post the mem_in process task to the session using the [gf_filter_post_process_task(src_filter)](https://doxygen.gpac.io/group__fs__filter.html#ga5806cdb70097f7d5d181884928870842) function. -+ -+## Execution report -+ -+If we define a static h264 frame to use it as a memory input -+ -+```C -+static const uint8_t h264_grey_frame_dsi[] = { -+ 0x01, 0x4d, 0x40, 0x0a, 0xff, 0xe1, 0x00, 0x15, 0x67, 0x4d, -+ 0x40, 0x0a, 0xe8, 0x8f, 0x42, 0x00, 0x00, 0x03, 0x00, 0x02, -+ 0x00, 0x00, 0x03, 0x00, 0x64, 0x1e, 0x24, 0x4a, 0x24, 0x01, -+ 0x00, 0x05, 0x68, 0xeb, 0xc3, 0xcb, 0x20 }; -+``` -+and we run our filters session that will result in the following filters graph being executed. -+ -+![custom filter execution](images/T1_img0.png) -diff --git a/docs/Developers/tutorials/filter-session-intro.md b/docs/Developers/tutorials/filter-session-intro.md -new file mode 100644 -index 00000000..2dd48c7c ---- /dev/null -+++ b/docs/Developers/tutorials/filter-session-intro.md -@@ -0,0 +1,229 @@ -+## About the gpac filters API -+ -+The API enables you to use GPAC capabilities in your own personal code. By calling any filter in gpac’s set of built-in filters, or even create your own personalized custom filters to interact within the media pipelines. This can be useful to interface with other pieces of software or create faster ways of executing some media processing workflows. -+ -+To learn more about general concepts refer to the general wiki page of [gpac](/Filters/filters_general). -+ -+ -+## Build and install the gpac library -+ -+Refer to the gpac [build documentation](/Build/Build-Introduction) provides detailed instructions for compiling GPAC on all supported platforms. -+ -+For linux systems, following the general build, you can use -+ `$ make install-lib` to install the necessary libraries and header files. It will also install a gpac.pc file for pkg-config. With it you can easily build projects that use the gpac library with something like: -+ `$ gcc -o example $(pkg-config --cflags gpac) example.c $(pkg-config --libs gpac)` -+ -+**_NOTE:_**: pkg-config needs to be installed on your machine. -+ -+## Creating a filter session -+ -+The GPAC filter session object allows building media pipelines using multiple sources and destinations and arbitrary filter chains. -+ -+The simplest way to create a session object is to use the gf_fs_new_defaults() function. -+ -+```C -+GF_FilterSession *session = gf_fs_new_defaults(0u); -+if (session == NULL) { -+ fprintf(stderr, "Failed to create GPAC session\n"); -+} -+``` -+ -+This function will create a new filter session, loading parameters from [gpac config](/Filters/core_config). This will also load all available filter registers not blacklisted. -+ -+More information on this function and alternatives can be found on the doxygen [libgpac documentation page](https://doxygen.gpac.io/group__fs__grp.html#gaa7570001b4d4c07ef8883b17d7ed12ca). -+ -+## Loading filters -+ -+### Loading a source filter -+ -+Filters can be processing block ex: (de-)multiplexers, de/encoders, media segmenters (for HTTP Adaptive Streaming), RTSP server. But also they can be file access objects either as a source filter or a destination filter, (eventually pipe and sockets too): -+ -+```C -+GF_Err gf_err = GF_OK; -+GF_Filter *src_filter = gf_fs_load_source(session, "logo.png", NULL, NULL, &gf_err); -+if (gf_err != GF_OK) -+{ -+ fprintf(stderr, "Failed to load filter: %s", gf_error_to_string(gf_err)); -+} -+``` -+ -+Alternatively to [gf_fs_load_source](https://doxygen.gpac.io/group__fs__grp.html#gafce8e6e28696bc68e863bd4153282f80) function we can use the more generic [gf_fs_load_filter](https://doxygen.gpac.io/group__fs__grp.html#ga962fa3063a69ef02e43f06abe14cfe65) and use the [Fin](/Filters/fin) filter (followed by its options with the syntax :opt=val) as follows: -+ -+```C -+GF_Filter *src_filter = gf_fs_load_filter(session, "fin:src=logo.png", &gf_err); -+``` -+ -+### Loading a filter -+ -+Filters are described through a [__gf_filter_register](https://doxygen.gpac.io/struct____gf__filter__register.html) structure. A set of built-in filters are available, and user-defined filters can be added or removed at runtime. -+ -+ -+ -+The filter session keeps an internal graph representation of all available filters and their possible input connections, which is used when resolving connections between filters. -+ -+ -+ -+The following code snippet provides an example to load the [reframer](/Filters/reframer) filter. -+ -+ -+```C -+GF_Filter *reframer_filter = gf_fs_load_filter(session, "reframer", &gf_err); -+if (gf_err != GF_OK) -+{ -+ fprintf(stderr, "Failed to load filter: %s", gf_error_to_string(gf_err)); -+} -+``` -+ -+options can be specified the same way as in the CLI of gpac, as stated with ‘fin’ above. Here is another example: -+ -+```C -+GF_Filter *reframer_filter = gf_fs_load_filter(session, "reframer:rt=on", &gf_err); -+``` -+ -+### Loading a destination filter -+ -+Loading a destination filter, exactly like loading a source filter mentioned above can be done in two different ways: -+ -+ -+ -+by using [gf_fs_load_destination()](https://doxygen.gpac.io/group__fs__grp.html#ga2fd8f1f59622bc781cc81aafee99ee7d) function : -+ -+ -+```C -+GF_Err gf_err = GF_OK; -+GF Filter *src_filter = gf_fs_load_destination(session, "logo_result.png", NULL, NULL, &gf_err); -+if (gf_err != GF_OK) -+{ -+ fprintf(stderr, "Failed to load filter: %s", gf_error_to_string(gf_err)); -+} -+``` -+ -+Or by using the gf_fs_load_filter and use the Fout filter (or any alternative output destinations pipes, sockets.. ) as follows: -+ -+```C -+//load destination filter -+GF_Filter *dst_filter = gf_fs_load_filter(session, "fout:dst=logo_result.png", &gf_err); -+if (gf_err != GF_OK) -+{ -+ fprintf(stderr, "Failed to load filter: %s", gf_error_to_string(gf_err)); -+} -+``` -+ -+## Connecting filters and creating a filter chain -+ -+The function [gf_filter_set_source()](https://doxygen.gpac.io/group__fs__filter.html#ga9d8a5da25284b1325d0210cc04a6a302) serves the purpose of explicitly assigning a source ID to a designated filter. Therefore it serves as an indicator for the graph resolver to link two filters. (it does not do the actual linking). -+ -+It is crucial to invoke this function before establishing a connection from the specified source filter. If the linked filter lacks an assigned ID, the function automatically generates a dynamic one in the format %08X, utilizing the memory address of the filter. -+ -+It is important to note that in multithreaded sessions, proper session locking is essential before the filter creation step and must be unlocked after calling this function. Failure to do so may result in graph resolution occurring before the gf_filter_set_source() is invoked. -+ -+The function accepts three parameters, including the target filter, the filter to link from (source filter), and any link extensions allowed in link syntax, providing flexibility in defining link properties. ( Pid name, properties, types of properties…) -+ -+ -+```C -+gf_filter_set_source(destination_filter, source_filter, NULL); -+``` -+ -+ -+## Disabling the graph resolver and connecting filters manually -+ -+The helper function [gf_fs_set_max_resolution_chain_length()](https://doxygen.gpac.io/group__fs__grp.html#gaef500f3cb6589e05c161d8a50ab20f12) sets the maximum length of a filter chain dynamically loaded to solve connection between two filters. Setting the value to 0 disables dynamic link resolution. You will have to specify the entire chain manually. ( using the above gf_filterset_source() function). -+ -+Note: this will not guarantee the linking, matching of capabilities between the two filters will still be evaluated. This can be very helpful in a testing/development scenario where one may need to disable the dynamic linking to solve compatibility issues between two filters. -+ -+```C -+gf_fs_set_max_resolution_chain_length(session, 0); -+``` -+ -+## Running the session and displaying stats -+ -+By default, the gpac filters session operates in a semi-blocking state. meaning whenever output PID buffers on a filter are all full, the filter is marked as blocked and not scheduled for processing. And whenever one output PID buffer is not full, the filter unblocks. -+ -+The function [gf_fs_run](https://doxygen.gpac.io/group__fs__grp.html#gafdef85e209aef33193e02f83ff5fcbab)() allows for executing the filter session. When the session is non-blocking, it processes tasks of the oldest scheduled filter, manages pending PID connections, and then returns. In the case of a blocking session, gf_fs_run() continues to run until the session concludes or is aborted. The function returns an error if any issues arise during execution, and the last errors can be retrieved using [gf_fs_get_last_connect_error](https://doxygen.gpac.io/group__fs__grp.html#ga026f96a009dd073700b7339fb3ade492) and [gf_fs_get_last_process_error](https://doxygen.gpac.io/group__fs__grp.html#ga2a217d0b7f3f44050f9f78cab10e577d). -+ -+ -+```C -+gf_err = gf_fs_run(session); -+ -+if (gf_err>=GF_OK) { -+ gf_err = gf_fs_get_last_connect_error(session); -+if (gf_err>=GF_OK) -+ gf_err = gf_fs_get_last_process_error(session); -+} -+ -+//print connections -+gf_fs_print_connections(session); -+gf_fs_print_stats(session); -+ -+gf_fs_del(session); -+session = NULL; -+``` -+ -+## Sample code -+ -+In the following example we reproduce a [testsuite example](https://github.com/gpac/testsuite/blob/master/scripts/reframers.sh) that takes a png image as input and calls the reframer filter on the png and writes a new image using writegen and fout filters. -+ -+[ (image file png) fin -> ] -> reframe -> writegen -> [ -> fout (image result file) ] -+ -+**_NOTE:_**: the reframer filter has no functionnnal use in this particular example. the example is just an illustartion of a filters chain. -+ -+```C -+int main(int argc, char *argv[]) -+{ -+ GF_Err gf_err = GF_OK; -+ -+ // session scheme for testing reframer with fin filter -+ GF_FilterSession *session = gf_fs_new_defaults(0u); -+ if (session == NULL) { -+ fprintf(stderr, "Failed to create GPAC session\n"); -+ return EXIT_FAILURE; -+ } -+ -+ // load source filter -+ GF_Filter * src_filter = gf_fs_load_filter(session, "fin:src=logo.png", &gf_err); -+ if (gf_err != GF_OK) -+ { -+ fprintf(stderr, "Failed to load filter: %s", gf_error_to_string(gf_err)); -+ } -+ //load reframer filter -+ GF_Filter *reframer_filter = gf_fs_load_filter(session, "reframer", &gf_err); -+ if (gf_err != GF_OK) -+ { -+ fprintf(stderr, "Failed to load filter: %s", gf_error_to_string(gf_err)); -+ } -+ //load writergen filter -+ GF_Filter *writegen_filter = gf_fs_load_filter(session, "writegen", &gf_err); -+ if (gf_err != GF_OK) -+ { -+ fprintf(stderr, "Failed to load filter: %s", gf_error_to_string(gf_err)); -+ } -+ -+ //load destination filter -+ GF_Filter *dst_filter = gf_fs_load_filter(session, "fout:dst=logo_result.png", &gf_err); -+ if (gf_err != GF_OK) -+ { -+ fprintf(stderr, "Failed to load filter: %s", gf_error_to_string(gf_err)); -+ } -+ -+ gf_filter_set_source(reframer_filter, src_filter, NULL); -+ gf_filter_set_source(writegen_filter,reframer_filter,NULL); -+ gf_filter_set_source(dst_filter ,writegen_filter,NULL); -+ -+ gf_err = gf_fs_run(session); -+ -+ if (gf_err>=GF_OK) -+ { -+ gf_err = gf_fs_get_last_connect_error(session); -+ if (gf_err>=GF_OK) -+ gf_err = gf_fs_get_last_process_error(session); -+ } -+ -+ //print connections -+ gf_fs_print_connections(session); -+ gf_fs_print_stats(session); -+ -+ gf_fs_del(session); -+ session = NULL; -+ return EXIT_SUCCESS; -+} -+``` -diff --git a/docs/Developers/tutorials/images/T1_img0.png b/docs/Developers/tutorials/images/T1_img0.png -new file mode 100644 -index 00000000..edbacfd0 -Binary files /dev/null and b/docs/Developers/tutorials/images/T1_img0.png differ -diff --git a/docs/Filters/Filters.md b/docs/Filters/Filters.md -index 98e1c0e1..0b414689 100644 ---- a/docs/Filters/Filters.md -+++ b/docs/Filters/Filters.md -@@ -1,4 +1,5 @@ --# Overview -+ -+# Overview {:data-level="all"} - - This part of the wiki describes general concepts of the GPAC filter architecture, available starting from GPAC 0.9.0. - -diff --git a/docs/Filters/Rearchitecture.md b/docs/Filters/Rearchitecture.md -index 78d52c1d..e3bda6d5 100644 ---- a/docs/Filters/Rearchitecture.md -+++ b/docs/Filters/Rearchitecture.md -@@ -1,4 +1,4 @@ --# Overview -+# Overview {:data-level="all"} - - For version 0.9.0, GPAC has undergone a major re-architecture of its core, the first one in 15 years! - The re-architecture was done with the following goals: -diff --git a/docs/Filters/a52dec.md b/docs/Filters/a52dec.md -index 96a078da..9615a76f 100644 ---- a/docs/Filters/a52dec.md -+++ b/docs/Filters/a52dec.md -@@ -1,6 +1,6 @@ - - --# A52 decoder -+# A52 decoder {:data-level="all"} - - Register name used to load filter: __a52dec__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/aout.md b/docs/Filters/aout.md -index 3e27748f..500c5fe8 100644 ---- a/docs/Filters/aout.md -+++ b/docs/Filters/aout.md -@@ -1,11 +1,11 @@ - - --# Audio output -+# Audio output {:data-level="all"} - - Register name used to load filter: __aout__ - This filter may be automatically loaded during graph resolution. - --This filter writes a single uncompressed audio input PID to a sound card or other audio output device. -+This filter writes a single PCM (uncompressed) audio input PID to a sound card or other audio output device. - - The longer the audio buffering [bdur](#bdur) is, the longer the audio latency will be (pause/resume). The quality of fast forward audio playback will also be degraded when using large audio buffers. - -diff --git a/docs/Filters/avgen.md b/docs/Filters/avgen.md -index ef512eba..29f83acc 100644 ---- a/docs/Filters/avgen.md -+++ b/docs/Filters/avgen.md -@@ -1,9 +1,9 @@ - - --# AV Counter Generator -+# AV Counter Generator {:data-level="all"} - - Register name used to load filter: __avgen__ --This is a JavaScript filter, not checked during graph resolution and needs explicit loading. -+This is a JavaScript filter. It is not checked during graph resolution and needs explicit loading. - Author: GPAC Team - - This filter generates AV streams representing a counter. Streams can be enabled or disabled using [type](#type). -@@ -11,7 +11,7 @@ The filter is software-based and does not use GPU. - - When [adjust](#adjust) is set, the first video frame is adjusted such that a full circle happens at each exact second according to the system UTC clock. - By default, video UTC and date are computed at each frame generation from current clock and not from frame number. --This will result in broken timing when playing at speeds other than 1.0. -+This will result in broken UTC timing text when playing at speeds other than 1.0. - This can be changed using [lock](#lock). - - Audio beep is generated every second, with octave (2xfreq) of even beep used every 10 seconds. -@@ -47,9 +47,10 @@ If multiple [views](#views) are generated, they are assigned the names `videoN_v - # Options - - __type__ (enum, default: _av_): output selection --* a: audio only --* v: video only --* av: audio and video -+ -+- a: audio only -+- v: video only -+- av: audio and video - - __freq__ (uint, default: _440_): frequency of beep - __freq2__ (uint, default: _659_): frequency of odd beep -@@ -68,9 +69,10 @@ If multiple [views](#views) are generated, they are assigned the names `videoN_v - __dur__ (frac, default: _0/0_): run for the given time in second - __adjust__ (bool, default: _true_): adjust start time to synchronize counter and UTC - __pack__ (enum, default: _no_): packing mode for stereo views -- * no: no packing -- * ss: side by side packing, forces [views](#views) to 2 -- * tb: top-bottom packing, forces [views](#views) to 2 -+ -+- no: no packing -+- ss: side by side packing, forces [views](#views) to 2 -+- tb: top-bottom packing, forces [views](#views) to 2 - - __disparity__ (uint, default: _20_): disparity in pixels between left-most and right-most views - __views__ (uint, default: _1_): number of views -diff --git a/docs/Filters/avidmx.md b/docs/Filters/avidmx.md -index e88564a3..00983807 100644 ---- a/docs/Filters/avidmx.md -+++ b/docs/Filters/avidmx.md -@@ -1,6 +1,6 @@ - - --# AVI demultiplexer -+# AVI demultiplexer {:data-level="all"} - - Register name used to load filter: __avidmx__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/avimx.md b/docs/Filters/avimx.md -index bd5f2fd3..02a46e4d 100644 ---- a/docs/Filters/avimx.md -+++ b/docs/Filters/avimx.md -@@ -1,6 +1,6 @@ - - --# AVI multiplexer -+# AVI multiplexer {:data-level="all"} - - Register name used to load filter: __avimx__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/avmix.md b/docs/Filters/avmix.md -index cd373293..1cf280b3 100644 ---- a/docs/Filters/avmix.md -+++ b/docs/Filters/avmix.md -@@ -1,21 +1,25 @@ - - --# Audio Video Mixer -+# Audio Video Mixer {:data-level="all"} - - Register name used to load filter: __avmix__ --This is a JavaScript filter, not checked during graph resolution and needs explicit loading. -+This is a JavaScript filter. It is not checked during graph resolution and needs explicit loading. - Author: GPAC team - - AVMix is an audio video mixer controlled by an updatable JSON playlist format. The filter can be used to: -+ - - schedule video sequence(s) over time - - mix videos together - - layout of multiple videos - - overlay images, text and graphics over source videos -+ - - All input streams are decoded prior to entering the mixer. -+ - - audio streams are mixed in software - - video streams are composed according to the `gpu` option - - other stream types are not yet supported -+ - - OpenGL hardware acceleration can be used, but the supported feature set is currently not the same with or without GPU. - -@@ -31,30 +35,36 @@ This implies that there shall always be a media to compose, i.e. no "holes" in t - _Note: The playlist is still refreshed in offline mode._ - - When operating live, the mixer will initially wait for video frames to be ready for `lwait` seconds. After this initial timeout, the output frames will indicate: -+ - - 'No signal' if no input is available (no source frames) or no scene is defined - - 'Signal lost' if no new input data has been received for `lwait` on a source -+ - - # Playlist Format - - ## Overview - The main components in a playlist are: --* Media sources and sequences: each source is described by one or more URL to the media data, and each sequence is a set of sources to be played continuously --* Transitions: sources in a sequence can be combined using transitions --* Scenes: a scene describes one graphical object to put on screen and if and how input video are mapped on objects --* Groups: a group is a hierarchy of scenes and groups with positioning properties, and can also be used to create offscreen images reused by other elements --* Timers: a timer can be used to animate scene parameters in various fashions -+ -+- Media sources and sequences: each source is described by one or more URL to the media data, and each sequence is a set of sources to be played continuously -+- Transitions: sources in a sequence can be combined using transitions -+- Scenes: a scene describes one graphical object to put on screen and if and how input video are mapped on objects -+- Groups: a group is a hierarchy of scenes and groups with positioning properties, and can also be used to create offscreen images reused by other elements -+- Timers: a timer can be used to animate scene parameters in various fashions -+ - - The playlist content shall be either a single JSON object or an array of JSON objects, hereafter called root objects. - Root objects types can be indicated through a `type` property: --* seq: a `sequence` object --* url: a `source` object (if used as root, a default `sequence` object will be created) --* scene: a `scene` object --* group: a `group` object --* timer: a `timer` object --* script: a `script` object --* config: a `config` object --* watch: a `watcher` object --* style: a `style` object -+ -+- seq: a `sequence` object -+- url: a `source` object (if used as root, a default `sequence` object will be created) -+- scene: a `scene` object -+- group: a `group` object -+- timer: a `timer` object -+- script: a `script` object -+- config: a `config` object -+- watch: a `watcher` object -+- style: a `style` object -+ - - Except for `style`, the `type` property of root objects is usually not needed as the parser guesses the object types from its properties. - -@@ -65,11 +75,13 @@ Any unrecognized property not starting with `_` will be reported as warning. - - ## Colors - Colors are handled as strings, formatted as: -+ - - the DOM color name (see `gpac -h colors`) - - HTML codes `$RRGGBB` or `#RRGGBB` - - RGB hex vales `0xRRGGBB` - - RGBA hex values `0xAARRGGBB` - - the color `none` is `0x00000000`, its signification depends on the object using it. -+ - - If JS code needs to manipulate colors, use sys.color_lerp and sys.color_component functions. - -@@ -82,37 +94,43 @@ The `JSFun` arguments and return value are dependent on the parent object type. - The parent object is exposed as `this` in `JSFun` and can be used to store context information for the JS code. - - The code can use the global functions and modules defined, especially: --* sys: GPAC system module --* evg: GPAC EVG module --* os: QuickJS OS module --* video_playing: video playing state --* audio_playing: audio playing state --* video_time: output video time --* video_timescale: output video timescale --* video_width: output video width --* video_height: output video height --* audio_time: output audio time --* audio_timescale: output audio timescale --* samplerate: output audio samplerate --* channels: output audio channels --* current_utc_clock: current UTC clock in ms --* get_media_time: gets media time of output (no argument) or of source with id matching the first argument. Return -- * -4: not found -- * -3: not playing -- * -2: in prefetch -- * -1: timing not yet known -- * value: media time in seconds (float) --* resolve_url: resolves URL given in first argument against media playlist URL and returns the resolved url (string) --* get_scene(id): gets scene with given ID --* get_group(id): gets group with given ID --* mouse_over(evt): returns scene under mouse described by a GPAC event, or null if no scene (picking for scenes with perspective projection is not supported) --* mouse_over(x, y): returns scene under coordinates {x, y} in pixels, {0,0} representing the center of the frame, x axis oriented towards the right and y axis oriented towards the top -+ -+- sys: GPAC system module -+- evg: GPAC EVG module -+- os: QuickJS OS module -+- video_playing: video playing state -+- audio_playing: audio playing state -+- video_time: output video time -+- video_timescale: output video timescale -+- video_width: output video width -+- video_height: output video height -+- audio_time: output audio time -+- audio_timescale: output audio timescale -+- samplerate: output audio samplerate -+- channels: output audio channels -+- current_utc_clock: current UTC clock in ms -+- get_media_time: gets media time of output (no argument) or of source with id matching the first argument. Return -+ -+ - -4: not found -+ - -3: not playing -+ - -2: in prefetch -+ - -1: timing not yet known -+ - value: media time in seconds (float) -+ -+- resolve_url: resolves URL given in first argument against media playlist URL and returns the resolved url (string) -+- get_scene(id): gets scene with given ID -+- get_group(id): gets group with given ID -+- mouse_over(evt): returns scene under mouse described by a GPAC event, or null if no scene (picking for scenes with perspective projection is not supported) -+- mouse_over(x, y): returns scene under coordinates {x, y} in pixels, {0,0} representing the center of the frame, x axis oriented towards the right and y axis oriented towards the top -+ - - Scene and group options must be accessed through getters and setters: --* scene.get(prop_name): gets the scene option --* scene.set(prop_name, value): sets the scene option --* group.get(prop_name): gets the group option --* group.set(prop_name, value): sets the group option -+ -+- scene.get(prop_name): gets the scene option -+- scene.set(prop_name, value): sets the scene option -+- group.get(prop_name): gets the group option -+- group.set(prop_name, value): sets the group option -+ - - __Warning: Results are undefined if JS code modifies the scene/group objects in any other way.__ - -@@ -121,23 +139,27 @@ Other playlist objects (as well as scene and group objects) can be queried using - __Warning: There is no protection of global variables and state, write your script carefully!__ - - Additionally, scripts executed within scene modules can modify the internal playlist using: --* remove_element(ID): removes a scene, group, sequence, timer, script or watcher with given ID from playlist --* parse_element(JSON): parses a root playlist element and add it to the current playlist --* parse_scene(JSON, parent): parses a scene and add it to `parent` group if not null or root otherwise --* parse_group(JSON, parent): parses a group and add it to `parent` group if not null or root otherwise --* reload_playlist(JSON): parses a new playlist (an empty JSON array will reset the playlist). If the calling scene is no longer in the resulting scene tree, it will be added to the root of the scene tree. -+ -+- remove_element(ID): removes a scene, group, sequence, timer, script or watcher with given ID from playlist -+- parse_element(JSON): parses a root playlist element and add it to the current playlist -+- parse_scene(JSON, parent): parses a scene and add it to `parent` group if not null or root otherwise -+- parse_group(JSON, parent): parses a group and add it to `parent` group if not null or root otherwise -+- reload_playlist(JSON): parses a new playlist (an empty JSON array will reset the playlist). If the calling scene is no longer in the resulting scene tree, it will be added to the root of the scene tree. -+ - - All these playlist-related functions must be called within the update() callback of the scene module. - - ## Sequences - ### Properties for `sequence` objects: - -- * id (null): sequence identifier -- * loop (0): number of loops for the sequence (0 means no loop, -1 will loop forever) -- * start (0): sequence start time (see notes). If negative, the sequence is not active -- * stop (0): sequence stop time (see notes). If less than `start`, the sequence will stop only when over -- * transition (null): a `transition` object to apply between sources of the sequence -- * seq ([]): array of one or more `source` objects -+ -+ - id (null): sequence identifier -+ - loop (0): number of loops for the sequence (0 means no loop, -1 will loop forever) -+ - start (0): sequence start time (see notes). If negative, the sequence is not active -+ - stop (0): sequence stop time (see notes). If less than `start`, the sequence will stop only when over -+ - transition (null): a `transition` object to apply between sources of the sequence -+ - seq ([]): array of one or more `source` objects -+ - - ### Notes - -@@ -145,50 +167,62 @@ Media source timing does not depend on the media being used by a scene or not, i - This means that a `sequence` not used by any active scene will not be rendered (video nor audio). - - The syntax for `start` and `stop` fields is: --* `now`: resolves to current UTC clock in `live` mode, and to 0 for non-live mode --* date: converted to UTC date in `live` mode, and to 0 for non-live mode --* N: converted to current utc clock (or 0 for non-live mode) plus N seconds UTC --* "N": converted to current utc clock (or 0 for non-live mode) plus N seconds UTC -+ -+- `now`: resolves to current UTC clock in `live` mode, and to 0 for non-live mode -+- date: converted to UTC date in `live` mode, and to 0 for non-live mode -+- N: converted to current utc clock (or 0 for non-live mode) plus N seconds UTC -+- "N": converted to current utc clock (or 0 for non-live mode) plus N seconds UTC -+ - - In 'live' mode, if `start` is set using a UTC date, the sequence will have a start range equal to `MAX(current_UTC - start_in_UTC, 0)`. Some sources may be skipped to fulfill this condition. - This allows different instances of the filter using the same playlist to initialize media time in the same fashion. - - When reloading the playlist: -+ - - if the sequence is active, `start` value is ignored - - if the sequence was not started, `start` value is updated - - if the sequence was over, `start` value is updated only of greater than previous resolved UTC start time. -+ - - ## Sources - ### Properties for `source` objects - --* id (null): source identifier, used when reloading the playlist --* src ([]): list of `sourceURL` describing the URLs to play. Multiple sources will be played in parallel --* start (0.0): media start time in source --* stop (0.0): media stop time in source, ignored if less than or equal to `start` --* mix (true): if true, apply sequence transition or mix effect ratio as audio volume. Otherwise volume is not modified by transitions. --* fade ('inout'): indicate how audio should be faded at stream start/end: -- * in: audio fade-in when playing first frame -- * out: audio fade-out when playing last frame -- * inout: both fade-in and fade-out are enabled -- * other: no audio fade --* keep_alive (false): if using a dedicated gpac process for one or more input, relaunch process(es) at source end if exit code is greater than 2 or if not responding after `rtimeout` --* seek (false): if true and `keep_alive` is active, adjust `start` according to the time elapsed since source start when relaunching process(es) --* prefetch (500): prefetch duration in ms (play before start time of source), 0 for no prefetch --* hold (false): if media duration is known and media stop time is greater than media duration, activate no signal mode until desired stop time is reached (disable transition), otherwise move to next source at end of stream -+ -+- id (null): source identifier, used when reloading the playlist -+- src ([]): list of `sourceURL` describing the URLs to play. Multiple sources will be played in parallel -+- start (0.0): media start time in source -+- stop (0.0): media stop time in source, ignored if less than or equal to `start` -+- mix (true): if true, apply sequence transition or mix effect ratio as audio volume. Otherwise volume is not modified by transitions. -+- fade ('inout'): indicate how audio should be faded at stream start/end: -+ -+ - in: audio fade-in when playing first frame -+ - out: audio fade-out when playing last frame -+ - inout: both fade-in and fade-out are enabled -+ - other: no audio fade -+ -+- keep_alive (false): if using a dedicated gpac process for one or more input, relaunch process(es) at source end if exit code is greater than 2 or if not responding after `rtimeout` -+- seek (false): if true and `keep_alive` is active, adjust `start` according to the time elapsed since source start when relaunching process(es) -+- prefetch (500): prefetch duration in ms (play before start time of source), 0 for no prefetch -+- hold (false): if media duration is known and media stop time is greater than media duration, activate no signal mode until desired stop time is reached (disable transition), otherwise move to next source at end of stream -+ - - ## Source Locations - ### Properties for `sourceURL` objects - --* id (null): source URL identifier, used when reloading the playlist --* in (null): input URL or filter chain to load as string. Words starting with `-` are ignored. The first entry must specify a source URL, and additional filters and links can be specified using `@N[#LINKOPT]` and `@@N[#LINKOPT]` syntax, as in gpac --* port (null): input port for source. Possible values are: -- * pipe: launch a gpac process to play the source using GSF format over pipe -- * tcp, tcpu: launch a gpac process to play the source using GSF format over TCP socket (`tcp`) or unix domain TCP socket (`tcpu`) -- * not specified or empty string: loads source using the current process -- * other: use value as input filter declaration and launch `in` as a dedicated process (e.g. `in="ffmpeg ..." port="pipe://..."`) --* opts (null): options for the gpac process instance when using a dedicated gpac process, ignored otherwise --* media ('all'): filter input media by type, `a` for audio, `v` for video, `t` for text (several characters allowed, e.g. `av` or `va`), `all` accept all input media --* raw (true): indicate if input port is decoded AV (true) or compressed AV (false) when using a dedicated gpac process, ignored otherwise -+ -+- id (null): source URL identifier, used when reloading the playlist -+- in (null): input URL or filter chain to load as string. Words starting with `-` are ignored. The first entry must specify a source URL, and additional filters and links can be specified using `@N[#LINKOPT]` and `@@N[#LINKOPT]` syntax, as in gpac -+- port (null): input port for source. Possible values are: -+ -+ - pipe: launch a gpac process to play the source using GSF format over pipe -+ - tcp, tcpu: launch a gpac process to play the source using GSF format over TCP socket (`tcp`) or unix domain TCP socket (`tcpu`) -+ - not specified or empty string: loads source using the current process -+ - other: use value as input filter declaration and launch `in` as a dedicated process (e.g. `in="ffmpeg ..." port="pipe://..."`) -+ -+- opts (null): options for the gpac process instance when using a dedicated gpac process, ignored otherwise -+- media ('all'): filter input media by type, `a` for audio, `v` for video, `t` for text (several characters allowed, e.g. `av` or `va`), `all` accept all input media -+- raw (true): indicate if input port is decoded AV (true) or compressed AV (false) when using a dedicated gpac process, ignored otherwise -+ - - ### Notes - -@@ -197,12 +231,14 @@ Example - ``` - in=ipid://#foo=bar - ``` -+ - This will use pids having property `foo` with value `bar`, regardless of source filter ID. - - Example - ``` - in=ipid://TEST#foo=bar - ``` -+ - This will use pids having property `foo` with value `bar` coming from filter with ID `TEST`. - - When using the `ipid://` scheme, filter chains cannot be specified (in accepts a single argument) and `port` is ignored. -@@ -215,83 +251,101 @@ __Warning: When launching a child process directly (e.g. `in="ffmpeg ..."`), any - ## 2D and 3D transformation - ### Common properties for `group` and `scene` objects - --* active (true): indicate if the object is active or not. An inactive object will not be refreshed nor rendered --* x (0): horizontal translation --* y (0): vertical translation --* cx (0): horizontal coordinate of rotation center --* cy (0): vertical coordinate of rotation center --* units ('rel'): unit type for `x`, `y`, `cx`, `cy`, `width` and `height`. Possible values are: -- * rel: units are expressed in percent of current reference (see below) -- * pix: units are expressed in pixels --* rotation (0): rotation angle of the scene in degrees --* hscale (1): horizontal scaling factor to apply to the group --* vscale (1): vertical skewing factor to apply to the scene --* hskew (0): horizontal skewing factor to apply to the scene --* vskew (0): vertical skewing factor to apply to the scene --* zorder (0): display order of the scene or of the offscreen group (ignored for regular groups) --* untransform (false): if true, reset parent tree matrix to identity before computing matrix --* mxjs (null): JS code for matrix evaluation --* z (0): depth translation --* cz (0): depth coordinate of rotation center --* zscale (1): depth scaling factor to apply to the group --* orientation ([0, 0, 1, 0]): scale along the given orientation axis [x, y, z, angle] - see VRML `scaleOrientation` --* axis ([0, 0, 1]): rotation axis --* position ([0, 0, auto]): camera location --* target ([0, 0, 0]): point where the camera is looking --* up ([0, 1, 0]): camera up vector --* viewport ([0, 0, 100, 100]): viewport for camera --* fov (45): field of view in degrees --* ar (0): camera aspect ratio, 0 means default --* znear (0): near Z plane distance, 0 means default --* zfar (0): far Z plane distance, 0 means default -+ -+- active (true): indicate if the object is active or not. An inactive object will not be refreshed nor rendered -+- x (0): horizontal translation -+- y (0): vertical translation -+- cx (0): horizontal coordinate of rotation center -+- cy (0): vertical coordinate of rotation center -+- units ('rel'): unit type for `x`, `y`, `cx`, `cy`, `width` and `height`. Possible values are: -+ -+ - rel: units are expressed in percent of current reference (see below) -+ - pix: units are expressed in pixels -+ -+- rotation (0): rotation angle of the scene in degrees -+- hscale (1): horizontal scaling factor to apply to the group -+- vscale (1): vertical skewing factor to apply to the scene -+- hskew (0): horizontal skewing factor to apply to the scene -+- vskew (0): vertical skewing factor to apply to the scene -+- zorder (0): display order of the scene or of the offscreen group (ignored for regular groups) -+- untransform (false): if true, reset parent tree matrix to identity before computing matrix -+- mxjs (null): JS code for matrix evaluation -+- z (0): depth translation -+- cz (0): depth coordinate of rotation center -+- zscale (1): depth scaling factor to apply to the group -+- orientation ([0, 0, 1, 0]): scale along the given orientation axis [x, y, z, angle] - see VRML `scaleOrientation` -+- axis ([0, 0, 1]): rotation axis -+- position ([0, 0, auto]): camera location -+- target ([0, 0, 0]): point where the camera is looking -+- up ([0, 1, 0]): camera up vector -+- viewport ([0, 0, 100, 100]): viewport for camera -+- fov (45): field of view in degrees -+- ar (0): camera aspect ratio, 0 means default -+- znear (0): near Z plane distance, 0 means default -+- zfar (0): far Z plane distance, 0 means default -+ - - ### Coordinate System - - Each group or scene is specified in a local coordinate system for which: -+ - - {0,0} represents the center - - X values increase to the right - - Y values increase to the top - - Z values increase towards the eye of a viewer (Z=X^Y) -+ - - The 2D local transformation matrix is computed as `rotate(cx, cy, rotation)` * `hskew` * `vskew` * `scale(hscale, vscale)` * `translate(x, y)`. - The 3D local transformation matrix is computed as `translate(x, y, z)` * `rotate(cx, cy, cz, rotation)` * `scale(hscale, vscale, zscale)`. Skewing is not supported for 3D. - - The default unit system (`rel`) is relative to the current established reference space: -+ - - by default, the reference space is `{output_width, output_height}`, the origin {0,0} being the center of the output frame - - any group with `reference=true`, `width>0` and `height>0` establishes a new reference space `{group.width, group.height}` -+ - - Inside a reference space `R`, relative coordinates are interpreted as follows: -+ - - For horizontal coordinates, 0 means center, -50 means left edge (`-R.width/2`), 50 means right edge (`+R.width/2`). - - For vertical coordinates, 0 means center, -50 means bottom edge (`-R.height/2`), 50 means top edge (`+R.height/2`). - - For `width`, 100 means `R.width`. - - For `height`, 100 means `R.height`. - - For depth (z and cz) coordinates, the value is a percent of the reference height (`+R.height`). -+ - - If `width=height`, the width is set to the computed height of the object. - If `height=width`, the height is set to the computed width of the object. - For `x` property, the following special values are defined: -+ - - `y` will set the value to the computed `y` of the object. - - `-y` will set the value to the computed `-y` of the object. -+ - For `y` property, the following special values are defined: -+ - - `x` will set the value to the computed `x` of the object. - - `-x` will set the value to the computed `-x` of the object. -+ - - Changing reference is typically needed when creating offscreen groups, so that children relative coordinates are resolved against the offscreen canvas size. - - The selection between 2D and 3D is done automatically based on `z`, `cz`, `axis` and `orientation` values. - The default projection is: -+ - - viewport is the entire output frame - - field of view is PI/4 and aspect ratio is output width/height - - zNear is 0.1 and zFar is 10 times maximum(output width, output height) - - camera up direction is Y axis and camera distance is so that a rectangle facing the camera with `z=0` and size equal to output size covers exactly the output frame. - - depth buffer is disabled -+ - - The default projection can be changed by setting camera properties at group or scene level. When set on a group, all children of the group will use the given camera properties (camera parameters on children are ignored). - The `viewport` parameter is specified as an array `[x, y, w, h]`, where: --* x: horizontal coordinate of the viewport center, in group or scene units, or 'y' to use `y` value, or '-y' to use -`y` value. --* y: vertical coordinate of the viewport center, in group or scene units, or 'x' to use `x` value, or '-x' to use -`x` value. --* w: width of the viewport, in group or scene units, or 'height' to use `h` value. --* h: height of the viewport, in group or scene units, or 'width' to use `w` value. -+ -+- x: horizontal coordinate of the viewport center, in group or scene units, or 'y' to use `y` value, or '-y' to use -`y` value. -+- y: vertical coordinate of the viewport center, in group or scene units, or 'x' to use `x` value, or '-x' to use -`x` value. -+- w: width of the viewport, in group or scene units, or 'height' to use `h` value. -+- h: height of the viewport, in group or scene units, or 'width' to use `w` value. -+ - - ### z-ordering - -@@ -303,33 +357,42 @@ This order is independent of the parent group z-ordering. This allows moving obj - The `JSFun` specified in `mxjs` has a single parameter `tr`. - - The `tr` parameter is an object containing the following variables that the code can modify: --* x, y, z, cx, cy, cz, hscale, vscale, zscale, hskew, vskew, rotation, untransform, axis, orientation: these values are initialized to the current group values in local coordinate system units --* update: if set to true, the object matrix will be recomputed at each frame even if no change in the group or scene parameters (always enforced to true if `use` is set) --* depth: for groups with `use`, indicates the recursion level of the used element. A value of 0 indicates this is a direct render of the element, otherwise it is a render through `use` -+ -+- x, y, z, cx, cy, cz, hscale, vscale, zscale, hskew, vskew, rotation, untransform, axis, orientation: these values are initialized to the current group values in local coordinate system units -+- update: if set to true, the object matrix will be recomputed at each frame even if no change in the group or scene parameters (always enforced to true if `use` is set) -+- depth: for groups with `use`, indicates the recursion level of the used element. A value of 0 indicates this is a direct render of the element, otherwise it is a render through `use` -+ - - The `JSFun` may return false to indicate that the scene should be considered as inactive. Any other return value (undefined or not false) will mark the scene as active. - --EX: "mxjs": "tr.rotation = (get_media_time() % 8) * 360 / 8; tr.update=true;" -+Example -+``` -+"mxjs": "tr.rotation = (get_media_time() % 8) * 360 / 8; tr.update=true;" -+``` - - ## Grouping - ### Properties for `group` objects - --* id (null): group identifier --* scenes ([]): zero or more `group` or `scene` objects, cannot be animated or updated --* opacity (1): group opacity --* offscreen ('none'): set group in offscreen mode, cannot be animated or updated. An offscreen mode is not directly visible but can be used in some texture operations. Possible values are: -- * none: regular group -- * mask: offscreen surface is alpha+grey -- * color: offscreen surface is alpha+colors or colors if `back_color` is set -- * dual: same as `color` but allows group to be displayed --* scaler (1): when opacity or offscreen rendering is used, offscreen canvas size is divided by this factor (>=1) --* back_color ('none'): when opacity or offscreen rendering is used, fill offscreen canvas with the given color. --* width (-1): when opacity or offscreen rendering is used, limit offscreen width to given value (see below) --* height (-1): when opacity or offscreen rendering is used, limit offscreen height to given value (see below) --* use (null): id of group or scene to re-use --* use_depth (-1): number of recursion allowed for the used element, negative means global max branch depth as indicated by `maxdepth` --* reverse (false): reverse scenes order before draw --* reference (false): group is a reference space for relative coordinate of children nodes -+ -+- id (null): group identifier -+- scenes ([]): zero or more `group` or `scene` objects, cannot be animated or updated -+- opacity (1): group opacity -+- offscreen ('none'): set group in offscreen mode, cannot be animated or updated. An offscreen mode is not directly visible but can be used in some texture operations. Possible values are: -+ -+ - none: regular group -+ - mask: offscreen surface is alpha+grey -+ - color: offscreen surface is alpha+colors or colors if `back_color` is set -+ - dual: same as `color` but allows group to be displayed -+ -+- scaler (1): when opacity or offscreen rendering is used, offscreen canvas size is divided by this factor (>=1) -+- back_color ('none'): when opacity or offscreen rendering is used, fill offscreen canvas with the given color. -+- width (-1): when opacity or offscreen rendering is used, limit offscreen width to given value (see below) -+- height (-1): when opacity or offscreen rendering is used, limit offscreen height to given value (see below) -+- use (null): id of group or scene to re-use -+- use_depth (-1): number of recursion allowed for the used element, negative means global max branch depth as indicated by `maxdepth` -+- reverse (false): reverse scenes order before draw -+- reference (false): group is a reference space for relative coordinate of children nodes -+ - - ### Notes - -@@ -347,27 +410,33 @@ When enforcing `width` and `height` on a group with `opacity<1`, the display may - ## Scenes - ### Properties for `scene` objects - --* id (null): scene identifier --* js ('shape'): scene type, either builtin (see below) or path to a JS module, cannot be animated or updated --* sources ([]): list of identifiers of sequences or offscreen groups used by this scene --* width (-1): width of the scene, -1 means reference space width --* height (-1): height of the scene, -1 means reference space height --* mix (null): a `transition` object to apply if more than one source is set, ignored otherwise --* mix_ratio (-1): mix ratio for transition effect, <=0 means first source only, >=1 means second source only --* volume (1.0): audio volume (0: silence, 1: input volume), this value is not clamped by the mixer. --* fade ('inout'): indicate how audio should be faded at scene activate/deactivate: -- * in: audio fade-in when playing first frame after scene activation -- * out: audio fade-out when playing last frame at scene activation -- * inout: both fade-in and fade-out are enabled -- * other: no audio fade --* autoshow (true): automatically deactivate scene when sequences set in `sources` are not active --* nosig ('lost'): enable no-signal message for scenes using sequences: -- * no: disable message -- * lost: display message when signal is lost -- * before: display message if source is not yet active -- * all: always display message if source is inactive --* styles ([]): list of style IDs to use -+ -+- id (null): scene identifier -+- js ('shape'): scene type, either builtin (see below) or path to a JS module, cannot be animated or updated -+- sources ([]): list of identifiers of sequences or offscreen groups used by this scene -+- width (-1): width of the scene, -1 means reference space width -+- height (-1): height of the scene, -1 means reference space height -+- mix (null): a `transition` object to apply if more than one source is set, ignored otherwise -+- mix_ratio (-1): mix ratio for transition effect, <=0 means first source only, >=1 means second source only -+- volume (1.0): audio volume (0: silence, 1: input volume), this value is not clamped by the mixer. -+- fade ('inout'): indicate how audio should be faded at scene activate/deactivate: -+ -+ - in: audio fade-in when playing first frame after scene activation -+ - out: audio fade-out when playing last frame at scene activation -+ - inout: both fade-in and fade-out are enabled -+ - other: no audio fade -+ -+- autoshow (true): automatically deactivate scene when sequences set in `sources` are not active -+- nosig ('lost'): enable no-signal message for scenes using sequences: -+ -+ - no: disable message -+ - lost: display message when signal is lost -+ - before: display message if source is not yet active -+ - all: always display message if source is inactive -+ -+- styles ([]): list of style IDs to use - - any other property exposed by the underlying scene JS module. -+ - - ### Notes - -@@ -381,11 +450,13 @@ If a scene uses one or more sequences and `autoshow` is not set, the scene will - ### JSON syntax - - Properties for `transition` objects: --* id (null): transition identifier --* type: transition type, either builtin (see below) or path to a JS module --* dur: transition duration (transitions always end at source stop time). Ignored if transition is specified for a scene `mix`. --* fun (null): JS code modifying the ratio effect -+ -+- id (null): transition identifier -+- type: transition type, either builtin (see below) or path to a JS module -+- dur: transition duration (transitions always end at source stop time). Ignored if transition is specified for a scene `mix`. -+- fun (null): JS code modifying the ratio effect - - any other property exposed by the underlying transition module. -+ - - ### Notes - -@@ -401,41 +472,52 @@ Example - ## Timers and animations - ### Properties for `timer` objects - --* id (null): id of the timer --* dur (0): duration of the timer in seconds --* loop (false): loops timer when `stop` is not set --* pause (false): pause timer --* start (-1): start time (see notes), negative value means inactive --* stop (-1): stop time (see notes), ignored if less than `start` --* keys ([]): list of keys used for interpolation, ordered list between 0.0 and 1.0 --* anims ([]): list of `animation` objects -+ -+- id (null): id of the timer -+- dur (0): duration of the timer in seconds -+- loop (false): loops timer when `stop` is not set -+- pause (false): pause timer -+- start (-1): start time (see notes), negative value means inactive -+- stop (-1): stop time (see notes), ignored if less than `start` -+- keys ([]): list of keys used for interpolation, ordered list between 0.0 and 1.0 -+- anims ([]): list of `animation` objects -+ - - ### Properties for `animation` objects - --* values ([]): list of values to interpolate, there must be as many values as there are keys --* color (false): indicate the values are color (as strings) --* angle (false): indicate the interpolation factor is an angle in degree, to convert to radians (interpolation ratio multiplied by PI and divided by 180) before interpolation --* mode ('linear') : interpolation mode: -- * linear: linear interpolation between the values -- * discrete: do not interpolate -- * other: JS code modifying the interpolation ratio --* postfun (null): JS code modifying the interpolation result --* end ('freeze'): behavior at end of animation: -- * freeze: keep last animated values -- * restore: restore targets to their initial values --* targets ([]): list of strings indicating targets properties to modify. Syntax is: -- * ID@option: modifies property `option` of object with given ID -- * ID@option[IDX]: modifies value at index `IDX` of array property `option` of object with given ID -+ -+- values ([]): list of values to interpolate, there must be as many values as there are keys -+- color (false): indicate the values are color (as strings) -+- angle (false): indicate the interpolation factor is an angle in degree, to convert to radians (interpolation ratio multiplied by PI and divided by 180) before interpolation -+- mode ('linear') : interpolation mode: -+ -+ - linear: linear interpolation between the values -+ - discrete: do not interpolate -+ - other: JS code modifying the interpolation ratio -+ -+- postfun (null): JS code modifying the interpolation result -+- end ('freeze'): behavior at end of animation: -+ -+ - freeze: keep last animated values -+ - restore: restore targets to their initial values -+ -+- targets ([]): list of strings indicating targets properties to modify. Syntax is: -+ -+ - ID@option: modifies property `option` of object with given ID -+ - ID@option[IDX]: modifies value at index `IDX` of array property `option` of object with given ID -+ - - ### Notes - - Currently, only `scene`, `group`, `transition` and `script` objects can be modified through timers (see playlist updates). - - The syntax for `start` and `stop` fields is: --* `now`: resolves to current UTC clock in `live` mode, and to 0 for non-live mode --* date: converted to UTC date in `live` mode, and to 0 for non-live mode --* N: converted to UTC clock at init plus N seconds for `timer` objects (absolute offset from timeline init) --* "N": converted to current UTC clock plus N seconds (relative offset from current time) with N a positive or negative number -+ -+- `now`: resolves to current UTC clock in `live` mode, and to 0 for non-live mode -+- date: converted to UTC date in `live` mode, and to 0 for non-live mode -+- N: converted to UTC clock at init plus N seconds for `timer` objects (absolute offset from timeline init) -+- "N": converted to current UTC clock plus N seconds (relative offset from current time) with N a positive or negative number -+ - - The `JSFun` specified by `mode` has one input parameter `interp` equal to the interpolation factor and must return the new interpolation factor. - Example -@@ -452,9 +534,11 @@ Example - ## Scripts - ### Properties for `script` objects - --* id (null): id of the script --* script (null): JavaScript code or path to JavaScript file to execute, cannot be animated or updated --* active (true): indicate if script is active or not -+ -+- id (null): id of the script -+- script (null): JavaScript code or path to JavaScript file to execute, cannot be animated or updated -+- active (true): indicate if script is active or not -+ - - ### Notes - -@@ -462,20 +546,28 @@ Script objects allow read and write access to the playlist from script. They cur - - The `JSFun` function specified by `fun` has no input parameter. The return value (default 0) is the number of seconds (float) to wait until next evaluation of the script. - --EX: { "script": "let s=get_scene('s1'); let rot = s.get('rotation'); rot += 10; s.set('rotation', rot); return 2;" } -+Example -+``` -+{ "script": "let s=get_scene('s1'); let rot = s.get('rotation'); rot += 10; s.set('rotation', rot); return 2;" } -+``` -+ - This will change scene `s1` rotation every 2 seconds - - ## Watchers - ### Properties for `watcher` objects - --* id (null): ID of the watcher --* active (true): indicate if watcher is active or not --* watch (""): element watched, formatted as `ID@prop`, with `ID` the element ID and `prop` the property name to watch --* target (""): action for watcher. Allowed syntaxes are: -- * `ID@prop`, `ID@prop[idx]`: copy value to property `prop` of the element `ID` (potentially at index `idx` if specified for arrays) -- * `ID.fun_name`: call function `fun_name` exported from scene module `ID`, using three arguments ['value', 'watchID', 'watchPropName'], no return value check -- * otherwise: action must be JS code, and the resulting `JSFun` has one argument `value` containing the watched value, and no return value check --* with (undefined): for targets in the form `ID@prop`, use this value instead of the watched value -+ -+- id (null): ID of the watcher -+- active (true): indicate if watcher is active or not -+- watch (""): element watched, formatted as `ID@prop`, with `ID` the element ID and `prop` the property name to watch -+- target (""): action for watcher. Allowed syntaxes are: -+ -+ - `ID@prop`, `ID@prop[idx]`: copy value to property `prop` of the element `ID` (potentially at index `idx` if specified for arrays) -+ - `ID.fun_name`: call function `fun_name` exported from scene module `ID`, using three arguments ['value', 'watchID', 'watchPropName'], no return value check -+ - otherwise: action must be JS code, and the resulting `JSFun` has one argument `value` containing the watched value, and no return value check -+ -+- with (undefined): for targets in the form `ID@prop`, use this value instead of the watched value -+ - - ### Notes - -@@ -483,9 +575,11 @@ A watcher can be used to monitor changes in an object in the playlist. - Any object property that can be animated or updated can be monitored by a watcher. - - In addition, the following virtual properties (cannot be read or write) can be watched: --* sequence.active: value is set to true when sequence is activated, and false when deactivated --* source.active: value is set to true when source playback starts, and false when source playback stops --* timer.active: value is set to true when timer starts, and false when timer stops -+ -+- sequence.active: value is set to true when sequence is activated, and false when deactivated -+- source.active: value is set to true when source playback starts, and false when source playback stops -+- timer.active: value is set to true when timer starts, and false when timer stops -+ - - Only the `active` property can be animated or updated in a watcher. - -@@ -493,19 +587,23 @@ Example - ``` - {'watch': 's1@rotation', 'target': 's2@rotation'} - ``` -+ - This will copy s1.rotation to s2.rotation. - - Example - ``` - {'watch': 's1@rotation', 'target': 'get_scene('s2').set('rotation', -value); } --``` -+``` -+ - This will copy the -1*s1.rotation to s2.rotation. - - ### Watching UI events - - Watchers can also be used to monitor GPAC user events by setting `watch` to: -+ - - an event name to monitor, one of `keydown`, `keyup`, `mousemove`, `mouseup`, `mousedown`, `wheel`, `textInput` - - `events` to monitor all events (including internal events). -+ - - For `keyup` and `keydown` events, the key code to watch may additionally be given in parenthesis, e.g. `'watch': 'keyup(T)'`. - -@@ -518,14 +616,17 @@ Example - ``` - {'watch': 'mousemove', 'target': 'let s = mouse_over(evt); get_scene('s2').set('fill', (s && (s.id=='s1') ? 'white' : 'black' );'} - ``` -+ - This will set s1 fill color to white of mouse is over s2 and to black otherwise. - - ## Styles - ### Properties for `style` objects - --* id (null): ID of the style --* forced (false): always apply style even when no modifications --* other: any property to share between scene -+ -+- id (null): ID of the style -+- forced (false): always apply style even when no modifications -+- other: any property to share between scene -+ - - ### Notes - -@@ -533,9 +634,11 @@ A style object allows scenes to share the same values for a given set of propert - - If a scene property has the same name as a style property, the scene property is replaced by the style property. - Styles only apply to scene properties as follows: -+ - - volume, fade, mix_ratio can use style - - all options defined by the scene module can use style - - transformation or other scene properties cannot use style -+ - - Properties of a style object can be animated or updated, but a style object cannot be watched. - -@@ -546,16 +649,20 @@ modified (animation, update), `st2` will only be applied once. - - ## Filter configuration - The playlist may specify configuration options of the filter, using a root object of type 'config': -+ - - property names are the same as the filter options - - property values are given in the native type, or as strings for fractions (format `N/D`), vectors (format `WxH`) or enums - - each declared property overrides the filter option of the same name (whether default or set at filter creation) -+ - - A configuration object in the playlist is only parsed when initially loading the playlist, and ignored when reloading it. - - The following additional properties are defined for testing: --* reload_tests([]): list of playlists to reload --* reload_timeout(1.0): timeout in seconds before playlist reload --* reload_loop (0): number of times to repeat the reload tests (not including original playlist which is not reloaded) -+ -+- reload_tests([]): list of playlists to reload -+- reload_timeout(1.0): timeout in seconds before playlist reload -+- reload_loop (0): number of times to repeat the reload tests (not including original playlist which is not reloaded) -+ - - ## Playlist modification - The playlist file can be modified at any time. -@@ -613,22 +720,29 @@ __Warning: The `updates` file is only read when modified _AFTER_ the initializat - - The `updates` file content shall be either a single JSON object or an array of JSON objects. - The properties of these objects are: --* skip: if true or 1, ignores the update, otherwise apply it --* replace: string identifying the target replacement. Syntax is: -- * ID@name: indicate property name of element with given ID to replace -- * ID@name[idx]: indicate the index in the property name of element with given ID to replace --* with: replacement value, must be of the same type as the target value. -+ -+- skip: if true or 1, ignores the update, otherwise apply it -+- replace: string identifying the target replacement. Syntax is: -+ -+ - ID@name: indicate property name of element with given ID to replace -+ - ID@name[idx]: indicate the index in the property name of element with given ID to replace -+ -+- with: replacement value, must be of the same type as the target value. -+ - - An `id` property cannot be updated. - - The following playlist elements of a playlist can be updated: --* scene: all properties except `js` and read-only module properties --* group: all properties except `scenes` and `offscreen` --* sequence: `start`, `stop`, `loop` and `transition` properties --* timer: `start`, `stop`, `loop`, `pause` and `dur` properties --* transition: all properties -- * for sequence transitions: most of these properties will only be updated at next reload -- * for active scene transitions: whether these changes are applied right away depend on the transition module -+ -+- scene: all properties except `js` and read-only module properties -+- group: all properties except `scenes` and `offscreen` -+- sequence: `start`, `stop`, `loop` and `transition` properties -+- timer: `start`, `stop`, `loop`, `pause` and `dur` properties -+- transition: all properties -+ -+ - for sequence transitions: most of these properties will only be updated at next reload -+ - for active scene transitions: whether these changes are applied right away depend on the transition module -+ - - Example - ``` -@@ -640,18 +754,68 @@ Example - - # Scene modules - -+## Scene `mask` -+This scene sets the canvas alpha mask mode. -+ -+The canvas alpha mask is always full screen. -+ -+In software mode, combining mask effect in record mode and reverse group drawing allows drawing front to back while writing pixels only once. -+ -+Options: -+ -+- mode ('off'): if set, reset clipper otherwise set it to scene position and size -+ -+ - off: mask is disabled -+ - on: mask is enabled and cleared, further draw operations will take place on mask -+ - onkeep: mask is enabled but not cleared, further draw operations will take place on mask -+ - use: mask is enabled, further draw operations will be filtered by mask -+ - use_inv: mask is enabled, further draw operations will be filtered by 1-mask -+ - rec: mask is in record mode, further draw operations will be drawn on output and will set mask value to 0 -+ -+ -+ -+## Scene `clear` -+This scene clears the canvas area covered by the scene with a given color. -+ -+The default clear color of the mixer is `black`. -+ -+The clear area is always axis-aligned in output frame, so when skew/rotation are present, the axis-aligned bounding box of the transformed scene area will be cleared. -+ -+Options: -+ -+- color ('none'): clear color -+ -+ -+## Scene `clip` -+This scene resets the canvas clipper or sets the canvas clipper to the scene area. -+ -+The clipper is always axis-aligned in output frame, so when skew/rotation are present, the axis-aligned bounding box of the transformed clipper will be used. -+ -+Clippers are handled through a stack, resetting the clipper pops the stack and restores previous clipper. -+If a clipper is already defined when setting the clipper, the clipper set is the intersection of the two clippers. -+ -+Options: -+ -+- reset (false): if set, reset clipper otherwise set it to scene position and size -+- stack (true): if false, clipper is set/reset independently of the clipper stack (no intersection, no push/pop of the stack) -+ -+ - ## Scene `shape` - This scene can be used to setup a shape, its outline and specify the fill and strike modes. - Supported shapes include: -+ - - a variety of rectangles, ellipse and other polygons - - custom paths specified from JS - - text -+ - - The color modes for shapes and outlines include: -+ - - texturing using data from input media streams (shape fill only) - - texturing using local JPEG and PNG files (shape fill only) - - solid color - - linear and radial gradients -+ - - The default scene is optimized to fallback to fast blit when no transformations are used on a straight rectangle shape. - -@@ -660,8 +824,10 @@ All options can be updated at run time. - The module accepts 0, 1 or 2 sequences as input. - - Color replacement operations can be specified for base scenes using source videos by specifying the `replace` option. The replacement source is: -+ - - the image data if `img` is set, potentially altered using `*_rep` options - - otherwise a linear gradient if `fill=linear` or a radial gradient if `fill=radial` (NOT supported in GPU mode, use an offscreen group for this). -+ - - __Warning: Color replacement operations cannot be used with transition or mix effects.__ - -@@ -669,32 +835,38 @@ __Warning: Color replacement operations cannot be used with transition or mix ef - - Text can be loaded from file if `text[0]` is an existing local file. - By default all lines are loaded. The number of loaded lines can be specified using `text[1]` as follows: --* 0 or not present: all lines are loaded --* N > 0: only keep the last N lines --* N < 0: only keep the first N lines -+ -+- 0 or not present: all lines are loaded -+- N > 0: only keep the last N lines -+- N < 0: only keep the first N lines -+ - - Text loaded from file will be refreshed whenever the file is modified. - - Predefined keywords can be used in input text, identified as `$KEYWORD$`. The following keywords (case insensitive) are defined: --* time: replaced by UTC date --* ltime: replaced by locale date --* date: replaced by date (Y/M/D) --* ldate: replaced by locale date (Y/M/D) --* mtime: replaced by output media time --* mtime_SRC: replaced by media time of input source `SRC` --* cpu: replaced by current CPU usage of process --* mem: replaced by current memory usage of process --* version: replaced by GPAC version --* fversion: replaced by GPAC full version --* P4CC, PropName: replaced by corresponding PID property -+ -+- time: replaced by UTC date -+- ltime: replaced by locale date -+- date: replaced by date (Y/M/D) -+- ldate: replaced by locale date (Y/M/D) -+- mtime: replaced by output media time -+- mtime_SRC: replaced by media time of input source `SRC` -+- cpu: replaced by current CPU usage of process -+- mem: replaced by current memory usage of process -+- version: replaced by GPAC version -+- fversion: replaced by GPAC full version -+- P4CC, PropName: replaced by corresponding PID property -+ - - ## Custom paths - - Custom paths (shapes) can be created through JS code indicated in 'shape', either inline or through a file. - The following GPAC JS modules are imported: -- - `Sys` as `sys` -- - All EVG as `evg` -- - `os` form QuickJS -+ -+ - `Sys` as `sys` -+ - All EVG as `evg` -+ - `os` form QuickJS -+ - - See [https://doxygen.gpac.io]() for more information on EVG and Sys JS APIs. - -@@ -713,191 +885,186 @@ Example - ``` - "shape": "this.path.add_rectangle(0, 0, this.width, this.height); let el = new evg.Path().ellipse(0, 0, this.width, this.height/3); this.path.add_path(el); this.tx_adjust = true;" - ``` -+ - In this example, the texture mapping will be adjusted to the desired size. - - The global variables and functions are available (c.f. `gpac -h avmix:global`): -- * get_media_time(): return media time in seconds (float) of output -- * get_media_time(SRC): get time of source with id `SRC`, return -4 if not found, -3 if not playing, -2 if in prefetch, -1 if timing not yet known, media time in seconds (float) otherwise -- * current_utc_clock: current UTC time in ms -- * video_time: output video time -- * video_timescale: output video timescale -- * video_width: output video width -- * video_height: output video height -+ -+ - get_media_time(): return media time in seconds (float) of output -+ - get_media_time(SRC): get time of source with id `SRC`, return -4 if not found, -3 if not playing, -2 if in prefetch, -1 if timing not yet known, media time in seconds (float) otherwise -+ - current_utc_clock: current UTC time in ms -+ - video_time: output video time -+ - video_timescale: output video timescale -+ - video_width: output video width -+ - video_height: output video height -+ - - If your path needs to be reevaluated on regular basis, set the value `this.reload` to the timeout to next reload, in milliseconds. - - Options: --* rx (0): horizontal radius for rounded rect in percent of object width if positive, in absolute value if negative, value `y` means use `ry` --* ry (0): vertical radius for rounded rect in percent of object height if positive, in absolute value if negative, value `x` means use `rx` --* tl (1): top-left corner scaler (positive, 0 disables corner) --* bl (1): bottom-left corner scaler (positive, 0 disables corner) --* tr (1): top-right corner scaler (positive, 0 disables corner) --* br (1): bottom-right corner scaler (positive, 0 disables corner) --* rs (false): repeat texture horizontally --* rt (false): repeat texture vertically --* keep_ar (true): keep aspect ratio --* pad_color ('0x00FFFFFF'): color to use for texture padding if `rs` or `rt` are false. Use `none` to use texture edge, `0x00FFFFFF` for transparent (always enforced if source is transparent) --* txmx ([]): texture matrix - all 6 coefficients must be set, i.e. [xx xy tx yx yy ty] --* cmx ([]): color transform - all 20 coefficients must be set in order, i.e. [Mrr, Mrg, Mrb, Mra, Tr, Mgr, Mgg ...] --* line_width (0): line width in percent of width if positive, or absolute value if negative --* line_color ('white'): line color, `linear` for linear gradient and `radial` for radial gradient --* line_pos ('center'): line/shape positioning. Possible values are: -- * center: line is centered around shape -- * outside: line is outside the shape -- * inside: line is inside the shape --* line_dash ('plain'): line dashing mode. Possible values are: -- * plain: no dash -- * dash: predefined dash pattern is used -- * dot: predefined dot pattern is used -- * dashdot: predefined dash-dot pattern is used -- * dashdashdot: predefined dash-dash-dot pattern is used -- * dashdotdot: predefined dash-dot-dot pattern is used --* dashes ([]): dash/dot pattern lengths for custom dashes (these will be multiplied by line size) --* cap ('flat'): line end style. Possible values are: -- * flat: flat end -- * round: round end -- * square: square end (extends limit compared to flat) -- * triangle: triangle end --* join ('miter'): line joint style. Possible values are: -- * miter: miter join (straight lines) -- * round: round join -- * bevel: bevel join -- * bevelmiter: bevel+miter join --* miter_limit (2): miter limit for joint styles --* dash_length (-1): length of path to outline, negative values mean full path --* dash_offset (0): offset in path at which the outline starts --* blit (true): use blit if possible, otherwise EVG texturing. If disabled, always use texturing --* fill ('none'): fill color if used without sources, `linear` for linear gradient and `radial` for radial gradient --* img (''): image for scene without sources or when `replace` is set. Accepts either a path to a local image (JPG or PNG), the ID of an offscreen group or the ID of a sequence --* alpha (1): global texture transparency --* replace (''): if `img` or `fill` is set and shape is using source, set multi texture option. Possible modes are: -- * a, r, g or b: replace alpha source component by indicated component from `img` . If prefix `-` is set, replace by one minus the indicated component -- * m: mix using `mix_ratio` the color components of source and `img` and set alpha to full opacity -- * M: mix using `mix_ratio` all components of source and `img`, including alpha -- * xC: mix source 1 and source 2 using `img` component `C` (`a`, `r`, `g` or `b`) and force alpha to full opacity -- * XC: mix source 1 and source 2 using `img` component `C` (`a`, `r`, `g` or `b`), including alpha -- --* shape ('rect'): shape type. Possible values are: -- * rect: rounded rectangle -- * square: square using smaller width/height value -- * ellipse: ellipse -- * circle: circle using smaller width/height value -- * rhombus: axis-aligned rhombus -- * text: force text mode even if text field is empty -- * rects: same as rounded rectangle but use straight lines for corners -- * other value: JS code for custom path creation, either string or local file name (dynamic reload possible) --* grad_p ([]): gradient positions between 0 and 1 --* grad_c ([]): gradient colors for each position, as strings --* grad_start ([]): start point for linear gradient or center point for radial gradient --* grad_end ([]): end point for linear gradient or radius value for radial gradient --* grad_focal ([]): focal point for radial gradient --* grad_mode ('pad'): gradient mode. Possible values are: -- * pad: color padding outside of gradient bounds -- * spread: mirror gradient outside of bounds -- * repeat: repeat gradient outside of bounds --* text ([]): text lines (UTF-8 only). If not empty, force `shape=text` --* font ([]): font name(s) --* size (20): font size in percent of height (horizontal text) or width (vertical text), or absolute value if negative --* baseline ('alphabetic'): baseline position. Possible values are: -- * alphabetic: alphabetic position of baseline -- * top: baseline at top of EM Box -- * hanging: reserved, _not implemented_ -- * middle: baseline at middle of EM Box -- * ideograph: reserved, _not implemented_ -- * bottom: baseline at bottom of EM Box --* align ('center'): horizontal text alignment. Possible values are: -- * center: center of shape -- * start: start of shape (left or right depending on text direction) -- * end: end of shape (right or left depending on text direction) -- * left: left of shape -- * right: right of shape --* spacing (0): line spacing in percent of height (horizontal text) or width (vertical text), or absolute value if negative --* bold (false): use bold version of font --* italic (false): use italic version of font --* underline (false): underline text --* vertical (false): draw text vertically --* flip (false): flip text vertically --* extend (0): maximum text width in percent of width (for horizontal) or height (for vertical), or absolute value if negative --* keep_ar_rep (true): same as `keep_ar` for local image in replace mode --* txmx_rep ([]): same as `txmx` for local image in replace mode --* cmx_rep ([]): same as `cmx` for local image in replace mode --* pad_color_rep ('none'): same as `pad_color` for local image in replace mode --* rs_rep (false): same as `rs` for local image in replace mode --* rt_rep (false): same as `rt` for local image in replace mode - --## Scene `mask` --This scene sets the canvas alpha mask mode. -- --The canvas alpha mask is always full screen. -- --In software mode, combining mask effect in record mode and reverse group drawing allows drawing front to back while writing pixels only once. -- --Options: --* mode ('off'): if set, reset clipper otherwise set it to scene position and size -- * off: mask is disabled -- * on: mask is enabled and cleared, further draw operations will take place on mask -- * onkeep: mask is enabled but not cleared, further draw operations will take place on mask -- * use: mask is enabled, further draw operations will be filtered by mask -- * use_inv: mask is enabled, further draw operations will be filtered by 1-mask -- * rec: mask is in record mode, further draw operations will be drawn on output and will set mask value to 0 -- -+- rx (0): horizontal radius for rounded rect in percent of object width if positive, in absolute value if negative, value `y` means use `ry` -+- ry (0): vertical radius for rounded rect in percent of object height if positive, in absolute value if negative, value `x` means use `rx` -+- tl (1): top-left corner scaler (positive, 0 disables corner) -+- bl (1): bottom-left corner scaler (positive, 0 disables corner) -+- tr (1): top-right corner scaler (positive, 0 disables corner) -+- br (1): bottom-right corner scaler (positive, 0 disables corner) -+- rs (false): repeat texture horizontally -+- rt (false): repeat texture vertically -+- keep_ar (true): keep aspect ratio -+- pad_color ('0x00FFFFFF'): color to use for texture padding if `rs` or `rt` are false. Use `none` to use texture edge, `0x00FFFFFF` for transparent (always enforced if source is transparent) -+- txmx ([]): texture matrix - all 6 coefficients must be set, i.e. [xx xy tx yx yy ty] -+- cmx ([]): color transform - all 20 coefficients must be set in order, i.e. [Mrr, Mrg, Mrb, Mra, Tr, Mgr, Mgg ...] -+- line_width (0): line width in percent of width if positive, or absolute value if negative -+- line_color ('white'): line color, `linear` for linear gradient and `radial` for radial gradient -+- line_pos ('center'): line/shape positioning. Possible values are: - --## Scene `clear` --This scene clears the canvas area covered by the scene with a given color. -- --The default clear color of the mixer is `black`. -- --The clear area is always axis-aligned in output frame, so when skew/rotation are present, the axis-aligned bounding box of the transformed scene area will be cleared. -- --Options: --* color ('none'): clear color -+ - center: line is centered around shape -+ - outside: line is outside the shape -+ - inside: line is inside the shape -+ -+- line_dash ('plain'): line dashing mode. Possible values are: -+ -+ - plain: no dash -+ - dash: predefined dash pattern is used -+ - dot: predefined dot pattern is used -+ - dashdot: predefined dash-dot pattern is used -+ - dashdashdot: predefined dash-dash-dot pattern is used -+ - dashdotdot: predefined dash-dot-dot pattern is used -+ -+- dashes ([]): dash/dot pattern lengths for custom dashes (these will be multiplied by line size) -+- cap ('flat'): line end style. Possible values are: -+ -+ - flat: flat end -+ - round: round end -+ - square: square end (extends limit compared to flat) -+ - triangle: triangle end -+ -+- join ('miter'): line joint style. Possible values are: -+ -+ - miter: miter join (straight lines) -+ - round: round join -+ - bevel: bevel join -+ - bevelmiter: bevel+miter join -+ -+- miter_limit (2): miter limit for joint styles -+- dash_length (-1): length of path to outline, negative values mean full path -+- dash_offset (0): offset in path at which the outline starts -+- blit (true): use blit if possible, otherwise EVG texturing. If disabled, always use texturing -+- fill ('none'): fill color if used without sources, `linear` for linear gradient and `radial` for radial gradient -+- img (''): image for scene without sources or when `replace` is set. Accepts either a path to a local image (JPG or PNG), the ID of an offscreen group or the ID of a sequence -+- alpha (1): global texture transparency -+- replace (''): if `img` or `fill` is set and shape is using source, set multi texture option. Possible modes are: -+ -+ - a, r, g or b: replace alpha source component by indicated component from `img` . If prefix `-` is set, replace by one minus the indicated component -+ - m: mix using `mix_ratio` the color components of source and `img` and set alpha to full opacity -+ - M: mix using `mix_ratio` all components of source and `img`, including alpha -+ - xC: mix source 1 and source 2 using `img` component `C` (`a`, `r`, `g` or `b`) and force alpha to full opacity -+ - XC: mix source 1 and source 2 using `img` component `C` (`a`, `r`, `g` or `b`), including alpha - --## Scene `clip` --This scene resets the canvas clipper or sets the canvas clipper to the scene area. -- --The clipper is always axis-aligned in output frame, so when skew/rotation are present, the axis-aligned bounding box of the transformed clipper will be used. -- --Clippers are handled through a stack, resetting the clipper pops the stack and restores previous clipper. --If a clipper is already defined when setting the clipper, the clipper set is the intersection of the two clippers. - --Options: --* reset (false): if set, reset clipper otherwise set it to scene position and size --* stack (true): if false, clipper is set/reset independently of the clipper stack (no intersection, no push/pop of the stack) -+ -+- shape ('rect'): shape type. Possible values are: -+ -+ - rect: rounded rectangle -+ - square: square using smaller width/height value -+ - ellipse: ellipse -+ - circle: circle using smaller width/height value -+ - rhombus: axis-aligned rhombus -+ - text: force text mode even if text field is empty -+ - rects: same as rounded rectangle but use straight lines for corners -+ - other value: JS code for custom path creation, either string or local file name (dynamic reload possible) -+ -+- grad_p ([]): gradient positions between 0 and 1 -+- grad_c ([]): gradient colors for each position, as strings -+- grad_start ([]): start point for linear gradient or center point for radial gradient -+- grad_end ([]): end point for linear gradient or radius value for radial gradient -+- grad_focal ([]): focal point for radial gradient -+- grad_mode ('pad'): gradient mode. Possible values are: -+ -+ - pad: color padding outside of gradient bounds -+ - spread: mirror gradient outside of bounds -+ - repeat: repeat gradient outside of bounds -+ -+- text ([]): text lines (UTF-8 only). If not empty, force `shape=text` -+- font ([]): font name(s) -+- size (20): font size in percent of height (horizontal text) or width (vertical text), or absolute value if negative -+- baseline ('alphabetic'): baseline position. Possible values are: -+ -+ - alphabetic: alphabetic position of baseline -+ - top: baseline at top of EM Box -+ - hanging: reserved, _not implemented_ -+ - middle: baseline at middle of EM Box -+ - ideograph: reserved, _not implemented_ -+ - bottom: baseline at bottom of EM Box -+ -+- align ('center'): horizontal text alignment. Possible values are: -+ -+ - center: center of shape -+ - start: start of shape (left or right depending on text direction) -+ - end: end of shape (right or left depending on text direction) -+ - left: left of shape -+ - right: right of shape -+ -+- spacing (0): line spacing in percent of height (horizontal text) or width (vertical text), or absolute value if negative -+- bold (false): use bold version of font -+- italic (false): use italic version of font -+- underline (false): underline text -+- vertical (false): draw text vertically -+- flip (false): flip text vertically -+- extend (0): maximum text width in percent of width (for horizontal) or height (for vertical), or absolute value if negative -+- keep_ar_rep (true): same as `keep_ar` for local image in replace mode -+- txmx_rep ([]): same as `txmx` for local image in replace mode -+- cmx_rep ([]): same as `cmx` for local image in replace mode -+- pad_color_rep ('none'): same as `pad_color` for local image in replace mode -+- rs_rep (false): same as `rs` for local image in replace mode -+- rt_rep (false): same as `rt` for local image in replace mode -+ - - # Transition modules - - ## Transition `gltrans` - GPU only - This transition module wraps gl-transitions, see https://gl-transitions.com/ and `gpac -h avmix:gltrans` for builtin transitions - Options: --* fx (''): effect name for built-in effects, or path to gl-transition GLSL file - --## Transition `mix` - software/GPU --This transition performs cross-fade of source videos -+- fx (''): effect name for built-in effects, or path to gl-transition GLSL file -+ - - ## Transition `swipe` - software/GPU - This transition performs simple 2D affine transformations for source videos transitions, with configurable effect origin - Options: --* from ('left'): direction of video 2 entry. Possible values are: -- * left: from left to right edges -- * right: from right to left edges -- * top: from top to bottom edges -- * bottom: from bottom to top edges -- * topleft: from top-left to bottom-right corners -- * topright: from top-right to bottom-left corners -- * bottomleft: from bottom-left to top-right corners -- * bottomright: from bottom-right to top-left corners -- --* mode ('slide'): how video 2 entry impacts video 1. Possible values are: -- * slide: video 1 position is not modified -- * push: video 2 pushes video 1 away -- * squeeze: video 2 squeezes video 1 along opposite edge -- * grow: video 2 size increases, video 1 not modified -- * swap: video 2 size increases, video 1 size decreases -+ -+- from ('left'): direction of video 2 entry. Possible values are: -+ -+ - left: from left to right edges -+ - right: from right to left edges -+ - top: from top to bottom edges -+ - bottom: from bottom to top edges -+ - topleft: from top-left to bottom-right corners -+ - topright: from top-right to bottom-left corners -+ - bottomleft: from bottom-left to top-right corners -+ - bottomright: from bottom-right to top-left corners -+ -+ -+ -+- mode ('slide'): how video 2 entry impacts video 1. Possible values are: -+ -+ - slide: video 1 position is not modified -+ - push: video 2 pushes video 1 away -+ - squeeze: video 2 squeezes video 1 along opposite edge -+ - grow: video 2 size increases, video 1 not modified -+ - swap: video 2 size increases, video 1 size decreases -+ - -+## Transition `mix` - software/GPU -+This transition performs cross-fade of source videos -+ - ## Transition `fade` - software/GPU - This transition performs fade to/from color of source videos - Options: --* color ('black'): fade color -+ -+- color ('black'): fade color -+ - - - # Options -@@ -905,9 +1072,10 @@ Options: - __pl__ (str, default: _avmix.json_): local playlist file to load - __live__ (bool, default: _true_): live mode - __gpu__ (enum, default: _off_): enable GPU usage -- * off: no GPU -- * mix: only render textured path to GPU, use software rasterizer for the outlines, solid fills and gradients -- * all: try to use GPU for everything -+ -+- off: no GPU -+- mix: only render textured path to GPU, use software rasterizer for the outlines, solid fills and gradients -+- all: try to use GPU for everything - - __thread__ (sint, default: _-1_): use threads for software rasterizer (-1 for all available cores) - __lwait__ (uint, default: _1000_): timeout in ms before considering no signal is present -@@ -919,9 +1087,10 @@ Options: - __fps__ (frac, default: _25_): output video frame rate - __pfmt__ (pfmt, default: _yuv_): output pixel format. Use `rgba` in GPU mode to force alpha channel - __dynpfmt__ (enum, default: _init_): allow dynamic change of output pixel format in software mode -- * off: pixel format is forced to desired value -- * init: pixel format is forced to format of fullscreen input in first generated frame -- * all: pixel format changes each time a full-screen input PID at same resolution is used -+ -+- off: pixel format is forced to desired value -+- init: pixel format is forced to format of fullscreen input in first generated frame -+- all: pixel format changes each time a full-screen input PID at same resolution is used - - __sr__ (uint, default: _44100_): output audio sample rate, 0 disable audio output - __ch__ (uint, default: _2_): number of output audio channels, 0 disable audio output -diff --git a/docs/Filters/bifsdec.md b/docs/Filters/bifsdec.md -index 1ab46e2e..e6c88719 100644 ---- a/docs/Filters/bifsdec.md -+++ b/docs/Filters/bifsdec.md -@@ -1,6 +1,6 @@ - - --# MPEG-4 BIFS decoder -+# MPEG-4 BIFS decoder {:data-level="all"} - - Register name used to load filter: __bifsdec__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/bsagg.md b/docs/Filters/bsagg.md -index 4d041e2d..c6ecae17 100644 ---- a/docs/Filters/bsagg.md -+++ b/docs/Filters/bsagg.md -@@ -1,6 +1,6 @@ - - --# Compressed layered bitstream aggregator -+# Compressed layered bitstream aggregator {:data-level="all"} - - Register name used to load filter: __bsagg__ - This filter is not checked during graph resolution and needs explicit loading. -diff --git a/docs/Filters/bsrw.md b/docs/Filters/bsrw.md -index c41a6d52..583526d1 100644 ---- a/docs/Filters/bsrw.md -+++ b/docs/Filters/bsrw.md -@@ -1,6 +1,6 @@ - - --# Compressed bitstream rewriter -+# Compressed bitstream rewriter {:data-level="all"} - - Register name used to load filter: __bsrw__ - This filter is not checked during graph resolution and needs explicit loading. -@@ -8,24 +8,33 @@ Filters of this class can connect to each-other. - - This filter rewrites some metadata of various bitstream formats. - The filter can currently modify the following properties in video bitstreams: -+ - - MPEG-4 Visual: -- - sample aspect ratio -- - profile and level -+ -+ - sample aspect ratio -+ - profile and level -+ - - AVC|H264, HEVC and VVC: -- - sample aspect ratio -- - profile, level, profile compatibility -- - video format, video fullrange -- - color primaries, transfer characteristics and matrix coefficients (or remove all info) -+ -+ - sample aspect ratio -+ - profile, level, profile compatibility -+ - video format, video fullrange -+ - color primaries, transfer characteristics and matrix coefficients (or remove all info) -+ - - ProRes: -- - sample aspect ratio -- - color primaries, transfer characteristics and matrix coefficients -+ -+ - sample aspect ratio -+ - color primaries, transfer characteristics and matrix coefficients -+ - - Values are by default initialized to -1, implying to keep the related info (present or not) in the bitstream. - A [sar](#sar) value of `0/0` will remove sample aspect ratio info from bitstream if possible. - - The filter can currently modify the following properties in the stream configuration but not in the bitstream: --* HEVC: profile IDC, profile space, general compatibility flags --* VVC: profile IDC, general profile and level indication -+ -+- HEVC: profile IDC, profile space, general compatibility flags -+- VVC: profile IDC, general profile and level indication -+ - - The filter will work in passthrough mode for all other codecs and media types. - -diff --git a/docs/Filters/bssplit.md b/docs/Filters/bssplit.md -index 3f4ef9db..f27bd1f2 100644 ---- a/docs/Filters/bssplit.md -+++ b/docs/Filters/bssplit.md -@@ -1,6 +1,6 @@ - - --# Compressed layered bitstream splitter -+# Compressed layered bitstream splitter {:data-level="all"} - - Register name used to load filter: __bssplit__ - This filter is not checked during graph resolution and needs explicit loading. -@@ -14,27 +14,36 @@ Splitting is based on temporalID value (start from 1) and layerID value (start f - For AVC|H264, layerID is the dependency value, or quality value if `svcqid` is set. - - Each input stream is filtered according to the `ltid` option as follows: --* no value set: input stream is split by layerID, i.e. each layer creates an output --* `all`: input stream is split by layerID and temporalID, i.e. each {layerID,temporalID} creates an output --* `lID`: input stream is split according to layer `lID` value, and temporalID is ignored --* `.tID`: input stream is split according to temporal sub-layer `tID` value and layerID is ignored --* `lID.tID`: input stream is split according to layer `lID` and sub-layer `tID` values -+ -+- no value set: input stream is split by layerID, i.e. each layer creates an output -+- `all`: input stream is split by layerID and temporalID, i.e. each {layerID,temporalID} creates an output -+- `lID`: input stream is split according to layer `lID` value, and temporalID is ignored -+- `.tID`: input stream is split according to temporal sub-layer `tID` value and layerID is ignored -+- `lID.tID`: input stream is split according to layer `lID` and sub-layer `tID` values -+ - - _Note: A tID value of 0 in `ltid` is equivalent to value 1._ - - Multiple values can be given in `ltid`, in which case each value gives the maximum {layerID,temporalID} values for the current layer. - A few examples on an input with 2 layers each with 2 temporal sublayers: --* `ltid=0.2`: this will split the stream in: -- - one stream with {lID=0,tID=1} and {lID=0,tID=2} NAL units -- - one stream with all other layers/substreams --* `ltid=0.1,1.1`: this will split the stream in: -- - one stream with {lID=0,tID=1} NAL units -- - one stream with {lID=0,tID=2}, {lID=1,tID=1} NAL units -- - one stream with the rest {lID=0,tID=2}, {lID=1,tID=2} NAL units --* `ltid=0.1,0.2`: this will split the stream in: -- - one stream with {lID=0,tID=1} NAL units -- - one stream with {lID=0,tID=2} NAL units -- - one stream with the rest {lID=1,tID=1}, {lID=1,tID=2} NAL units -+ -+- `ltid=0.2`: this will split the stream in: -+ -+ - one stream with {lID=0,tID=1} and {lID=0,tID=2} NAL units -+ - one stream with all other layers/substreams -+ -+- `ltid=0.1,1.1`: this will split the stream in: -+ -+ - one stream with {lID=0,tID=1} NAL units -+ - one stream with {lID=0,tID=2}, {lID=1,tID=1} NAL units -+ - one stream with the rest {lID=0,tID=2}, {lID=1,tID=2} NAL units -+ -+- `ltid=0.1,0.2`: this will split the stream in: -+ -+ - one stream with {lID=0,tID=1} NAL units -+ - one stream with {lID=0,tID=2} NAL units -+ - one stream with the rest {lID=1,tID=1}, {lID=1,tID=2} NAL units -+ - - The filter can also be used on AVC and HEVC DolbyVision streams to split base stream and DV RPU/EL. - -diff --git a/docs/Filters/btplay.md b/docs/Filters/btplay.md -index 8f53fc97..92d24014 100644 ---- a/docs/Filters/btplay.md -+++ b/docs/Filters/btplay.md -@@ -1,6 +1,6 @@ - - --# BT/XMT/X3D loader -+# BT/XMT/X3D loader {:data-level="all"} - - Register name used to load filter: __btplay__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/ccdec.md b/docs/Filters/ccdec.md -index 61af34e2..f3d1fa0a 100644 ---- a/docs/Filters/ccdec.md -+++ b/docs/Filters/ccdec.md -@@ -1,6 +1,6 @@ - - --# Closed-Caption decoder -+# Closed-Caption decoder {:data-level="all"} - - Register name used to load filter: __ccdec__ - This filter is not checked during graph resolution and needs explicit loading. -diff --git a/docs/Filters/cdcrypt.md b/docs/Filters/cdcrypt.md -index b65a674a..e6eb50db 100644 ---- a/docs/Filters/cdcrypt.md -+++ b/docs/Filters/cdcrypt.md -@@ -1,6 +1,6 @@ - - --# CENC decryptor -+# CENC decryptor {:data-level="all"} - - Register name used to load filter: __cdcrypt__ - This filter may be automatically loaded during graph resolution. -@@ -18,12 +18,13 @@ When the file is set per PID, the first `CryptInfo` with the same ID is used, ot - - __cfile__ (str): crypt file location - __decrypt__ (enum, default: _full_): decrypt mode (CENC only) --* full: decrypt everything, throwing error if keys are not found --* nokey: decrypt everything for which a key is found, skip decryption otherwise --* skip: decrypt nothing --* pad0: decrypt nothing and replace all crypted bits with 0 --* pad1: decrypt nothing and replace all crypted bits with 1 --* padsc: decrypt nothing and replace all crypted bytes with start codes -+ -+- full: decrypt everything, throwing error if keys are not found -+- nokey: decrypt everything for which a key is found, skip decryption otherwise -+- skip: decrypt nothing -+- pad0: decrypt nothing and replace all crypted bits with 0 -+- pad1: decrypt nothing and replace all crypted bits with 1 -+- padsc: decrypt nothing and replace all crypted bytes with start codes - - __drop_keys__ (uintl): consider keys with given 1-based indexes as not available (multi-key debug) - __kids__ (strl): define KIDs. If `keys` is empty, consider keys with given KID (as hex string) as not available (debug) -diff --git a/docs/Filters/cecrypt.md b/docs/Filters/cecrypt.md -index a78fe72b..0a633e97 100644 ---- a/docs/Filters/cecrypt.md -+++ b/docs/Filters/cecrypt.md -@@ -1,6 +1,6 @@ - - --# CENC encryptor -+# CENC encryptor {:data-level="all"} - - Register name used to load filter: __cecrypt__ - This filter is not checked during graph resolution and needs explicit loading. -diff --git a/docs/Filters/compositor.md b/docs/Filters/compositor.md -index 3b4bd5e7..aeb29227 100644 ---- a/docs/Filters/compositor.md -+++ b/docs/Filters/compositor.md -@@ -1,6 +1,6 @@ - - --# Compositor -+# Compositor {:data-level="all"} - - Register name used to load filter: __compositor__ - This filter may be automatically loaded during graph resolution. -@@ -24,8 +24,10 @@ It will stop generating frames as soon as all input streams are done, unless ext - If audio streams are loaded, an audio output PID is created. - - The default output pixel format in filter mode is: -+ - - `rgb` when the filter is explicitly loaded by the application - - `rgba` when the filter is loaded during a link resolution -+ - This can be changed by assigning the [opfmt](#opfmt) option. - If either [opfmt](#opfmt) specifies alpha channel or [bc](#bc) is not 0 but has alpha=0, background creation in default scene will be skipped. - -@@ -42,13 +44,17 @@ If 3D graphics are used or display driver is forced, OpenGL will be used on offs - # Specific URL syntaxes - - The compositor accepts any URL type supported by GPAC. It also accepts the following schemes for URLs: --* views:// : creates an auto-stereo scene of N views from `views://v1::.::vN` --* mosaic:// : creates a mosaic of N views from `mosaic://v1::.::vN` -+ -+- views:// : creates an auto-stereo scene of N views from `views://v1::.::vN` -+- mosaic:// : creates a mosaic of N views from `mosaic://v1::.::vN` -+ - - For both syntaxes, `vN` can be any type of URL supported by GPAC. - For `views://` syntax, the number of rendered views is set by [nbviews](#nbviews): -+ - - If the URL gives less views than rendered, the views will be repeated - - If the URL gives more views than rendered, the extra views will be ignored -+ - - The compositor can act as a source filter when the [src](#src) option is explicitly set, independently from the operating mode: - Example -@@ -66,9 +72,10 @@ gpac -i mosaic://URL1:URL2 vout - # Options - - __aa__ (enum, default: _all_, updatable): set anti-aliasing mode for raster graphics; whether the setting is applied or not depends on the graphics module or graphic card --* none: no anti-aliasing --* text: anti-aliasing for text only --* all: complete anti-aliasing -+ -+- none: no anti-aliasing -+- text: anti-aliasing for text only -+- all: complete anti-aliasing - - __hlfill__ (uint, default: _0x0_, updatable): set highlight fill color (ARGB) - __hlline__ (uint, default: _0xFF000000_, updatable): set highlight stroke color (ARGB) -@@ -81,14 +88,16 @@ gpac -i mosaic://URL1:URL2 vout - __stress__ (bool, default: _false_, updatable): enable stress mode of compositor (rebuild all vector graphics and texture states at each frame) - __fast__ (bool, default: _false_, updatable): enable speed optimization - whether the setting is applied or not depends on the graphics module / graphic card - __bvol__ (enum, default: _no_, updatable): draw bounding volume of objects --* no: disable bounding box --* box: draws a rectangle (2D) or box (3D) --* aabb: draws axis-aligned bounding-box tree (3D) or rectangle (2D) -+ -+- no: disable bounding box -+- box: draws a rectangle (2D) or box (3D) -+- aabb: draws axis-aligned bounding-box tree (3D) or rectangle (2D) - - __textxt__ (enum, default: _default_, updatable): specify whether text shall be drawn to a texture and then rendered or directly rendered. Using textured text can improve text rendering in 3D and also improve text-on-video like content --* default: use texturing for OpenGL rendering, no texture for 2D rasterizer --* never: never uses text textures --* always: always render text to texture before drawing -+ -+- default: use texturing for OpenGL rendering, no texture for 2D rasterizer -+- never: never uses text textures -+- always: always render text to texture before drawing - - __out8b__ (bool, default: _false_, updatable): convert 10-bit video to 8 bit texture before GPU upload - __drop__ (bool, default: _false_, updatable): drop late frame when drawing. If not set, frames are not dropped until a desynchronization of 1 second or more is observed -@@ -103,9 +112,11 @@ gpac -i mosaic://URL1:URL2 vout - __dur__ (dbl, default: _0_, updatable): duration of generation. Mostly used when no video input is present. Negative values mean number of frames, positive values duration in second, 0 stops as soon as all streams are done - __fsize__ (bool, default: _false_, updatable): force the scene to resize to the biggest bitmap available if no size info is given in the BIFS configuration - __mode2d__ (enum, default: _defer_, updatable): specify whether immediate drawing should be used or not --* immediate: the screen is completely redrawn at each frame (always on if pass-through mode is detected) --* defer: object positioning is tracked from frame to frame and dirty rectangles info is collected in order to redraw the minimal amount of the screen buffer --* debug: only renders changed areas, resetting other areas -+ -+- immediate: the screen is completely redrawn at each frame (always on if pass-through mode is detected) -+- defer: object positioning is tracked from frame to frame and dirty rectangles info is collected in order to redraw the minimal amount of the screen buffer -+- debug: only renders changed areas, resetting other areas -+ - Whether the setting is applied or not depends on the graphics module and player mode - - __amc__ (bool, default: _true_): audio multichannel support; if disabled always down-mix to stereo. Useful if the multichannel output does not work properly -@@ -128,76 +139,86 @@ Whether the setting is applied or not depends on the graphics module and player - __nojs__ (bool, default: _false_): disable javascript - __noback__ (bool, default: _false_): ignore background nodes and viewport fill (useful when dumping to PNG) - __ogl__ (enum, default: _auto_, updatable): specify 2D rendering mode --* auto: automatically decides between on, off and hybrid based on content --* off: disables OpenGL; 3D will not be rendered --* on: uses OpenGL for all graphics; this will involve polygon tesselation and 2D graphics will not look as nice as 2D mode --* hybrid: the compositor performs software drawing of 2D graphics with no textures (better quality) and uses OpenGL for all 2D objects with textures and 3D objects -+ -+- auto: automatically decides between on, off and hybrid based on content -+- off: disables OpenGL; 3D will not be rendered -+- on: uses OpenGL for all graphics; this will involve polygon tesselation and 2D graphics will not look as nice as 2D mode -+- hybrid: the compositor performs software drawing of 2D graphics with no textures (better quality) and uses OpenGL for all 2D objects with textures and 3D objects - - __pbo__ (bool, default: _false_, updatable): enable PixelBufferObjects to push YUV textures to GPU in OpenGL Mode. This may slightly increase the performances of the playback - __nav__ (enum, default: _none_, updatable): override the default navigation mode of MPEG-4/VRML (Walk) and X3D (Examine) --* none: disables navigation --* walk: 3D world walk --* fly: 3D world fly (no ground detection) --* pan: 2D/3D world zoom/pan --* game: 3D world game (mouse gives walk direction) --* slide: 2D/3D world slide --* exam: 2D/3D object examine --* orbit: 3D object orbit --* vr: 3D world VR (yaw/pitch/roll) -+ -+- none: disables navigation -+- walk: 3D world walk -+- fly: 3D world fly (no ground detection) -+- pan: 2D/3D world zoom/pan -+- game: 3D world game (mouse gives walk direction) -+- slide: 2D/3D world slide -+- exam: 2D/3D object examine -+- orbit: 3D object orbit -+- vr: 3D world VR (yaw/pitch/roll) - - __linegl__ (bool, default: _false_, updatable): indicate that outlining shall be done through OpenGL pen width rather than vectorial outlining - __epow2__ (bool, default: _true_, updatable): emulate power-of-2 textures for OpenGL (old hardware). Ignored if OpenGL rectangular texture extension is enabled --* yes: video texture is not resized but emulated with padding. This usually speeds up video mapping on shapes but disables texture transformations --* no: video is resized to a power of 2 texture when mapping to a shape -+ -+- yes: video texture is not resized but emulated with padding. This usually speeds up video mapping on shapes but disables texture transformations -+- no: video is resized to a power of 2 texture when mapping to a shape - - __paa__ (bool, default: _false_, updatable): indicate whether polygon antialiasing should be used in full antialiasing mode. If not set, only lines and points antialiasing are used - __bcull__ (enum, default: _on_, updatable): indicate whether backface culling shall be disable or not --* on: enables backface culling --* off: disables backface culling --* alpha: only enables backface culling for transparent meshes -+ -+- on: enables backface culling -+- off: disables backface culling -+- alpha: only enables backface culling for transparent meshes - - __wire__ (enum, default: _none_, updatable): wireframe mode --* none: objects are drawn as solid --* only: objects are drawn as wireframe only --* solid: objects are drawn as solid and wireframe is then drawn -+ -+- none: objects are drawn as solid -+- only: objects are drawn as wireframe only -+- solid: objects are drawn as solid and wireframe is then drawn - - __norms__ (enum, default: _none_, updatable): normal vector drawing for debug --* none: no normals drawn --* face: one normal per face drawn --* vertex: one normal per vertex drawn -+ -+- none: no normals drawn -+- face: one normal per face drawn -+- vertex: one normal per vertex drawn - - __rext__ (bool, default: _true_, updatable): use non power of two (rectangular) texture GL extension - __cull__ (bool, default: _true_, updatable): use aabb culling: large objects are rendered in multiple calls when not fully in viewport - __depth_gl_scale__ (flt, default: _100_, updatable): set depth scaler - __depth_gl_type__ (enum, default: _none_, updatable): set geometry type used to draw depth video --* none: no geometric conversion --* point: compute point cloud from pixel+depth --* strip: same as point but thins point set -+ -+- none: no geometric conversion -+- point: compute point cloud from pixel+depth -+- strip: same as point but thins point set - - __nbviews__ (uint, default: _0_, updatable): number of views to use in stereo mode - __stereo__ (enum, default: _none_, updatable): stereo output type. If your graphic card does not support OpenGL shaders, only `top` and `side` modes will be available --* none: no stereo --* side: images are displayed side by side from left to right --* top: images are displayed from top (laft view) to bottom (right view) --* hmd: same as side except that view aspect ratio is not changed --* ana: standard color anaglyph (red for left view, green and blue for right view) is used (forces views=2) --* cols: images are interleaved by columns, left view on even columns and left view on odd columns (forces views=2) --* rows: images are interleaved by columns, left view on even rows and left view on odd rows (forces views=2) --* spv5: images are interleaved by for SpatialView 5 views display, fullscreen mode (forces views=5) --* alio8: images are interleaved by for Alioscopy 8 views displays, fullscreen mode (forces views=8) --* custom: images are interleaved according to the shader file indicated in [mvshader](#mvshader). The shader is exposed each view as uniform sampler2D gfViewX, where X is the view number starting from the left -+ -+- none: no stereo -+- side: images are displayed side by side from left to right -+- top: images are displayed from top (laft view) to bottom (right view) -+- hmd: same as side except that view aspect ratio is not changed -+- ana: standard color anaglyph (red for left view, green and blue for right view) is used (forces views=2) -+- cols: images are interleaved by columns, left view on even columns and left view on odd columns (forces views=2) -+- rows: images are interleaved by columns, left view on even rows and left view on odd rows (forces views=2) -+- spv5: images are interleaved by for SpatialView 5 views display, fullscreen mode (forces views=5) -+- alio8: images are interleaved by for Alioscopy 8 views displays, fullscreen mode (forces views=8) -+- custom: images are interleaved according to the shader file indicated in [mvshader](#mvshader). The shader is exposed each view as uniform sampler2D gfViewX, where X is the view number starting from the left - - __mvshader__ (str, updatable): file path to the custom multiview interleaving shader - __fpack__ (enum, default: _none_, updatable): default frame packing of input video --* none: no frame packing --* top: top bottom frame packing --* side: side by side packing -+ -+- none: no frame packing -+- top: top bottom frame packing -+- side: side by side packing - - __camlay__ (enum, default: _offaxis_, updatable): camera layout in multiview modes --* straight: camera is moved along a straight line, no rotation --* offaxis: off-axis projection is used --* linear: camera is moved along a straight line with rotation --* circular: camera is moved along a circle with rotation -+ -+- straight: camera is moved along a straight line, no rotation -+- offaxis: off-axis projection is used -+- linear: camera is moved along a straight line with rotation -+- circular: camera is moved along a circle with rotation - - __iod__ (flt, default: _6.4_, updatable): inter-ocular distance (eye separation) in cm (distance between the cameras). - __rview__ (bool, default: _false_, updatable): reverse view order -@@ -205,9 +226,10 @@ Whether the setting is applied or not depends on the graphics module and player - __tvtn__ (uint, default: _30_, updatable): number of point sampling for tile visibility algorithm - __tvtt__ (uint, default: _8_, updatable): number of points above which the tile is considered visible - __tvtd__ (enum, default: _off_, updatable): debug tiles and full coverage SRD --* off: regular draw --* partial: only displaying partial tiles, not the full sphere video --* full: only display the full sphere video -+ -+- off: regular draw -+- partial: only displaying partial tiles, not the full sphere video -+- full: only display the full sphere video - - __tvtf__ (bool, default: _false_, updatable): force all tiles to be considered visible, regardless of viewpoint - __fov__ (flt, default: _1.570796326794897_, updatable): default field of view for VR -@@ -221,17 +243,19 @@ Whether the setting is applied or not depends on the graphics module and player - __dpi__ (v2di, default: _96x96_, updatable): default dpi if not indicated by video output - __dbgpvr__ (flt, default: _0_, updatable): debug scene used by PVR addon - __player__ (enum, default: _no_): set compositor in player mode --* no: regular mode --* base: player mode --* gui: player mode with GUI auto-start -+ -+- no: regular mode -+- base: player mode -+- gui: player mode with GUI auto-start - - __noaudio__ (bool, default: _false_): disable audio output - __opfmt__ (pfmt, default: _none_, Enum: none|yuv420|yvu420|yuv420_10|yuv422|yuv422_10|yuv444|yuv444_10|uyvy|vyuy|yuyv|yvyu|uyvl|vyul|yuyl|yvyl|nv12|nv21|nv1l|nv2l|yuva|yuvd|yuv444a|yuv444p|v308|yuv444ap|v408|v410|v210|grey|algr|gral|rgb4|rgb5|rgb6|rgba|argb|bgra|abgr|rgb|bgr|xrgb|rgbx|xbgr|bgrx|rgbd|rgbds|uncv): pixel format to use for output. Ignored in [player](#player) mode - - __drv__ (enum, default: _auto_): indicate if graphics driver should be used --* no: never loads a graphics driver, software blit is used, no 3D possible (in player mode, disables OpenGL) --* yes: always loads a graphics driver, output pixel format will be RGB (in player mode, same as `auto`) --* auto: decides based on the loaded content -+ -+- no: never loads a graphics driver, software blit is used, no 3D possible (in player mode, disables OpenGL) -+- yes: always loads a graphics driver, output pixel format will be RGB (in player mode, same as `auto`) -+- auto: decides based on the loaded content - - __src__ (cstr): URL of source content - __gaze_x__ (sint, default: _0_, updatable): horizontal gaze coordinate (0=left, width=right) -diff --git a/docs/Filters/core_config.md b/docs/Filters/core_config.md -index 497284ff..094db8de 100644 ---- a/docs/Filters/core_config.md -+++ b/docs/Filters/core_config.md -@@ -1,20 +1,24 @@ - - --# Configuration file -+# Configuration file {:data-level="all"} - - GPAC uses a configuration file to modify default options of libgpac and filters. This file is called `GPAC.cfg` and is located: -+ - - on Windows platforms, in `C:\Users\FOO\AppData\Roaming\GPAC` or in `C:\Program Files\GPAC`. - - on iOS platforms, in a .gpac folder in the app storage directory. - - on Android platforms, in `/sdcard/GPAC/` if this directory exists, otherwise in `/data/data/io.gpac.gpac/GPAC`. - - on other platforms, in a `$HOME/.gpac/`. -+ - - Applications in GPAC can also specify a different configuration file through the [-p](gpac_general/#p) profile option. EX gpac -p=foo [] - This will load configuration from $HOME/.gpac/foo/GPAC.cfg, creating it if needed. - The reserved name `0` is used to disable configuration file writing. - - The configuration file is structured in sections, each made of one or more keys: -+ - - section `foo` is declared as `[foo]\n` - - key `bar` with value `N` is declared as `bar=N\n`. The key value `N` is not interpreted and always handled as ASCII text. -+ - - By default the configuration file only holds a few system specific options and directories. It is possible to serialize the entire set of options to the configuration file, using [-wc](gpac_general/#wc) [-wf](gpac_general/#wf). - This should be avoided as the resulting configuration file size will be quite large, hence larger memory usage for the applications. -@@ -29,6 +33,7 @@ Example - [core] - threads=2 - ``` -+ - Setting this in the config file is equivalent to using `-threads=2`. - The options specified at prompt overrides the value of the config file. - -@@ -40,6 +45,7 @@ Example - [filter@rtpin] - interleave=yes - ``` -+ - This will force the rtp input filter to always request RTP over RTSP by default. - To generate a configuration file with all filters options serialized, use [-wf](gpac_general/#wf). - -@@ -51,11 +57,13 @@ Example - ``` - --buffer=100 -i file vout aout - ``` -+ - This is equivalent to specifying `vout:buffer=100 aout:buffer=100`. - Example - ``` - --buffer=100 -i file vout aout:buffer=10 - ``` -+ - This is equivalent to specifying `vout:buffer=100 aout:buffer=10`. - - __Warning: This syntax only applies to regular filter options. It cannot be used with builtin shortcuts (gfreg, enc, ...).__ -@@ -65,6 +73,7 @@ Example - ``` - --profile=Baseline -i file.cmp -o dump.264 - ``` -+ - This is equivalent to specifying `-o dump.264:profile=Baseline`. - - For both syntaxes, it is possible to specify the filter registry name of the option, using `--FNAME:OPTNAME=VAL` or `--FNAME@OPTNAME=VAL`. -@@ -73,4 +82,5 @@ Example - ``` - --flist@timescale=100 -i plist1 -i plist2 -o live.mpd - ``` -+ - This will set the timescale option on the playlists filters but not on the dasher filter. -diff --git a/docs/Filters/core_logs.md b/docs/Filters/core_logs.md -index cf6bbf54..e1ff0ae4 100644 ---- a/docs/Filters/core_logs.md -+++ b/docs/Filters/core_logs.md -@@ -1,7 +1,7 @@ - - # GPAC Log System - --# libgpac logs options: -+# libgpac logs options: {:data-level="all"} - - __-noprog__: disable progress messages - __-quiet__: disable all messages, including errors -@@ -13,40 +13,44 @@ - You can independently log different tools involved in a session. - log_args is formatted as a colon (':') separated list of `toolX[:toolZ]@levelX` - `levelX` can be one of: --* quiet: skip logs --* error: logs only error messages --* warning: logs error+warning messages --* info: logs error+warning+info messages --* debug: logs all messages -+ -+- quiet: skip logs -+- error: logs only error messages -+- warning: logs error+warning messages -+- info: logs error+warning+info messages -+- debug: logs all messages -+ - - `toolX` can be one of: --* core: libgpac core --* mutex: log all mutex calls --* mem: GPAC memory tracker --* module: GPAC modules (av out, font engine, 2D rasterizer) --* filter: filter session debugging --* sched: filter session scheduler debugging --* codec: codec messages (used by encoder and decoder filters) --* coding: bitstream formats (audio, video, scene) --* container: container formats (ISO File, MPEG-2 TS, AVI, ...) and multiplexer/demultiplexer filters --* network: TCP/UDP sockets and TLS --* http: HTTP traffic --* cache: HTTP cache subsystem --* rtp: RTP traffic --* dash: HTTP streaming logs --* route: ROUTE (ATSC3) debugging --* media: messages from generic filters and reframer/rewriter filters --* parser: textual parsers (svg, xmt, bt, ...) --* mmio: I/O management (AV devices, file, pipes, OpenGL) --* audio: audio renderer/mixer/output --* script: script engine except console log --* console: script console log --* scene: scene graph and scene manager --* compose: composition engine (2D, 3D, etc) --* ctime: media and SMIL timing info from composition engine --* interact: interaction messages (UI events and triggered DOM events and VRML route) --* rti: run-time stats of compositor --* all: all tools logged - other tools can be specified afterwards. -+ -+- core: libgpac core -+- mutex: log all mutex calls -+- mem: GPAC memory tracker -+- module: GPAC modules (av out, font engine, 2D rasterizer) -+- filter: filter session debugging -+- sched: filter session scheduler debugging -+- codec: codec messages (used by encoder and decoder filters) -+- coding: bitstream formats (audio, video, scene) -+- container: container formats (ISO File, MPEG-2 TS, AVI, ...) and multiplexer/demultiplexer filters -+- network: TCP/UDP sockets and TLS -+- http: HTTP traffic -+- cache: HTTP cache subsystem -+- rtp: RTP traffic -+- dash: HTTP streaming logs -+- route: ROUTE (ATSC3) debugging -+- media: messages from generic filters and reframer/rewriter filters -+- parser: textual parsers (svg, xmt, bt, ...) -+- mmio: I/O management (AV devices, file, pipes, OpenGL) -+- audio: audio renderer/mixer/output -+- script: script engine except console log -+- console: script console log -+- scene: scene graph and scene manager -+- compose: composition engine (2D, 3D, etc) -+- ctime: media and SMIL timing info from composition engine -+- interact: interaction messages (UI events and triggered DOM events and VRML route) -+- rti: run-time stats of compositor -+- all: all tools logged - other tools can be specified afterwards. -+ - The special keyword `ncl` can be set to disable color logs. - The special keyword `strict` can be set to exit at first error. - -@@ -54,6 +58,7 @@ Example - ``` - -logs=all@info:dash@debug:ncl - ``` -+ - This moves all log to info level, dash to debug level and disable color logs - - __-proglf__: use new line at each progress messages -diff --git a/docs/Filters/core_options.md b/docs/Filters/core_options.md -index 083c90c8..10ea9de9 100644 ---- a/docs/Filters/core_options.md -+++ b/docs/Filters/core_options.md -@@ -1,7 +1,7 @@ - --# GPAC Core Options -+# GPAC Core Options - --# libgpac core options: -+# libgpac core options: {:data-level="all"} - - __-noprog__: disable progress messages - __-quiet__: disable all messages, including errors -@@ -14,12 +14,13 @@ - __-ifce__ (string): set default multicast interface (default is ANY), either an IP address or a device name as listed by `gpac -h net`. Prefix '+' will force using IPv6 for dual interface - __-lang__ (string): set preferred language - __-cfg__,__-opt__ (string): get or set configuration file value. The string parameter can be formatted as: --* `section:key=val`: set the key to a new value --* `section:key=null`, `section:key`: remove the key --* `section=null`: remove the section --* no argument: print the entire configuration file --* `section`: print the given section --* `section:key`: print the given `key` in `section` (section can be set to `*`)- `*:key`: print the given `key` in all sections -+ -+- `section:key=val`: set the key to a new value -+- `section:key=null`, `section:key`: remove the key -+- `section=null`: remove the section -+- no argument: print the entire configuration file -+- `section`: print the given section -+- `section:key`: print the given `key` in `section` (section can be set to `*`)- `*:key`: print the given `key` in all sections - - __-no-save__: discard any changes made to the config file upon exit - __-mod-reload__: unload / reload module shared libs when no longer used -@@ -40,45 +41,50 @@ - __-xml-max-csize__ (int, default: __100k__): maximum XML content or attribute size - __-netcap__ (string): set packet capture and filtering rules formatted as [CFG][RULES]. Each `-netcap` argument will define a configuration - [CFG] is an optional comma-separated list of: --* id=ID: ID (string) for this configuration. If NULL, configuration will apply to all sockets not specifying a netcap ID --* src=F: read packets from `F`, as produced by GPAC or a pcap or pcapng file --* dst=F: output packets to `F` (GPAC or pcap/pcapng file), cannot be set if src is set --* loop[=N]: loop capture file N times, or forever if N is not set or negative --* nrt: disable real-time playback -+ -+- id=ID: ID (string) for this configuration. If NULL, configuration will apply to all sockets not specifying a netcap ID -+- src=F: read packets from `F`, as produced by GPAC or a pcap or pcapng file -+- dst=F: output packets to `F` (GPAC or pcap/pcapng file), cannot be set if src is set -+- loop[=N]: loop capture file N times, or forever if N is not set or negative -+- nrt: disable real-time playback -+ - [RULES] is an optional list of `[OPT,OPT2...]` with OPT in: --* m=K: set rule mode - `K` can be `r` for reception only (default), `w` for send only or `rw` for both --* s=K: set packet start range to `K` --* e=K: set packet end range to `K` - only used for `r` and `f` rules, 0 or not set means rule apply until end --* n=K: set number of packets to drop to `K` - not set, 0 or 1 means single packet --* r=K: random drop `n` packet every `K` --* f=K: drop first `n` packets every `K` --* d=K: reorder `n` packets after the next `K` packets, can be used with `f` or `r` rules --* p=K: filter packets on port `K` only, if not set the rule applies to all packets --* o=K: patch packet instead of droping (always true for TCP), replacing byte at offset `K` (0 is first byte, <0 for random) --* v=K: set patch byte value to `K` (hexa) or negative value for random (default) -+ -+- m=K: set rule mode - `K` can be `r` for reception only (default), `w` for send only or `rw` for both -+- s=K: set packet start range to `K` -+- e=K: set packet end range to `K` - only used for `r` and `f` rules, 0 or not set means rule apply until end -+- n=K: set number of packets to drop to `K` - not set, 0 or 1 means single packet -+- r=K: random drop `n` packet every `K` -+- f=K: drop first `n` packets every `K` -+- d=K: reorder `n` packets after the next `K` packets, can be used with `f` or `r` rules -+- p=K: filter packets on port `K` only, if not set the rule applies to all packets -+- o=K: patch packet instead of droping (always true for TCP), replacing byte at offset `K` (0 is first byte, <0 for random) -+- v=K: set patch byte value to `K` (hexa) or negative value for random (default) -+ - - Example - ``` - -netcap=dst=dump.gpc --``` -+``` -+ - This will record packets to dump.gpc - - Example - ``` - -netcap=src=dump.gpc,id=NC1 -i session1.sdp:NCID=NC1 -i session2.sdp --``` -+``` -+ - This will read packets from dump.gpc only for session1.sdp and let session2.sdp use regular sockets - - Example - ``` - -netcap=[p=1234,s=100,n=20][r=200,s=500,o=10,v=FE] --``` -+``` -+ - This will use regular network interface and drop packets 100 to 119 on port 1234 and patch one random packet every 200 starting from packet 500, setting byte 10 to FE - - __-cache__ (string): cache directory location --__-proxy-on__: enable HTTP proxy --__-proxy-name__ (string): set HTTP proxy address --__-proxy-port__ (int, default: __80__): set HTTP proxy port -+__-proxy__ (string): set HTTP proxy server address and port - __-maxrate__ (int): set max HTTP download rate in bits per sec. 0 means unlimited - __-no-cache__: disable HTTP caching - __-offline-cache__: enable offline HTTP caching (no re-validation of existing resource in cache) -@@ -95,25 +101,33 @@ This will use regular network interface and drop packets 100 to 119 on port 1234 - __-dm-threads__: force using threads for async download requests rather than session scheduler - __-cte-rate-wnd__ (int, default: __20__): set window analysis length in milliseconds for chunk-transfer encoding rate estimation - __-cred__ (string): path to 128 bits key for credential storage -+__-no-h2__: disable HTTP2 -+__-no-h2c__: disable HTTP2 upgrade (i.e. over non-TLS) -+__-h2-copy__: enable intermediate copy of data in nghttp2 (default is disabled but may report as broken frames in wireshark) -+__-curl__: use CURL instead of GPAC HTTP stack -+__-no-h3__: disable HTTP3 (CURL only) - __-dbg-edges__: log edges status in filter graph before dijkstra resolution (for debug). Edges are logged as edge_source(status(disable_depth), weight, src_cap_idx -> dst_cap_idx) - __-full-link__: throw error if any PID in the filter graph cannot be linked - __-no-dynf__: disable dynamically loaded filters - __-no-block__ (Enum, default: __no__): disable blocking mode of filters --* no: enable blocking mode --* fanout: disable blocking on fan-out, unblocking the PID as soon as one of its destinations requires a packet --* all: disable blocking -+ -+- no: enable blocking mode -+- fanout: disable blocking on fan-out, unblocking the PID as soon as one of its destinations requires a packet -+- all: disable blocking - - __-no-reg__: disable regulation (no sleep) in session - __-no-reassign__: disable source filter reassignment in PID graph resolution - __-sched__ (Enum, default: __free__): set scheduler mode --* free: lock-free queues except for task list (default) --* lock: mutexes for queues when several threads --* freex: lock-free queues including for task lists (experimental) --* flock: mutexes for queues even when no thread (debug mode) --* direct: no threads and direct dispatch of tasks whenever possible (debug mode) -+ -+- free: lock-free queues except for task list (default) -+- lock: mutexes for queues when several threads -+- freex: lock-free queues including for task lists (experimental) -+- flock: mutexes for queues even when no thread (debug mode) -+- direct: no threads and direct dispatch of tasks whenever possible (debug mode) - - __-max-chain__ (int, default: __6__): set maximum chain length when resolving filter links. Default value covers for _[ in -> ] dmx -> reframe -> decode -> encode -> reframe -> mx [ -> out]_. Filter chains loaded for adaptation (e.g. pixel format change) are loaded after the link resolution. Setting the value to 0 disables dynamic link resolution. You will have to specify the entire chain manually - __-max-sleep__ (int, default: __50__): set maximum sleep time slot in milliseconds when regulation is enabled -+__-step-link__: load filters one by one when solvink a link instead of loading all filters for the solved path - __-threads__ (int): set N extra thread for the session. -1 means use all available cores - __-no-probe__: disable data probing on sources and relies on extension (faster load but more error-prone) - __-no-argchk__: disable tracking of argument usage (all arguments will be considered as used) -@@ -135,10 +149,11 @@ This will use regular network interface and drop packets 100 to 119 on port 1234 - __-wait-fonts__: wait for SVG fonts to be loaded before displaying frames - __-webvtt-hours__: force writing hour when serializing WebVTT - __-charset__ (string): set charset when not recognized from input. Possible values are: --* utf8: force UTF-8 --* utf16: force UTF-16 little endian --* utf16be: force UTF-16 big endian --* other: attempt to parse anyway -+ -+- utf8: force UTF-8 -+- utf16: force UTF-16 little endian -+- utf16be: force UTF-16 big endian -+- other: attempt to parse anyway - - __-rmt__: enable profiling through [Remotery](https://github.com/Celtoys/Remotery). A copy of Remotery visualizer is in gpac/share/vis, usually installed in _/usr/share/gpac/vis_ or _Program Files/GPAC/vis_ - __-rmt-port__ (int, default: __17815__): set remotery port -diff --git a/docs/Filters/cryptin.md b/docs/Filters/cryptin.md -index df8ef72b..5dda470c 100644 ---- a/docs/Filters/cryptin.md -+++ b/docs/Filters/cryptin.md -@@ -1,6 +1,6 @@ - - --# CryptFile input -+# CryptFile input {:data-level="all"} - - Register name used to load filter: __cryptin__ - This filter is not checked during graph resolution and needs explicit loading. -diff --git a/docs/Filters/cryptout.md b/docs/Filters/cryptout.md -index 01a22fd2..f6ca07ff 100644 ---- a/docs/Filters/cryptout.md -+++ b/docs/Filters/cryptout.md -@@ -1,6 +1,6 @@ - - --# CryptFile output -+# CryptFile output {:data-level="all"} - - Register name used to load filter: __cryptout__ - This filter is not checked during graph resolution and needs explicit loading. -diff --git a/docs/Filters/dasher.md b/docs/Filters/dasher.md -index 153274cb..8b354f87 100644 ---- a/docs/Filters/dasher.md -+++ b/docs/Filters/dasher.md -@@ -1,6 +1,6 @@ - - --# DASH and HLS segmenter -+# DASH and HLS segmenter {:data-level="all"} - - Register name used to load filter: __dasher__ - This filter may be automatically loaded during graph resolution. -@@ -8,12 +8,14 @@ This filter requires the graph resolver to be activated. - - This filter provides segmentation and manifest generation for MPEG-DASH and HLS formats. - The segmenter currently supports: -+ - - MPD and m3u8 generation (potentially in parallel) - - ISOBMFF, MPEG-2 TS, MKV and raw bitstream segment formats - - override of profiles and levels in manifest for codecs - - most MPEG-DASH profiles - - static and dynamic (live) manifest offering - - context store and reload for batch processing of live/dynamic sessions -+ - - The filter does perform per-segment real-time regulation using [sreg](#sreg). - If you need per-frame real-time regulation on non-real-time inputs, insert a [reframer](reframer) before to perform real-time regulation. -@@ -36,45 +38,55 @@ template=Great_$File$_$Width$ - If input is `foo.mp4` with `640x360` video resolution, this will resolve in `Great_foo_640.mp4` for onDemand case. - - Standard DASH replacement strings: --* $Number[%%0Nd]$: replaced by the segment number, possibly prefixed with 0 --* $RepresentationID$: replaced by representation name --* $Time$: replaced by segment start time --* $Bandwidth$: replaced by representation bandwidth. -+ -+- $Number[%%0Nd]$: replaced by the segment number, possibly prefixed with 0 -+- $RepresentationID$: replaced by representation name -+- $Time$: replaced by segment start time -+- $Bandwidth$: replaced by representation bandwidth. -+ - _Note: these strings are not replaced in the manifest templates elements._ - - Additional replacement strings (not DASH, not generic GPAC replacements but may occur multiple times in template): --* $Init=NAME$: replaced by NAME for init segment, ignored otherwise --* $XInit=NAME$: complete replace by NAME for init segment, ignored otherwise --* $InitExt=EXT$: replaced by EXT for init segment file extensions, ignored otherwise --* $Index=NAME$: replaced by NAME for index segments, ignored otherwise --* $Path=PATH$: replaced by PATH when creating segments, ignored otherwise --* $Segment=NAME$: replaced by NAME for media segments, ignored for init segments --* $SegExt=EXT$: replaced by EXT for media segment file extensions, ignored for init segments --* $FS$ (FileSuffix): replaced by `_trackN` in case the input is an AV multiplex, or kept empty otherwise -+ -+- $Init=NAME$: replaced by NAME for init segment, ignored otherwise -+- $XInit=NAME$: complete replace by NAME for init segment, ignored otherwise -+- $InitExt=EXT$: replaced by EXT for init segment file extensions, ignored otherwise -+- $Index=NAME$: replaced by NAME for index segments, ignored otherwise -+- $Path=PATH$: replaced by PATH when creating segments, ignored otherwise -+- $Segment=NAME$: replaced by NAME for media segments, ignored for init segments -+- $SegExt=EXT$: replaced by EXT for media segment file extensions, ignored for init segments -+- $FS$ (FileSuffix): replaced by `_trackN` in case the input is an AV multiplex, or kept empty otherwise -+ - _Note: these strings are replaced in the manifest templates elements._ - - ## PID assignment and configuration - To assign PIDs into periods and adaptation sets and configure the session, the segmenter looks for the following properties on each input PID: --* `Representation`: assigns representation ID to input PID. If not set, the default behavior is to have each media component in different adaptation sets. Setting the `Representation` allows explicit multiplexing of the source(s) --* `Period`: assigns period ID to input PID. If not set, the default behavior is to have all media in the same period with the same start time --* `PStart`: assigns period start. If not set, 0 is assumed, and periods appear in the Period ID declaration order. If negative, this gives the period order (-1 first, then -2 ...). If positive, this gives the true start time and will abort DASHing at period end -+ -+- `Representation`: assigns representation ID to input PID. If not set, the default behavior is to have each media component in different adaptation sets. Setting the `Representation` allows explicit multiplexing of the source(s) -+- `Period`: assigns period ID to input PID. If not set, the default behavior is to have all media in the same period with the same start time -+- `PStart`: assigns period start. If not set, 0 is assumed, and periods appear in the Period ID declaration order. If negative, this gives the period order (-1 first, then -2 ...). If positive, this gives the true start time and will abort DASHing at period end -+ - _Note: When both positive and negative values are found, the by-order periods (negative) will be inserted AFTER the timed period (positive)_ --* `ASID`: assigns parent adaptation set ID. If not 0, only sources with same AS ID will be in the same adaptation set -+ -+- `ASID`: assigns parent adaptation set ID. If not 0, only sources with same AS ID will be in the same adaptation set -+ - _Note: If multiple streams in source, only the first stream will have an AS ID assigned_ --* `xlink`: for remote periods, only checked for null PID --* `Role`, `PDesc`, `ASDesc`, `ASCDesc`, `RDesc`: various descriptors to set for period, AS or representation --* `BUrl`: overrides segmenter [-base] with a set of BaseURLs to use for the PID (per representation) --* `Template`: overrides segmenter [template](#template) for this PID --* `DashDur`: overrides segmenter segment duration for this PID --* `StartNumber`: sets the start number for the first segment in the PID, default is 1 --* `IntraOnly`: indicates input PID follows HLS EXT-X-I-FRAMES-ONLY guidelines --* `CropOrigin`: indicates x and y coordinates of video for SRD (size is video size) --* `SRD`: indicates SRD position and size of video for SRD, ignored if `CropOrigin` is set --* `SRDRef`: indicates global width and height of SRD, ignored if `CropOrigin` is set --* `HLSPL`: name of variant playlist, can use templates --* `HLSMExt`: list of extensions to add to master playlist entries, ['foo','bar=val'] added as `,foo,bar=val` --* `HLSVExt`: list of extensions to add to variant playlist, ['#foo','#bar=val'] added as `#foo \n #bar=val` --* Non-dash properties: `Bitrate`, `SAR`, `Language`, `Width`, `Height`, `SampleRate`, `NumChannels`, `Language`, `ID`, `DependencyID`, `FPS`, `Interlaced`, `Codec`. These properties are used to setup each representation and can be overridden on input PIDs using the general PID property settings (cf global help). -+ -+- `xlink`: for remote periods, only checked for null PID -+- `Role`, `PDesc`, `ASDesc`, `ASCDesc`, `RDesc`: various descriptors to set for period, AS or representation -+- `BUrl`: overrides segmenter [-base] with a set of BaseURLs to use for the PID (per representation) -+- `Template`: overrides segmenter [template](#template) for this PID -+- `DashDur`: overrides segmenter segment duration for this PID -+- `StartNumber`: sets the start number for the first segment in the PID, default is 1 -+- `IntraOnly`: indicates input PID follows HLS EXT-X-I-FRAMES-ONLY guidelines -+- `CropOrigin`: indicates x and y coordinates of video for SRD (size is video size) -+- `SRD`: indicates SRD position and size of video for SRD, ignored if `CropOrigin` is set -+- `SRDRef`: indicates global width and height of SRD, ignored if `CropOrigin` is set -+- `HLSPL`: name of variant playlist, can use templates -+- `HLSMExt`: list of extensions to add to master playlist entries, ['foo','bar=val'] added as `,foo,bar=val` -+- `HLSVExt`: list of extensions to add to variant playlist, ['#foo','#bar=val'] added as `#foo \n #bar=val` -+- Non-dash properties: `Bitrate`, `SAR`, `Language`, `Width`, `Height`, `SampleRate`, `NumChannels`, `Language`, `ID`, `DependencyID`, `FPS`, `Interlaced`, `Codec`. These properties are used to setup each representation and can be overridden on input PIDs using the general PID property settings (cf global help). -+ - - Example - ``` -@@ -149,19 +161,25 @@ This will put video segments and playlist in `dash/video/` and audio segments an - ## Segmentation - The default behavior of the segmenter is to estimate the theoretical start time of each segment based on target segment duration, and start a new segment when a packet with SAP type 1,2,3 or 4 with time greater than the theoretical time is found. - This behavior can be changed to find the best SAP packet around a segment theoretical boundary using [sbound](#sbound): --* `closest` mode: the segment will start at the closest SAP of the theoretical boundary --* `in` mode: the segment will start at or before the theoretical boundary -+ -+- `closest` mode: the segment will start at the closest SAP of the theoretical boundary -+- `in` mode: the segment will start at or before the theoretical boundary -+ - - __Warning: These modes will introduce delay in the segmenter (typically buffering of one GOP) and should not be used for low-latency modes.__ - - The segmenter can also be configured to: -+ - - completely ignore SAP when segmenting using [sap](#sap). - - ignore SAP on non-video streams when segmenting using [strict_sap](#strict_sap). -+ - - When [seg_sync](#seg_sync) is disabled, the segmenter will by default announce a new segment in the manifest(s) as soon as its size/offset is known or its name is known, but the segment (or part in LL-HLS) may still not be completely written/sent. - This may result in temporary mismatches between segment/part size currently received versus size as advertized in manifest. - When [seg_sync](#seg_sync) is enabled, the segmenter will wait for the last byte of the fragment/segment to be pushed before announcing a new segment in the manifest(s). This can however slightly increase the latency in MPEG-DASH low-latency. - -+When (-sflush)[] is set to `single`, segmentation is skipped and a single segment is generated per input. -+ - ## Dynamic (real-time live) Mode - The dasher does not perform real-time regulation by default. - For regular segmentation, you should enable segment regulation [sreg](#sreg) if your sources are not real-time. -@@ -171,9 +189,11 @@ gpac -i source.mp4 -o live.mpd:segdur=2:profile=live:dmode=dynamic:sreg - ``` - - For low latency segmentation with fMP4, you will need to specify the following options: --* cdur: set the fMP4 fragment duration --* asto: set the availability time offset for DASH. This value should be equal or slightly greater than segment duration minus cdur --* llhls: enable low latency for HLS -+ -+- cdur: set the fMP4 fragment duration -+- asto: set the availability time offset for DASH. This value should be equal or slightly greater than segment duration minus cdur -+- llhls: enable low latency for HLS -+ - - _Note: [llhls](#llhls) does not force `cmaf` mode to allow for multiplexed media in segments but it enforces to `tfdt_traf` in the muxer._ - -@@ -219,15 +239,19 @@ The segmenter can take a list of instructions, or Cues, to use for the segmentat - - Cue files can be specified for the entire segmenter, or per PID using `DashCue` property. - Cues are given in an XML file with a root element called <DASHCues>, with currently no attribute specified. The children are one or more <Stream> elements, with attributes: --* id: integer for stream/track/PID ID --* timescale: integer giving the units of following timestamps --* mode: if present and value is `edit`, the timestamp are in presentation time (edit list applied) otherwise they are in media time --* ts_offset: integer giving a value (in timescale) to subtract to the DTS/CTS values listed -+ -+- id: integer for stream/track/PID ID -+- timescale: integer giving the units of following timestamps -+- mode: if present and value is `edit`, the timestamp are in presentation time (edit list applied) otherwise they are in media time -+- ts_offset: integer giving a value (in timescale) to subtract to the DTS/CTS values listed -+ - - The children of <Stream> are one or more <Cue> elements, with attributes: --* sample: integer giving the sample/frame number of a sample at which splitting shall happen --* dts: long integer giving the decoding time stamp of a sample at which splitting shall happen --* cts: long integer giving the composition / presentation time stamp of a sample at which splitting shall happen -+ -+- sample: integer giving the sample/frame number of a sample at which splitting shall happen -+- dts: long integer giving the decoding time stamp of a sample at which splitting shall happen -+- cts: long integer giving the composition / presentation time stamp of a sample at which splitting shall happen -+ - - __Warning: Cues shall be listed in decoding order.__ - -@@ -246,13 +270,17 @@ The segmenter can be used to generate manifests from already fragmented ISOBMFF - In this case, segment boundaries are attached to each packet starting a segment and used to drive the segmentation. - This can be used with single-track ISOBMFF sources, either single file or multi file. - For single file source: -+ - - if onDemand [profile](#profile) is requested, sources have to be formatted as a DASH self-initializing media segment with the proper sidx. - - templates are disabled. - - [sseg](#sseg) is forced for all profiles except onDemand ones. -+ - For multi files source: -+ - - input shall be a playlist containing the initial file followed by the ordered list of segments. - - if no [template](#template) is provided, the full or main [profile](#profile) will be used --* if [-template]() is provided, it shall be correct: the filter will not try to guess one from the input file names and will not validate it either. -+- if [-template]() is provided, it shall be correct: the filter will not try to guess one from the input file names and will not validate it either. -+ - - The manifest generation-only mode supports both MPD and HLS generation. - -@@ -285,9 +313,11 @@ The segmentation logic is not changed, and packets are forwarded with the same i - Output PIDs are forwarded with `DashCue=inband` property, so that any subsequent dasher follows the same segmentation process (see above). - - The first packet in a segment has: -+ - - property `FileNumber` (and, if multiple files, `FileName`) set as usual - - property `CueStart` set - - property `DFPStart=0` set if this is the first packet in a period -+ - - This mode can be used to pre-segment the streams for later processing that must take place before final dashing. - Example -@@ -300,9 +330,11 @@ Example - gpac -i s1.mp4 -i s2.mp4:#CryptInfo=clear:#Period=3 -i s3.mp4:#Period=3 dasher:gencues cecrypt:cfile=roll_period.xml -o live.mpd - ``` - If the DRM file uses `keyRoll=period`, this will generate: -+ - - first period crypted with one key - - second period clear - - third period crypted with another key -+ - - ## Forced-Template mode - When [tpl_force](#tpl_force) is set, the [template](#template) string is not analyzed nor modified for missing elements. -@@ -315,6 +347,46 @@ This will trash the manifest and open `mypipe` as destination for the muxer resu - - __Warning: Options for segment destination cannot be set through the [template](#template), global options must be used.__ - -+## Batch Operations -+The segmentation can be performed in multiple calls using a DASH context set with [state](#state). -+Between calls, the PIDs are reassigned by checking that the PID ID match between the calls and: -+ -+- the input file names match between the calls -+- or the representation ID (and period ID if specified) match between the calls -+ -+ -+If a PID is not matched, it will be assigned to a new period. -+ -+The default behaviour assume that the same inputs are used for segmentation and rebuilds a contiguous timeline at each new file start. -+If the inputs change but form a continuous timeline, [-keep_ts])() must be used to skip timeline reconstruction. -+ -+The inputs will be segmented for a duration of [subdur](#subdur) if set, otherwise the input media duration. -+When inputs are over, they are restarted if [loop](#loop) is set otherwise a new period is created. -+To avoid this behaviour, the [sflush](#sflush) option should be set to `end` or `single`, indicating that further sources for the same representations will be added in subsequent calls. When [sflush](#sflush) is not `off`, the (-loop)[] option is ignored. -+ -+Example -+``` -+gpac -i SRC -o dash.mpd:segdur=2:state=CTX && gpac -i SRC -o dash.mpd:segdur=2:state=CTX -+``` -+This will generate all dash segments for `SRC` (last one possibly shorter) and create a new period at end of input. -+Example -+``` -+gpac -i SRC -o dash.mpd:segdur=2:state=CTX:loop && gpac -i SRC -o dash.mpd:segdur=2:state=CTX:loop -+``` -+This will generate all dash segments for `SRC` and restart `SRC` to fill-up last segment. -+Example -+``` -+gpac -i SRC -o dash.mpd:segdur=2:state=CTX:sflush=end && gpac -i SRC -o dash.mpd:segdur=2:state=CTX:sflush=end -+``` -+This will generate all dash segments for `SRC` without looping/closing the period at end of input. Timestamps in the second call will be rewritten to be contiguous with timestamp at end of first call. -+Example -+``` -+gpac -i SRC1 -o dash.mpd:segdur=2:state=CTX:sflush=end:keep_ts && gpac -i SRC2 -o dash.mpd:segdur=2:state=CTX:sflush=end:keep_ts -+``` -+This will generate all dash segments for `SRC1` without looping/closing the period at end of input, then for `SRC2`. Timestamps of the sources will not be rewritten. -+ -+_Note: The default behaviour of MP4Box `-dash-ctx` option is to set the (-loop)[] to true._ -+ - ## Output redirecting - When loaded implicitly during link resolution, the dasher will only link its outputs to the target sink - Example -@@ -335,25 +407,32 @@ When explicitly loading the filter, the [dual](#dual) option will be disabled un - - ## Multiplexer development considerations - Output multiplexers allowing segmented output must obey the following: -+ - - inspect packet properties -- * FileNumber: if set, indicate the start of a new DASH segment -- * FileName: if set, indicate the file name. If not present, output shall be a single file. This is only set for packet carrying the `FileNumber` property, and only on one PID (usually the first) for multiplexed outputs -- * IDXName: gives the optional index name. If not present, index shall be in the same file as dash segment. Only used for MPEG-2 TS for now -- * EODS: property is set on packets with no payload and no timestamp to signal the end of a DASH segment. This is only used when stopping/resuming the segmentation process, in order to flush segments without dispatching an EOS (see [subdur](#subdur) ) -+ -+ - FileNumber: if set, indicate the start of a new DASH segment -+ - FileName: if set, indicate the file name. If not present, output shall be a single file. This is only set for packet carrying the `FileNumber` property, and only on one PID (usually the first) for multiplexed outputs -+ - IDXName: gives the optional index name. If not present, index shall be in the same file as dash segment. Only used for MPEG-2 TS for now -+ - EODS: property is set on packets with no payload and no timestamp to signal the end of a DASH segment. This is only used when stopping/resuming the segmentation process, in order to flush segments without dispatching an EOS (see [subdur](#subdur) ) -+ - - for each segment done, send a downstream event on the first connected PID signaling the size of the segment and the size of its index if any - - for multiplexers with init data, send a downstream event signaling the size of the init and the size of the global index if any - - the following filter options are passed to multiplexers, which should declare them as arguments: -- * noinit: disables output of init segment for the multiplexer (used to handle bitstream switching with single init in DASH) -- * frag: indicates multiplexer shall use fragmented format (used for ISOBMFF mostly) -- * subs_sidx=0: indicates an SIDX shall be generated - only added if not already specified by user -- * xps_inband=all|no|both: indicates AVC/HEVC/... parameter sets shall be sent inband, out of band, or both -- * nofragdef: indicates fragment defaults should be set in each segment rather than in init segment -+ -+ - noinit: disables output of init segment for the multiplexer (used to handle bitstream switching with single init in DASH) -+ - frag: indicates multiplexer shall use fragmented format (used for ISOBMFF mostly) -+ - subs_sidx=0: indicates an SIDX shall be generated - only added if not already specified by user -+ - xps_inband=all|no|both: indicates AVC/HEVC/... parameter sets shall be sent inband, out of band, or both -+ - nofragdef: indicates fragment defaults should be set in each segment rather than in init segment -+ - - The segmenter adds the following properties to the output PIDs: --* DashMode: identifies VoD (single file with global index) or regular DASH mode used by segmenter --* DashDur: identifies target DASH segment duration - this can be used to estimate the SIDX size for example --* LLHLS: identifies LLHLS is used; the multiplexer must send fragment size events back to the dasher, and set `LLHLSFragNum` on the first packet of each fragment --* SegSync: indicates that fragments/segments must be completely flushed before sending back size events -+ -+- DashMode: identifies VoD (single file with global index) or regular DASH mode used by segmenter -+- DashDur: identifies target DASH segment duration - this can be used to estimate the SIDX size for example -+- LLHLS: identifies LLHLS is used; the multiplexer must send fragment size events back to the dasher, and set `LLHLSFragNum` on the first packet of each fragment -+- SegSync: indicates that fragments/segments must be completely flushed before sending back size events -+ - - - # Options -@@ -362,10 +441,11 @@ The segmenter adds the following properties to the output PIDs: - __tpl__ (bool, default: _true_): use template mode (multiple segment, template URLs) - __stl__ (bool, default: _false_): use segment timeline (ignored in on_demand mode) - __dmode__ (enum, default: _static_, updatable): dash content mode --* static: static content --* dynamic: live generation --* dynlast: last call for live, will turn the MPD into static --* dynauto: live generation and move to static manifest upon end of stream -+ -+- static: static content -+- dynamic: live generation -+- dynlast: last call for live, will turn the MPD into static -+- dynauto: live generation and move to static manifest upon end of stream - - __sseg__ (bool, default: _false_): single segment is used - __sfile__ (bool, default: _false_): use a single file for all segments (default in on_demand) -@@ -373,59 +453,65 @@ The segmenter adds the following properties to the output PIDs: - __sap__ (bool, default: _true_): enable splitting segments at SAP boundaries - __mix_codecs__ (bool, default: _false_): enable mixing different codecs in an adaptation set - __ntp__ (enum, default: _rem_): insert/override NTP clock at the beginning of each segment --* rem: removes NTP from all input packets --* yes: inserts NTP at each segment start --* keep: leaves input packet NTP untouched -+ -+- rem: removes NTP from all input packets -+- yes: inserts NTP at each segment start -+- keep: leaves input packet NTP untouched - - __no_sar__ (bool, default: _false_): do not check for identical sample aspect ratio for adaptation sets - __bs_switch__ (enum, default: _def_): bitstream switching mode (single init segment) --* def: resolves to off for onDemand and inband for live --* off: disables BS switching --* on: enables it if same decoder configuration is possible --* inband: moves decoder config inband if possible --* both: inband and outband parameter sets --* pps: moves PPS and APS inband, keep VPS,SPS and DCI out of band (used for VVC RPR) --* force: enables it even if only one representation --* multi: uses multiple stsd entries in ISOBMFF -+ -+- def: resolves to off for onDemand and inband for live -+- off: disables BS switching -+- on: enables it if same decoder configuration is possible -+- inband: moves decoder config inband if possible -+- both: inband and outband parameter sets -+- pps: moves PPS and APS inband, keep VPS,SPS and DCI out of band (used for VVC RPR) -+- force: enables it even if only one representation -+- multi: uses multiple stsd entries in ISOBMFF - - __template__ (str): template string to use to generate segment name - __segext__ (str): file extension to use for segments - __initext__ (str): file extension to use for the init segment - __muxtype__ (enum, default: _auto_): muxtype to use for the segments --* mp4: uses ISOBMFF format --* ts: uses MPEG-2 TS format --* mkv: uses Matroska format --* webm: uses WebM format --* ogg: uses OGG format --* raw: uses raw media format (disables multiplexed representations) --* auto: guess format based on extension, default to mp4 if no extension -+ -+- mp4: uses ISOBMFF format -+- ts: uses MPEG-2 TS format -+- mkv: uses Matroska format -+- webm: uses WebM format -+- ogg: uses OGG format -+- raw: uses raw media format (disables multiplexed representations) -+- auto: guess format based on extension, default to mp4 if no extension - - __rawsub__ (bool, default: _no_): use raw subtitle format instead of encapsulating in container - __asto__ (dbl, default: _0_): availabilityStartTimeOffset to use in seconds. A negative value simply increases the AST, a positive value sets the ASToffset to representations - __profile__ (enum, default: _auto_): target DASH profile. This will set default option values to ensure conformance to the desired profile. For MPEG-2 TS, only main and live are used, others default to main --* auto: turns profile to live for dynamic and full for non-dynamic --* live: DASH live profile, using segment template --* onDemand: MPEG-DASH live profile --* main: MPEG-DASH main profile, using segment list --* full: MPEG-DASH full profile --* hbbtv1.5.live: HBBTV 1.5 DASH profile --* dashavc264.live: DASH-IF live profile --* dashavc264.onDemand: DASH-IF onDemand profile --* dashif.ll: DASH IF low-latency profile (set UTC server to time.akamai.com if none set) -+ -+- auto: turns profile to live for dynamic and full for non-dynamic -+- live: DASH live profile, using segment template -+- onDemand: MPEG-DASH live profile -+- main: MPEG-DASH main profile, using segment list -+- full: MPEG-DASH full profile -+- hbbtv1.5.live: HBBTV 1.5 DASH profile -+- dashavc264.live: DASH-IF live profile -+- dashavc264.onDemand: DASH-IF onDemand profile -+- dashif.ll: DASH IF low-latency profile (set UTC server to time.akamai.com if none set) - - __profX__ (str): list of profile extensions, as used by DASH-IF and DVB. The string will be colon-concatenated with the profile used. If starting with `+`, the profile string by default is erased and `+` is skipped - __cp__ (enum, default: _set_): content protection element location --* set: in adaptation set element --* rep: in representation element --* both: in both adaptation set and representation elements -+ -+- set: in adaptation set element -+- rep: in representation element -+- both: in both adaptation set and representation elements - - __pssh__ (enum, default: _v_): storage mode for PSSH box --* f: stores in movie fragment only --* v: stores in movie only, or movie and fragments if key roll is detected --* m: stores in mpd only --* mf: stores in mpd and movie fragment --* mv: stores in mpd and movie --* n: discard pssh from mpd and segments -+ -+- f: stores in movie fragment only -+- v: stores in movie only, or movie and fragments if key roll is detected -+- m: stores in mpd only -+- mf: stores in mpd and movie fragment -+- mv: stores in mpd and movie -+- n: discard pssh from mpd and segments - - __buf__ (sint, default: _-100_): min buffer duration in ms. negative value means percent of segment duration (e.g. -150 = 1.5*seg_dur) - __spd__ (sint, default: _0_): suggested presentation delay in ms -@@ -442,78 +528,94 @@ The segmenter adds the following properties to the output PIDs: - __refresh__ (dbl, default: _0_): refresh rate for dynamic manifests, in seconds (a negative value sets the MPD duration, value 0 uses dash duration) - __tsb__ (dbl, default: _30_): time-shift buffer depth in seconds (a negative value means infinity) - __keep_segs__ (bool, default: _false_): do not delete segments no longer in time-shift buffer --__subdur__ (dbl, default: _0_): maximum duration of the input file to be segmented. This does not change the segment duration, segmentation stops once segments produced exceeded the duration - __ast__ (str): set start date (as xs:date, e.g. YYYY-MM-DDTHH:MM:SSZ) for live mode. Default is now. !! Do not use with multiple periods, nor when DASH duration is not a multiple of GOP size !! - __state__ (str): path to file used to store/reload state info when simulating live. This is stored as a valid MPD with GPAC XML extensions -+__keep_ts__ (bool, default: _false_): do not shift timestamp when reloading a context - __loop__ (bool, default: _false_): loop sources when dashing with subdur and state. If not set, a new period is created once the sources are over -+__subdur__ (dbl, default: _0_): maximum duration of the input file to be segmented. This does not change the segment duration, segmentation stops once segments produced exceeded the duration - __split__ (bool, default: _true_): enable cloning samples for text/metadata/scene description streams, marking further clones as redundant - __hlsc__ (bool, default: _false_): insert clock reference in variant playlist in live HLS - __cues__ (str): set cue file - __strict_cues__ (bool, default: _false_): strict mode for cues, complains if splitting is not on SAP type 1/2/3 or if unused cue is found - __strict_sap__ (enum, default: _off_): strict mode for sap --* off: ignore SAP types for PID other than video, enforcing _startsWithSAP=1_ --* sig: same as [off](#off) but keep _startsWithSAP_ to the true SAP value --* on: warn if any PID uses SAP 3 or 4 and switch to FULL profile --* intra: ignore SAP types greater than 3 on all media types -+ -+- off: ignore SAP types for PID other than video, enforcing `AdaptationSet@startsWithSAP=1` -+- sig: same as [off](#off) but keep `AdaptationSet@startsWithSAP` to the true SAP value -+- on: warn if any PID uses SAP 3 or 4 and switch to FULL profile -+- intra: ignore SAP types greater than 3 on all media types - --__subs_sidx__ (sint, default: _-1_): number of subsegments per sidx. negative value disables sidx. Only used to inherit sidx option of destination -+__subs_sidx__ (sint, default: _-1_): number of subsegments per sidx. Negative value disables sidx. Only used to inherit sidx option of destination - __cmpd__ (bool, default: _false_): skip line feed and spaces in MPD XML for compactness - __styp__ (str): indicate the 4CC to use for styp boxes when using ISOBMFF output - __dual__ (bool): indicate to produce both MPD and M3U files - __sigfrag__ (bool): use manifest generation only mode - __sbound__ (enum, default: _out_): indicate how the theoretical segment start `TSS (= segment_number * duration)` should be handled --* out: segment split as soon as `TSS` is exceeded (`TSS` <= segment_start) --* closest: segment split at closest SAP to theoretical bound --* in: `TSS` is always in segment (`TSS` >= segment_start) -+ -+- out: segment split as soon as `TSS` is exceeded (`TSS` <= segment_start) -+- closest: segment split at closest SAP to theoretical bound -+- in: `TSS` is always in segment (`TSS` >= segment_start) - - __reschedule__ (bool, default: _false_): reschedule sources with no period ID assigned once done (dynamic mode only) - __sreg__ (bool, default: _false_): regulate the session -+ - - when using subdur and context, only generate segments from the past up to live edge - - otherwise in dynamic mode without context, do not generate segments ahead of time - - __scope_deps__ (bool, default: _true_): scope PID dependencies to be within source. If disabled, PID dependencies will be checked across all input PIDs regardless of their sources - __utcs__ (str): URL to use as time server / UTCTiming source. Special value `inband` enables inband UTC (same as publishTime), special prefix `xsd@` uses xsDateTime schemeURI rather than ISO --__force_flush__ (bool, default: _false_): force generating a single segment for each input. This can be useful in batch mode when average source duration is known and used as segment duration but actual duration may sometimes be greater -+__sflush__ (enum, default: _off_): segment flush mode - see filter help: -+ -+- off: no specific actions -+- single: force generating a single segment for each input -+- end: skip loop detection and clamp duration adjustment at end of input, used for state mode -+ - __last_seg_merge__ (bool, default: _false_): force merging last segment if less than half the target duration - __mha_compat__ (enum, default: _no_): adaptation set generation mode for compatible MPEG-H Audio profile --* no: only generate the adaptation set for the main profile --* comp: only generate the adaptation sets for all compatible profiles --* all: generate the adaptation set for the main profile and all compatible profiles -+ -+- no: only generate the adaptation set for the main profile -+- comp: only generate the adaptation sets for all compatible profiles -+- all: generate the adaptation set for the main profile and all compatible profiles - - __mname__ (str): output manifest name for ATSC3 multiplexing (using 'm3u8' only toggles HLS generation) - __llhls__ (enum, default: _off_): HLS low latency type --* off: do not use LL-HLS --* br: use LL-HLS with byte-range for segment parts, pointing to full segment (DASH-LL compatible) --* sf: use separate files for segment parts (post-fixed .1, .2 etc.) --* brsf: generate two sets of manifest, one for byte-range and one for files (`_IF` added before extension of manifest) -+ -+- off: do not use LL-HLS -+- br: use LL-HLS with byte-range for segment parts, pointing to full segment (DASH-LL compatible) -+- sf: use separate files for segment parts (post-fixed .1, .2 etc.) -+- brsf: generate two sets of manifest, one for byte-range and one for files (`_IF` added before extension of manifest) - - __hlsdrm__ (str): cryp file info for HLS full segment encryption - __hlsx__ (strl): list of string to append to master HLS header before variants with `['#foo','#bar=val']` added as `#foo \n #bar=val` -+__hlsiv__ (bool, default: _true_): inject IV in variant HLS playlist`` - __ll_preload_hint__ (bool, default: _true_): inject preload hint for LL-HLS - __ll_rend_rep__ (bool, default: _true_): inject rendition reports for LL-HLS - __ll_part_hb__ (dbl, default: _-1_): user-defined part hold-back for LLHLS, negative value means 3 times max part duration in session - __ckurl__ (str): set the ClearKey URL common to all encrypted streams (overriden by `CKUrl` pid property) - __hls_absu__ (enum, default: _no_): use absolute url in HLS generation using first URL in [base]() --* no: do not use absolute URL --* var: use absolute URL only in variant playlists --* mas: use absolute URL only in master playlist --* both: use absolute URL everywhere -+ -+- no: do not use absolute URL -+- var: use absolute URL only in variant playlists -+- mas: use absolute URL only in master playlist -+- both: use absolute URL everywhere - - __hls_ap__ (bool, default: _false_): use audio as primary media instead of video when generating playlists - __seg_sync__ (enum, default: _auto_): control how waiting on last packet P of fragment/segment to be written impacts segment injection in manifest --* no: do not wait for P --* yes: wait for P --* auto: wait for P if HLS is used -+ -+- no: do not wait for P -+- yes: wait for P -+- auto: wait for P if HLS is used - - __cmaf__ (enum, default: _no_): use cmaf guidelines --* no: CMAF not enforced --* cmfc: use CMAF `cmfc` guidelines --* cmf2: use CMAF `cmf2` guidelines -+ -+- no: CMAF not enforced -+- cmfc: use CMAF `cmfc` guidelines -+- cmf2: use CMAF `cmf2` guidelines - - __pswitch__ (enum, default: _single_): period switch control mode --* single: change period if PID configuration changes --* force: force period switch at each PID reconfiguration instead of absorbing PID reconfiguration (for splicing or add insertion not using periodID) --* stsd: change period if PID configuration changes unless new configuration was advertised in initial config -+ -+- single: change period if PID configuration changes -+- force: force period switch at each PID reconfiguration instead of absorbing PID reconfiguration (for splicing or add insertion not using periodID) -+- stsd: change period if PID configuration changes unless new configuration was advertised in initial config - - __chain__ (str): URL of next MPD for regular chaining - __chain_fbk__ (str): URL of fallback MPD -@@ -522,9 +624,11 @@ The segmenter adds the following properties to the output PIDs: - __keep_src__ (bool, default: _false_): keep source URLs in manifest generation mode - __gxns__ (bool, default: _false_): insert some gpac extensions in manifest (for now, only tfdt of first segment) - __dkid__ (enum, default: _auto_): control injection of default KID in MPD --* off: default KID not injected --* on: default KID always injected --* auto: default KID only injected if no key roll is detected (as per DASH-IF guidelines) -+ -+- off: default KID not injected -+- on: default KID always injected -+- auto: default KID only injected if no key roll is detected (as per DASH-IF guidelines) - - __tpl_force__ (bool, default: _false_): use template string as is without trying to add extension or solve conflicts in names -+__ttml_agg__ (bool, default: _false_): force aggregation of TTML samples of a DASH segment into a single sample - -diff --git a/docs/Filters/dashin.md b/docs/Filters/dashin.md -index 98c26b4f..52dc660f 100644 ---- a/docs/Filters/dashin.md -+++ b/docs/Filters/dashin.md -@@ -1,6 +1,6 @@ - - --# MPEG-DASH and HLS client -+# MPEG-DASH and HLS client {:data-level="all"} - - Register name used to load filter: __dashin__ - This filter may be automatically loaded during graph resolution. -@@ -12,12 +12,16 @@ This filter reads MPEG-DASH, HLS and MS Smooth manifests. - - This is the default mode, in which the filter produces media PIDs and frames from sources indicated in the manifest. - The default behavior is to perform adaptation according to [algo](#algo), but the filter can: -+ - - run with no adaptation, to grab maximum quality. -+ - Example - ``` - gpac -i MANIFEST_URL:algo=none:start_with=max_bw -o dest.mp4 - ``` -+ - - run with no adaptation, fetching all qualities. -+ - Example - ``` - gpac -i MANIFEST_URL:split_as -o dst=$File$.mp4 -@@ -60,28 +64,34 @@ This will encrypt an existing DASH session and republish it as HLS, using same s - This mode will force [noseek](#noseek)=`true` to ensure the first segment fetched is complete, and [split_as](#split_as)=`true` to fetch all qualities. - - Each first packet of a segment will have the following properties attached: --* `CueStart`: indicate this is a segment start --* `FileNumber`: current segment number --* `FileName`: current segment file name without manifest (MPD or master HLS) base url --* `DFPStart`: set with value `0` if this is the first packet in the period, absent otherwise -+ -+- `CueStart`: indicate this is a segment start -+- `FileNumber`: current segment number -+- `FileName`: current segment file name without manifest (MPD or master HLS) base url -+- `DFPStart`: set with value `0` if this is the first packet in the period, absent otherwise -+ - - If [forward](#forward) is set to `mani`, the first packet of a segment dispatched after a manifest update will also carry the manifest payload as a property: --* `DFManifest`: contains main manifest (MPD, M3U8 master) --* `DFVariant`: contains list of HLS child playlists as strings for the given quality --* `DFVariantName`: contains list of associated HLS child playlists name, in same order as manifests in `DFVariant` -+ -+- `DFManifest`: contains main manifest (MPD, M3U8 master) -+- `DFVariant`: contains list of HLS child playlists as strings for the given quality -+- `DFVariantName`: contains list of associated HLS child playlists name, in same order as manifests in `DFVariant` -+ - - Each output PID will have the following properties assigned: --* `DFMode`: set to 1 for `segb` or 2 for `mani` --* `DCue`: set to `inband` --* `DFPStart`: set to current period start value --* `FileName`: set to associated init segment if any --* `Representation`: set to the associated representation ID in the manifest --* `DashDur`: set to the average segment duration as indicated in the manifest --* `source_template`: set to true to indicate the source template is known --* `stl_timescale`: timescale used by SegmentTimeline, or 0 if no SegmentTimeline --* `init_url`: unresolved intialization URL (as it appears in the MPD or in the variant playlist) --* `manifest_url`: manifest URL --* `hls_variant_name`: HLS variant playlist name (as it appears in the HLS master playlist) -+ -+- `DFMode`: set to 1 for `segb` or 2 for `mani` -+- `DCue`: set to `inband` -+- `DFPStart`: set to current period start value -+- `FileName`: set to associated init segment if any -+- `Representation`: set to the associated representation ID in the manifest -+- `DashDur`: set to the average segment duration as indicated in the manifest -+- `source_template`: set to true to indicate the source template is known -+- `stl_timescale`: timescale used by SegmentTimeline, or 0 if no SegmentTimeline -+- `init_url`: unresolved intialization URL (as it appears in the MPD or in the variant playlist) -+- `manifest_url`: manifest URL -+- `hls_variant_name`: HLS variant playlist name (as it appears in the HLS master playlist) -+ - - When the [dasher](dasher) is used together with this mode, this will force all generated segments to have the same name, duration and fragmentation properties as the input ones. It is therefore not recommended for sessions stored/generated on local storage to generate the output in the same directory. - -@@ -89,56 +99,62 @@ When the [dasher](dasher) is used together with this mode, this will force all g - # Options - - __auto_switch__ (sint, default: _0_): switch quality every N segments --* positive: go to higher quality or loop to lowest --* negative: go to lower quality or loop to highest --* 0: disabled -+ -+- positive: go to higher quality or loop to lowest -+- negative: go to lower quality or loop to highest -+- 0: disabled - - __segstore__ (enum, default: _mem_): enable file caching --* mem: all files are stored in memory, no disk IO --* disk: files are stored to disk but discarded once played --* cache: all files are stored to disk and kept -+ -+- mem: all files are stored in memory, no disk IO -+- disk: files are stored to disk but discarded once played -+- cache: all files are stored to disk and kept - - __algo__ (str, default: _gbuf_, Enum: none|grate|gbuf|bba0|bolaf|bolab|bolau|bolao|JS): adaptation algorithm to use --* none: no adaptation logic --* grate: GPAC legacy algo based on available rate --* gbuf: GPAC legacy algo based on buffer occupancy --* bba0: BBA-0 --* bolaf: BOLA Finite --* bolab: BOLA Basic --* bolau: BOLA-U --* bolao: BOLA-O --* JS: use file JS (either with specified path or in $GSHARE/scripts/) for algo (.js extension may be omitted) -+ -+- none: no adaptation logic -+- grate: GPAC legacy algo based on available rate -+- gbuf: GPAC legacy algo based on buffer occupancy -+- bba0: BBA-0 -+- bolaf: BOLA Finite -+- bolab: BOLA Basic -+- bolau: BOLA-U -+- bolao: BOLA-O -+- JS: use file JS (either with specified path or in $GSHARE/scripts/) for algo (.js extension may be omitted) - - __start_with__ (enum, default: _max_bw_): initial selection criteria --* min_q: start with lowest quality --* max_q: start with highest quality --* min_bw: start with lowest bitrate --* max_bw: start with highest bitrate; if tiles are used, all low priority tiles will have the lower (below max) bandwidth selected --* max_bw_tiles: start with highest bitrate; if tiles are used, all low priority tiles will have their lowest bandwidth selected -+ -+- min_q: start with lowest quality -+- max_q: start with highest quality -+- min_bw: start with lowest bitrate -+- max_bw: start with highest bitrate; if tiles are used, all low priority tiles will have the lower (below max) bandwidth selected -+- max_bw_tiles: start with highest bitrate; if tiles are used, all low priority tiles will have their lowest bandwidth selected - - __max_res__ (bool, default: _true_): use max media resolution to configure display - __abort__ (bool, default: _false_): allow abort during a segment download - __use_bmin__ (enum, default: _auto_): playout buffer handling --* no: use default player settings --* auto: notify player of segment duration if not low latency --* mpd: use the indicated min buffer time of the MPD -+ -+- no: use default player settings -+- auto: notify player of segment duration if not low latency -+- mpd: use the indicated min buffer time of the MPD - - __shift_utc__ (sint, default: _0_): shift DASH UTC clock in ms - __spd__ (sint, default: _-I_): suggested presentation delay in ms --__route_shift__ (sint, default: _0_): shift ROUTE requests time by given ms -+__mcast_shift__ (sint, default: _0_): shift requests time by given ms for multicast sources - __server_utc__ (bool, default: _yes_): use `ServerUTC` or `Date` HTTP headers instead of local UTC - __screen_res__ (bool, default: _yes_): use screen resolution in selection phase - __init_timeshift__ (sint, default: _0_): set initial timeshift in ms (if >0) or in per-cent of timeshift buffer (if <0) - __tile_mode__ (enum, default: _none_): tile adaptation mode --* none: bitrate is shared equally across all tiles --* rows: bitrate decreases for each row of tiles starting from the top, same rate for each tile on the row --* rrows: bitrate decreases for each row of tiles starting from the bottom, same rate for each tile on the row --* mrows: bitrate decreased for top and bottom rows only, same rate for each tile on the row --* cols: bitrate decreases for each columns of tiles starting from the left, same rate for each tile on the columns --* rcols: bitrate decreases for each columns of tiles starting from the right, same rate for each tile on the columns --* mcols: bitrate decreased for left and right columns only, same rate for each tile on the columns --* center: bitrate decreased for all tiles on the edge of the picture --* edges: bitrate decreased for all tiles on the center of the picture -+ -+- none: bitrate is shared equally across all tiles -+- rows: bitrate decreases for each row of tiles starting from the top, same rate for each tile on the row -+- rrows: bitrate decreases for each row of tiles starting from the bottom, same rate for each tile on the row -+- mrows: bitrate decreased for top and bottom rows only, same rate for each tile on the row -+- cols: bitrate decreases for each columns of tiles starting from the left, same rate for each tile on the columns -+- rcols: bitrate decreases for each columns of tiles starting from the right, same rate for each tile on the columns -+- mcols: bitrate decreased for left and right columns only, same rate for each tile on the columns -+- center: bitrate decreased for all tiles on the edge of the picture -+- edges: bitrate decreased for all tiles on the center of the picture - - __tiles_rate__ (uint, default: _100_): indicate the amount of bandwidth to use at each quality level. The rate is recursively applied at each level, e.g. if 50%, Level1 gets 50%, level2 gets 25%, ... If 100, automatic rate allocation will be done by maximizing the quality in order of priority. If 0, bitstream will not be smoothed across tiles/qualities, and concurrency may happen between different media - __delay40X__ (uint, default: _500_): delay in milliseconds to wait between two 40X on the same segment -@@ -153,24 +169,27 @@ When the [dasher](dasher) is used together with this mode, this will force all g - __noseek__ (bool, default: _no_): disable seeking of initial segment(s) in dynamic mode (useful when UTC clocks do not match) - __bwcheck__ (uint, default: _5_): minimum time in milliseconds between two bandwidth checks when allowing segment download abort - __lowlat__ (enum, default: _early_): segment scheduling policy in low latency mode --* no: disable low latency --* strict: strict respect of AST offset in low latency --* early: allow fetching segments earlier than their AST in low latency when input PID is empty -+ -+- no: disable low latency -+- strict: strict respect of AST offset in low latency -+- early: allow fetching segments earlier than their AST in low latency when input PID is empty - - __forward__ (enum, default: _none_): segment forwarding mode --* none: regular DASH read --* file: do not demultiplex files and forward them as file PIDs (imply `segstore=mem`) --* segb: turn on [split_as](#split_as), segment and fragment bounds signaling (`sigfrag`) in sources and DASH cue insertion --* mani: same as `segb` and also forward manifests -+ -+- none: regular DASH read -+- file: do not demultiplex files and forward them as file PIDs (imply `segstore=mem`) -+- segb: turn on [split_as](#split_as), segment and fragment bounds signaling (`sigfrag`) in sources and DASH cue insertion -+- mani: same as `segb` and also forward manifests - - __fmodefwd__ (bool, default: _yes_): forward packet rather than copy them in `file` forward mode. Packet copy might improve performances in low latency mode - __skip_lqt__ (bool, default: _no_): disable decoding of tiles with highest degradation hints (not visible, not gazed at) for debug purposes - __llhls_merge__ (bool, default: _yes_): merge LL-HLS byte range parts into a single open byte range request - __groupsel__ (bool, default: _no_): select groups based on language (by default all playable groups are exposed) - __chain_mode__ (enum, default: _on_): MPD chaining mode --* off: do not use MPD chaining --* on: use MPD chaining once over, fallback if MPD load failure --* error: use MPD chaining once over or if error (MPD or segment download) -+ -+- off: do not use MPD chaining -+- on: use MPD chaining once over, fallback if MPD load failure -+- error: use MPD chaining once over or if error (MPD or segment download) - - __asloop__ (bool, default: _false_): when auto switch is enabled, iterates back and forth from highest to lowest qualities - __bsmerge__ (bool, default: _true_): allow merging of video bitstreams (only HEVC for now) -diff --git a/docs/Filters/dtout.md b/docs/Filters/dtout.md -index 69a60051..d73965aa 100644 ---- a/docs/Filters/dtout.md -+++ b/docs/Filters/dtout.md -@@ -1,6 +1,6 @@ - - --# DekTec SDIOut -+# DekTec SDIOut {:data-level="all"} - - Register name used to load filter: __dtout__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/dvbin.md b/docs/Filters/dvbin.md -index e2c626e0..5b7468a2 100644 ---- a/docs/Filters/dvbin.md -+++ b/docs/Filters/dvbin.md -@@ -1,6 +1,6 @@ - - --# DVB for Linux -+# DVB for Linux {:data-level="all"} - - Register name used to load filter: __dvbin__ - This filter may be automatically loaded during graph resolution. -@@ -8,8 +8,10 @@ This filter may be automatically loaded during graph resolution. - Experimental DVB support for linux, requires a channel config file through [chcfg](#chcfg) - - The URL syntax is `dvb://CHANNAME[@FRONTEND]`, with: -- * CHANNAME: the channel name as listed in the channel config file -- * frontend: the index of the DVB adapter to use (optional, default is 0) -+ -+ - CHANNAME: the channel name as listed in the channel config file -+ - frontend: the index of the DVB adapter to use (optional, default is 0) -+ - - - # Options -diff --git a/docs/Filters/evgs.md b/docs/Filters/evgs.md -index d4dad26a..156ffa0a 100644 ---- a/docs/Filters/evgs.md -+++ b/docs/Filters/evgs.md -@@ -1,6 +1,6 @@ - - --# EVG video rescaler -+# EVG video rescaler {:data-level="all"} - - Register name used to load filter: __evgs__ - This filter may be automatically loaded during graph resolution. -@@ -22,18 +22,24 @@ evgs:osize=288x240:osar=3/2 - The output dimensions will be 192x240. - - When aspect ratio is not kept ([keepar=off](#keepar=off)): -+ - - source is resampled to desired dimensions - - if output aspect ratio is not set, output will use source sample aspect ratio -+ - - When aspect ratio is partially kept ([keepar=nosrc](#keepar=nosrc)): -+ - - resampling is done on the input data without taking input sample aspect ratio into account - - if output sample aspect ratio is not set ([osar=0/N](#osar=0/N)), source aspect ratio is forwarded to output. -+ - - When aspect ratio is fully kept ([keepar=full](#keepar=full)), output aspect ratio is force to 1/1 if not set. - - When sample aspect ratio is kept, the filter will: -+ - - center the rescaled input frame on the output frame - - fill extra pixels with [padclr](#padclr) -+ - - - # Options -@@ -42,9 +48,10 @@ When sample aspect ratio is kept, the filter will: - __ofmt__ (pfmt, default: _none_): pixel format for output video. When not set, input format is used - __ofr__ (bool, default: _false_): force output full range - __keepar__ (enum, default: _off_): keep aspect ratio --* off: ignore aspect ratio --* full: respect aspect ratio, applying input sample aspect ratio info --* nosrc: respect aspect ratio but ignore input sample aspect ratio -+ -+- off: ignore aspect ratio -+- full: respect aspect ratio, applying input sample aspect ratio info -+- nosrc: respect aspect ratio but ignore input sample aspect ratio - - __padclr__ (str, default: _black_): clear color when aspect ration preservation is used - __osar__ (frac, default: _0/1_): force output pixel aspect ratio -diff --git a/docs/Filters/faad.md b/docs/Filters/faad.md -index a2be3b59..01271771 100644 ---- a/docs/Filters/faad.md -+++ b/docs/Filters/faad.md -@@ -1,6 +1,6 @@ - - --# FAAD decoder -+# FAAD decoder {:data-level="all"} - - Register name used to load filter: __faad__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/ffavf.md b/docs/Filters/ffavf.md -index 06a18419..c53656f5 100644 ---- a/docs/Filters/ffavf.md -+++ b/docs/Filters/ffavf.md -@@ -1,6 +1,6 @@ - - --# FFmpeg AVFilter -+# FFmpeg AVFilter {:data-level="all"} - - Register name used to load filter: __ffavf__ - This filter is not checked during graph resolution and needs explicit loading. -@@ -49,9 +49,11 @@ Example - gpac -i video:#ffid=a -i logo:#ffid=b ffavf::f=[a][b]overlay=main_w-overlay_w-10:main_h-overlay_h-10 vout - ``` - In this example: -+ - - the video source is identified as `a` - - the logo source is identified as `b` - - the filter declaration maps `a` to its first input (in this case, main video) and `b` to its second input (in this case the overlay) -+ - - When a graph has several outputs, output PIDs will be identified using the `ffid` property set to the output avfilter name. - Example -@@ -59,17 +61,21 @@ Example - gpac -i source ffavf::f=split inspect:SID=#ffid=out0 vout#SID=out1 - ``` - In this example: -+ - - the splitter produces 2 video streams `out0` and `out1` - - the inspector only process stream with ffid `out0` - - the video output only displays stream with ffid `out1` -+ - - The name(s) of the final output of the avfilter graph cannot be configured in GPAC. You can however name intermediate output(s) in a complex filter chain as usual. - - # Filter graph commands - - The filter handles option updates as commands passed to the AV filter graph. The syntax expected in the option name is: --* com_name=value: sends command `com_name` with value `value` to all filters --* name#com_name=value: sends command `com_name` with value `value` to filter named `name` -+ -+- com_name=value: sends command `com_name` with value `value` to all filters -+- name#com_name=value: sends command `com_name` with value `value` to filter named `name` -+ - - - # Options -diff --git a/docs/Filters/ffavin.md b/docs/Filters/ffavin.md -index 9e1db959..505a9c45 100644 ---- a/docs/Filters/ffavin.md -+++ b/docs/Filters/ffavin.md -@@ -1,6 +1,6 @@ - - --# FFmpeg AV Capture -+# FFmpeg AV Capture {:data-level="all"} - - Register name used to load filter: __ffavin__ - This filter may be automatically loaded during graph resolution. -@@ -14,14 +14,18 @@ To list all supported grabbers for your GPAC build, use `gpac -h ffavin:*`. - Typical classes are `dshow` on windows, `avfoundation` on OSX, `video4linux2` or `x11grab` on linux - - Typical device name can be the webcam name: -+ - - `FaceTime HD Camera` on OSX, device name on windows, `/dev/video0` on linux - - `screen-capture-recorder`, see http://screencapturer.sf.net/ on windows - - `Capture screen 0` on OSX (0=first screen), or `screenN` for short - - X display name (e.g. `:0.0`) on linux -+ - - The general mapping from ffmpeg command line is: -+ - - ffmpeg `-f` maps to [fmt](#fmt) option - - ffmpeg `-i` maps to [dev](#dev) option -+ - - Example - ``` -@@ -42,10 +46,11 @@ gpac -i av://::dev=0:1 ... - __fmt__ (str): name of device class. If not set, defaults to first device class - __dev__ (str, default: _0_): name of device or index of device - __copy__ (enum, default: _A_): set copy mode of raw frames --* N: frames are only forwarded (shared memory, no copy) --* A: audio frames are copied, video frames are forwarded --* V: video frames are copied, audio frames are forwarded --* AV: all frames are copied -+ -+- N: frames are only forwarded (shared memory, no copy) -+- A: audio frames are copied, video frames are forwarded -+- V: video frames are copied, audio frames are forwarded -+- AV: all frames are copied - - __sclock__ (bool, default: _false_): use system clock (us) instead of device timestamp (for buggy devices) - __probes__ (uint, default: _10_, minmax: 0-100): probe a given number of video frames before emitting (this usually helps with bad timing of the first frames) -diff --git a/docs/Filters/ffbsf.md b/docs/Filters/ffbsf.md -index 3bfa30e4..30e8ac37 100644 ---- a/docs/Filters/ffbsf.md -+++ b/docs/Filters/ffbsf.md -@@ -1,6 +1,6 @@ - - --# FFmpeg BitStream filter -+# FFmpeg BitStream filter {:data-level="all"} - - Register name used to load filter: __ffbsf__ - This filter is not checked during graph resolution and needs explicit loading. -@@ -13,8 +13,10 @@ Several BSF may be specified in [f](#f) for different coding types. BSF not matc - When no BSF matches the input coding type, or when [f](#f) is empty, the filter acts as a passthrough filter. - - Options are specified after the desired filters: -+ - - `ffbsf:f=h264_metadata:video_full_range_flag=0` - - `ffbsf:f=h264_metadata,av1_metadata:video_full_range_flag=0:color_range=tv` -+ - - _Note: Using BSFs on some media types (e.g. avc, hevc) may trigger creation of a reframer filter (e.g. rfnalu)_ - -diff --git a/docs/Filters/ffdec.md b/docs/Filters/ffdec.md -index 0d9ed510..49632554 100644 ---- a/docs/Filters/ffdec.md -+++ b/docs/Filters/ffdec.md -@@ -1,6 +1,6 @@ - - --# FFmpeg decoder -+# FFmpeg decoder {:data-level="all"} - - Register name used to load filter: __ffdec__ - This filter may be automatically loaded during graph resolution. -@@ -16,9 +16,11 @@ The default threading mode is to let libavcodec decide how many threads to use. - - The [ffcmap](#ffcmap) option allows specifying FFmpeg codecs for codecs not supported by GPAC. - Each entry in the list is formatted as `GID@name` or `GID@+name`, with: --* GID: 4CC or 32 bit identifier of codec ID, as indicated by `gpac -i source inspect:full` --* name: FFmpeg codec name --* `+': is set and extra data is set and formatted as an ISOBMFF box, removes box header -+ -+- GID: 4CC or 32 bit identifier of codec ID, as indicated by `gpac -i source inspect:full` -+- name: FFmpeg codec name -+- `+': is set and extra data is set and formatted as an ISOBMFF box, removes box header -+ - - Example - ``` -diff --git a/docs/Filters/ffdmx.md b/docs/Filters/ffdmx.md -index fde5938a..a723256c 100644 ---- a/docs/Filters/ffdmx.md -+++ b/docs/Filters/ffdmx.md -@@ -1,6 +1,6 @@ - - --# FFmpeg demultiplexer -+# FFmpeg demultiplexer {:data-level="all"} - - Register name used to load filter: __ffdmx__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/ffenc.md b/docs/Filters/ffenc.md -index 01ce1e82..bdb2669b 100644 ---- a/docs/Filters/ffenc.md -+++ b/docs/Filters/ffenc.md -@@ -1,6 +1,6 @@ - - --# FFmpeg encoder -+# FFmpeg encoder {:data-level="all"} - - Register name used to load filter: __ffenc__ - This filter may be automatically loaded during graph resolution. -@@ -17,8 +17,10 @@ Options can be passed from prompt using `--OPT=VAL` (global options) or appendin - The filter will look for property `TargetRate` on input PID to set the desired bitrate per PID. - - The filter will force a closed gop boundary: -+ - - at each packet with a `FileNumber` property set or a `CueStart` property set to true. - - if [fintra](#fintra) and [rc](#rc) is set. -+ - - When forcing a closed GOP boundary, the filter will flush, destroy and recreate the encoder to make sure a clean context is used, as currently many encoders in libavcodec do not support clean reset when forcing picture types. - If [fintra](#fintra) is not set and the output of the encoder is a DASH session in live profile without segment timeline, [fintra](#fintra) will be set to the target segment duration and [rc](#rc) will be set. -diff --git a/docs/Filters/ffmx.md b/docs/Filters/ffmx.md -index d5c78101..4e261fea 100644 ---- a/docs/Filters/ffmx.md -+++ b/docs/Filters/ffmx.md -@@ -1,6 +1,6 @@ - - --# FFmpeg multiplexer -+# FFmpeg multiplexer {:data-level="all"} - - Register name used to load filter: __ffmx__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/ffsws.md b/docs/Filters/ffsws.md -index a87efbb4..b458607d 100644 ---- a/docs/Filters/ffsws.md -+++ b/docs/Filters/ffsws.md -@@ -1,6 +1,6 @@ - - --# FFmpeg video rescaler -+# FFmpeg video rescaler {:data-level="all"} - - Register name used to load filter: __ffsws__ - This filter may be automatically loaded during graph resolution. -@@ -22,23 +22,31 @@ ffsws:osize=288x240:osar=3/2 - The output dimensions will be 192x240. - - When aspect ratio is not kept ([keepar=off](#keepar=off)): -+ - - source is resampled to desired dimensions - - if output aspect ratio is not set, output will use source sample aspect ratio -+ - - When aspect ratio is partially kept ([keepar=nosrc](#keepar=nosrc)): -+ - - resampling is done on the input data without taking input sample aspect ratio into account - - if output sample aspect ratio is not set ([osar=0/N](#osar=0/N)), source aspect ratio is forwarded to output. -+ - - When aspect ratio is fully kept ([keepar=full](#keepar=full)), output aspect ratio is force to 1/1 if not set. - - When sample aspect ratio is kept, the filter will: -+ - - center the rescaled input frame on the output frame - - fill extra pixels with [padclr](#padclr) -+ - - ## Algorithms options -+ - - for bicubic, to tune the shape of the basis function, [p1](#p1) tunes f(1) and [p2](#p2) f´(1) - - for gauss [p1](#p1) tunes the exponent and thus cutoff frequency - - for lanczos [p1](#p1) tunes the width of the window function -+ - - See FFmpeg documentation (https://ffmpeg.org/documentation.html) for more details - -@@ -59,9 +67,10 @@ See FFmpeg documentation (https://ffmpeg.org/documentation.html) for more detail - __otable__ (sintl): the yuv2rgb coefficients describing the output yuv space, normally ff_yuv2rgb_coeffs[x], use default if not set - __itable__ (sintl): the yuv2rgb coefficients describing the input yuv space, normally ff_yuv2rgb_coeffs[x], use default if not set - __keepar__ (enum, default: _off_): keep aspect ratio --* off: ignore aspect ratio --* full: respect aspect ratio, applying input sample aspect ratio info --* nosrc: respect aspect ratio but ignore input sample aspect ratio -+ -+- off: ignore aspect ratio -+- full: respect aspect ratio, applying input sample aspect ratio info -+- nosrc: respect aspect ratio but ignore input sample aspect ratio - - __padclr__ (str, default: _black_): clear color when aspect ration preservation is used - __osar__ (frac, default: _0/1_): force output pixel aspect ratio -diff --git a/docs/Filters/filters_general.md b/docs/Filters/filters_general.md -index d7dea8ea..9c2dc994 100644 ---- a/docs/Filters/filters_general.md -+++ b/docs/Filters/filters_general.md -@@ -1,6 +1,6 @@ - - --# Overview -+# Overview {:data-level="all"} - - Filters are configurable processing units consuming and producing data packets. These packets are carried between filters through a data channel called _PID_. A PID is in charge of allocating/tracking data packets, and passing the packets to the destination filter(s). A filter output PID may be connected to zero or more filters. This fan-out is handled internally by GPAC (no such thing as a tee filter in GPAC). - _Note: When a PID cannot be connected to any filter, a warning is thrown and all packets dispatched on this PID will be destroyed. The session may however still run, unless [-full-link](core_options/#full-link) is set._ -@@ -11,26 +11,35 @@ Each filter exposes a set of argument to configure itself, using property types - - # Property and filter option format - --* boolean: formatted as `yes`,`true`,`1` or `no`,`false`,`0` --* enumeration (for filter arguments only): must use the syntax given in the argument description, otherwise value `0` (first in enum) is assumed. --* 1-dimension (numbers, floats, ints...): formatted as `value[unit]`, where `unit` can be `k`,`K` (x 1000) or `m`,`M` (x 1000000) or `g`,`G` (x 1000000000) or `sec` (x 1000) or `min` (x 60000). `+I` means max float/int/uint value, `-I` min float/int/uint value. --* fraction: formatted as `num/den` or `num-den` or `num`, in which case the denominator is 1 if `num` is an integer, or 1000000 if `num` is a floating-point value. --* unsigned 32 bit integer: formatted as number or hexadecimal using the format `0xAABBCCDD`. --* N-dimension (vectors): formatted as `DIM1xDIM2[xDIM3[xDIM4]]` values, without unit multiplier. -- * For 2D integer vectors, the following resolution names can be used: `360`, `480`, `576`, `720`, `1080`, `hd`, `2k`, `2160`, `4k`, `4320`, `8k` --* string: formatted as: -- * `value`: copies value to string. -- * `file@FILE`: load string from local `FILE` (opened in binary mode). -- * `bxml@FILE`: binarize XML from local `FILE` and set property type to data - see https://wiki.gpac.io/xmlformats/NHML-Format. --* data: formatted as: -- * `size@address`: constant data block, not internally copied; `size` gives the size of the block, `address` the data pointer. -- * `0xBYTESTRING`: data block specified in hexadecimal, internally copied. -- * `file@FILE`: load data from local `FILE` (opened in binary mode). -- * `bxml@FILE`: binarize XML from local `FILE` - see https://wiki.gpac.io/xmlformats/NHML-Format. -- * `b64@DATA`: load data from base-64 encoded `DATA`. --* pointer: pointer address as formatted by `%p` in C. --* string lists: formatted as `val1,val2[,...]`. Each value can also use `file@FILE` syntax. --* integer lists: formatted as `val1,val2[,...]` -+ -+- boolean: formatted as `yes`,`true`,`1` or `no`,`false`,`0` -+- enumeration (for filter arguments only): must use the syntax given in the argument description, otherwise value `0` (first in enum) is assumed. -+- 1-dimension (numbers, floats, ints...): formatted as `value[unit]`, where `unit` can be `k`,`K` (x 1000) or `m`,`M` (x 1000000) or `g`,`G` (x 1000000000) or `sec` (x 1000) or `min` (x 60000). `+I` means max float/int/uint value, `-I` min float/int/uint value. -+- fraction: formatted as `num/den` or `num-den` or `num`, in which case the denominator is 1 if `num` is an integer, or 1000000 if `num` is a floating-point value. -+- unsigned 32 bit integer: formatted as number or hexadecimal using the format `0xAABBCCDD`. -+- N-dimension (vectors): formatted as `DIM1xDIM2[xDIM3[xDIM4]]` values, without unit multiplier. -+ -+ - For 2D integer vectors, the following resolution names can be used: `360`, `480`, `576`, `720`, `1080`, `hd`, `2k`, `2160`, `4k`, `4320`, `8k` -+ -+- string: formatted as: -+ -+ - `value`: copies value to string. -+ - `file@FILE`: load string from local `FILE` (opened in binary mode). -+ - `bxml@FILE`: binarize XML from local `FILE` and set property type to data - see https://wiki.gpac.io/xmlformats/NHML-Format. -+ -+- data: formatted as: -+ -+ - `size@address`: constant data block, not internally copied; `size` gives the size of the block, `address` the data pointer. -+ - `0xBYTESTRING`: data block specified in hexadecimal, internally copied. -+ - `file@FILE`: load data from local `FILE` (opened in binary mode). -+ - `bxml@FILE`: binarize XML from local `FILE` - see https://wiki.gpac.io/xmlformats/NHML-Format. -+ - `b64@DATA`: load data from base-64 encoded `DATA`. -+ - `FMT@val`: load values from val (comma-separated list) with `FMT` being `u8`, `s8`, `u16`, `s16`, `u32`, `s32`, `u64`, `s64`, `flt`, `dbl`, `hex` or `str`. -+ -+- pointer: pointer address as formatted by `%p` in C. -+- string lists: formatted as `val1,val2[,...]`. Each value can also use `file@FILE` syntax. -+- integer lists: formatted as `val1,val2[,...]` -+ - - _Note: The special characters in property formats (0x,/,-,+I,-I,x) cannot be configured._ - -@@ -40,8 +49,10 @@ Numbers and fraction can be expressed as `THH:MM:SS.ms`, `TMM:SS.ms`, `THH:MM:SS - - ## Generic declaration - Each filter is declared by its name, with optional filter arguments appended as a list of colon-separated `name=value` pairs. Additional syntax is provided for: --* boolean: `value` can be omitted, defaulting to `true` (e.g. `:allt`). Using `!` before the name negates the result (e.g. `:!moof_first`) --* enumerations: name can be omitted, e.g. `:disp=pbo` is equivalent to `:pbo`. -+ -+- boolean: `value` can be omitted, defaulting to `true` (e.g. `:allt`). Using `!` before the name negates the result (e.g. `:!moof_first`) -+- enumerations: name can be omitted, e.g. `:disp=pbo` is equivalent to `:pbo`. -+ - - - When string parameters are used (e.g. URLs), it is recommended to escape the string using the keyword `gpac`. -@@ -49,31 +60,36 @@ Example - ``` - filter:ARG=http://foo/bar?yes:gpac:opt=VAL - ``` -+ - This will properly extract the URL. - Example - ``` - filter:ARG=http://foo/bar?yes:opt=VAL --``` -+``` -+ - This will fail to extract it and keep `:opt=VAL` as part of the URL. - The escape mechanism is not needed for local source, for which file existence is probed during argument parsing. It is also not needed for builtin protocol handlers (`avin://`, `video://`, `audio://`, `pipe://`) --For `tcp://` and `udp://` protocols, the escape is not needed if a trailing `/` is appended after the port number. -+For schemes not using a server path, e.g. `tcp://` and `udp://`, the escape is not needed if a trailing `/` is appended after the port number. - Example - ``` - -i tcp://127.0.0.1:1234:OPT - ``` -+ - This will fail to extract the URL and options. - Example - ``` - -i tcp://127.0.0.1:1234/:OPT - ``` -+ - This will extract the URL and options. - _Note: one trick to avoid the escape sequence is to declare the URLs option at the end, e.g. `f1:opt1=foo:url=http://bar`, provided you have only one URL parameter to specify on the filter._ - --It is possible to disable option parsing (for string options) by duplicating the separator. -+It is possible to locally disable option parsing (usefull for string options) by duplicating the separator. - Example - ``` - filter::opt1=UDP://IP:PORT/:someopt=VAL::opt2=VAL2 - ``` -+ - This will pass `UDP://IP:PORT/:someopt=VAL` to `opt1` without inspecting it, and `VAL2` to `opt2`. - - -@@ -83,11 +99,13 @@ Example - ``` - "src=file.mp4" or "-src file.mp4" or "-i file.mp4" - ``` -+ - This will find a filter (for example `fin`) able to load `file.mp4`. The same result can be achieved by using `fin:src=file.mp4`. - Example - ``` - "dst=dump.yuv" or "-dst dump.yuv" or "-o dump.yuv" --``` -+``` -+ - This will dump the video content in `dump.yuv`. The same result can be achieved by using `fout:dst=dump.yuv`. - - Specific source or sink filters may also be specified using `filterName:src=URL` or `filterName:dst=URL`. -@@ -99,17 +117,20 @@ There is a special option called `gfreg` which allows specifying preferred filte - Example - ``` - src=file.mp4:gfreg=ffdmx,ffdec --``` -+``` -+ - This will use _ffdmx_ to read `file.mp4` and _ffdec_ to decode it. - This can be used to test a specific filter when alternate filter chains are possible. - - ## Specifying encoders and decoders - By default filters chain will be resolved without any decoding/encoding if the destination accepts the desired format. Otherwise, decoders/encoders will be dynamically loaded to perform the conversion, unless dynamic resolution is disabled. There is a special shortcut filter name for encoders `enc` allowing to match a filter providing the desired encoding. The parameters for `enc` are: --* c=NAME: identifies the desired codec. `NAME` can be the GPAC codec name or the encoder instance for ffmpeg/others --* b=UINT, rate=UINT, bitrate=UINT: indicates the bitrate in bits per second --* g=UINT, gop=UINT: indicates the GOP size in frames --* pfmt=NAME: indicates the target pixel format name (see [properties (-h props)](filters_properties) ) of the source, if supported by codec --* all_intra=BOOL: indicates all frames should be intra frames, if supported by codec -+ -+- c=NAME: identifies the desired codec. `NAME` can be the GPAC codec name or the encoder instance for ffmpeg/others -+- b=UINT, rate=UINT, bitrate=UINT: indicates the bitrate in bits per second -+- g=UINT, gop=UINT: indicates the GOP size in frames -+- pfmt=NAME: indicates the target pixel format name (see [properties (-h props)](filters_properties) ) of the source, if supported by codec -+- all_intra=BOOL: indicates all frames should be intra frames, if supported by codec -+ - - Other options will be passed to the filter if it accepts generic argument parsing (as is the case for ffmpeg). - The shortcut syntax `c=TYPE` (e.g. `c=aac:opts`) is also supported. -@@ -118,13 +139,15 @@ Example - ``` - gpac -i dump.yuv:size=320x240:fps=25 enc:c=avc:b=150000:g=50:cgop=true:fast=true -o raw.264 - ``` -+ - This creates a 25 fps AVC at 175kbps with a gop duration of 2 seconds, using closed gop and fast encoding settings for ffmpeg. - - The inverse operation (forcing a decode to happen) is possible using the _reframer_ filter. - Example - ``` - gpac -i file.mp4 reframer:raw=av -o null --``` -+``` -+ - This will force decoding media from `file.mp4` and trash (send to `null`) the result (doing a decoder benchmark for example). - - ## Escaping option separators -@@ -132,17 +155,20 @@ When a filter uses an option defined as a string using the same separator charac - Example - ``` - f:a=foo:b=bar --``` -+``` -+ - This will set option `a` to `foo` and option `b` to `bar` on the filter. - Example - ``` - f::a=foo:b=bar --``` -+``` -+ - This will set option `a` to `foo:b=bar` on the filter. - Example - ``` - f:a=foo::b=bar:c::d=fun - ``` -+ - This will set option `a` to `foo`, `b` to `bar:c` and the option `d` to `fun` on the filter. - - # Filter linking [_LINK_] -@@ -159,12 +185,15 @@ _They do not specify which destination a filter can connect to._ - When no link instructions are given (see below), the default linking strategy used is either _implicit mode_ (default in `gpac`) or _complete mode_ (if [-cl](gpac_general/#cl) is set). - Each PID is checked for possible connection to all defined filters, in their declaration order. - For each filter `DST` accepting a connection from the PID, directly or with intermediate filters: -+ - - if `DST` filter has link directives, use them to allow or reject PID connection. - - otherwise, if _complete mode_ is enabled, allow connection.. - - otherwise (_implicit mode_): -- - if `DST` is not a sink and is the first matching filter with no link directive, allow connection. -- - otherwise, if `DST` is not a sink and is not the first matching filter with no link directive, reject connection. -- - otherwise (`DST` is a sink) and no previous connections to a non-sink filter, allow connection. -+ -+ - if `DST` is not a sink and is the first matching filter with no link directive, allow connection. -+ - otherwise, if `DST` is not a sink and is not the first matching filter with no link directive, reject connection. -+ - otherwise (`DST` is a sink) and no previous connections to a non-sink filter, allow connection. -+ - - In all linking modes, a filter can prevent being linked to a filter with no link directives by setting `RSID` option on the filter. - This is typically needed when dynamically inserting/removing filters in an existing session where some filters have no ID defined and are not desired for the inserted chain. -@@ -173,32 +202,42 @@ A filter with `RSID` set is not clonable. - Example - ``` - gpac -i file.mp4 c=avc -o output --``` -+``` -+ - With this setup in _implicit mode_: -+ - - if the file has a video PID, it will connect to `enc` but not to `output`. The output PID of `enc` will connect to `output`. - - if the file has other PIDs than video, they will connect to `output`, since this `enc` filter accepts only video. -+ - - Example - ``` - gpac -cl -i file.mp4 c=avc -o output - ``` -+ - With this setup in _complete mode_: -+ - - if the file has a video PID, it will connect both to `enc` and to `output`, and the output PID of `enc` will connect to `output`. - - if the file has other PIDs than video, they will connect to `output`. -+ - - Furthermore in _implicit mode_, filter connections are restricted to filters defined between the last source and the sink(s). - Example - ``` - gpac -i video1 reframer:saps=1 -i video2 ffsws:osize=128x72 -o output - ``` -+ - This will connect: -+ - - `video1` to `reframer` then `reframer` to `output` but will prevent `reframer` to `ffsws` connection. - - `video2` to `ffsws` then `ffsws` to `output` but will prevent `video2` to `reframer` connection. -+ - - Example - ``` - gpac -i video1 -i video2 reframer:saps=1 ffsws:osize=128x72 -o output --``` -+``` -+ - This will connect `video1` AND `video2` to `reframer->ffsws->output` - - The _implicit mode_ allows specifying linear processing chains (no PID fan-out except for final output(s)) without link directives, simplifying command lines for common cases. -@@ -209,6 +248,7 @@ Example - ``` - gpac -i file.mp4 c=avc c=aac -o output - ``` -+ - If the file has a video PID, it will connect to `c=avc` but not to `output`. The output PID of `c=avc` will connect to `output`. - If the file has an audio PID, it will connect to `c=aac` but not to `output`. The output PID of `c=aac` will connect to `output`. - If the file has other PIDs than audio or video, they will connect to `output`. -@@ -216,10 +256,13 @@ If the file has other PIDs than audio or video, they will connect to `output`. - Example - ``` - gpac -i file.mp4 ffswf=osize:128x72 c=avc resample=osr=48k c=aac -o output --``` -+``` -+ - This will force: -+ - - `SRC(video)->ffsws->enc(video)->output` and prevent `SRC(video)->output`, `SRC(video)->enc(video)` and `ffsws->output` connections which would happen in _complete mode_. - - `SRC(audio)->resample->enc(audio)->output` and prevent `SRC(audio)->output`, `SRC(audio)->enc(audio)` and `resample->output` connections which would happen in _complete mode_. -+ - - ## Quick links - Link between filters may be manually specified. The syntax is an `@` character optionally followed by an integer (0 if omitted). -@@ -231,56 +274,68 @@ Example - ``` - fA fB @1 fC - ``` -+ - This indicates that `fC` only accepts inputs from `fA`. - Example - ``` - fA fB fC @1 @0 fD - ``` -+ - This indicates that `fD` only accepts inputs from `fB` and `fC`. - Example - ``` - fA fB fC ... @@1 fZ --``` -+``` -+ - This indicates that `fZ` only accepts inputs from `fB`. - - ## Complex links - The `@` link directive is just a quick shortcut to set the following filter arguments: --* FID=name: assigns an identifier to the filter --* SID=name1[,name2...]: sets a list of filter identifiers, or _sourceIDs_, restricting the list of possible inputs for a filter. -+ -+- FID=name: assigns an identifier to the filter -+- SID=name1[,name2...]: sets a list of filter identifiers, or _sourceIDs_, restricting the list of possible inputs for a filter. -+ - - Example - ``` - fA fB @1 fC --``` -+``` -+ - This is equivalent to `fA:FID=1 fB fC:SID=1`. - Example - ``` - fA:FID=1 fB fC:SID=1 --``` -+``` -+ - This indicates that `fC` only accepts input from `fA`, but `fB` might accept inputs from `fA`. - Example - ``` - fA:FID=1 fB:FID=2 fC:SID=1 fD:SID=1,2 --``` -+``` -+ - This indicates that `fD` only accepts input from `fA` and `fB` and `fC` only from `fA` - _Note: A filter with sourceID set cannot get input from filters with no IDs._ - - A sourceID name can be further extended using fragment identifier (`#` by default): --* name#PIDNAME: accepts only PID(s) with name `PIDNAME` --* name#TYPE: accepts only PIDs of matching media type. TYPE can be `audio`, `video`, `scene`, `text`, `font`, `meta` --* name#TYPEN: accepts only `N` (1-based index) PID of matching type from source (e.g. `video2` to only accept second video PID) --* name#TAG=VAL: accepts the PID if its parent filter has no tag or a tag matching `VAL` --* name#ITAG=VAL: accepts the PID if its parent filter has no inherited tag or an inherited tag matching `VAL` --* name#P4CC=VAL: accepts only PIDs with builtin property of type `P4CC` and value `VAL`. --* name#PName=VAL: same as above, using the builtin name corresponding to the property. --* name#AnyName=VAL: same as above, using the name of a non built-in property. --* name#Name=OtherPropName: compares the value with the value of another property of the PID. The matching will fail if the value to compare to is not present or different from the value to check. The property to compare with shall be a built-in property. -+ -+- name#PIDNAME: accepts only PID(s) with name `PIDNAME` -+- name#TYPE: accepts only PIDs of matching media type. TYPE can be `audio`, `video`, `scene`, `text`, `font`, `meta` -+- name#TYPEN: accepts only `N` (1-based index) PID of matching type from source (e.g. `video2` to only accept second video PID) -+- name#TAG=VAL: accepts the PID if its parent filter has no tag or a tag matching `VAL` -+- name#ITAG=VAL: accepts the PID if its parent filter has no inherited tag or an inherited tag matching `VAL` -+- name#P4CC=VAL: accepts only PIDs with builtin property of type `P4CC` and value `VAL`. -+- name#PName=VAL: same as above, using the builtin name corresponding to the property. -+- name#AnyName=VAL: same as above, using the name of a non built-in property. -+- name#Name=OtherPropName: compares the value with the value of another property of the PID. The matching will fail if the value to compare to is not present or different from the value to check. The property to compare with shall be a built-in property. -+ - If the property is not defined on the PID, the property is matched. Otherwise, its value is checked against the given value. - - The following modifiers for comparisons are allowed (for any fragment format using `=`): --* name#P4CC=!VAL: accepts only PIDs with property NOT matching `VAL`. --* name#P4CC-VAL: accepts only PIDs with property strictly less than `VAL` (only for 1-dimension number properties). --* name#P4CC+VAL: accepts only PIDs with property strictly greater than `VAL` (only for 1-dimension number properties). -+ -+- name#P4CC=!VAL: accepts only PIDs with property NOT matching `VAL`. -+- name#P4CC-VAL: accepts only PIDs with property strictly less than `VAL` (only for 1-dimension number properties). -+- name#P4CC+VAL: accepts only PIDs with property strictly greater than `VAL` (only for 1-dimension number properties). -+ - - A sourceID name can also use wildcard or be empty to match a property regardless of the source filter. - Example -@@ -288,44 +343,52 @@ Example - fA fB:SID=*#ServiceID=2 - fA fB:SID=#ServiceID=2 - ``` -+ - This indicates to match connection between `fA` and `fB` only for PIDs with a `ServiceID` property of `2`. - These extensions also work with the _LINK_ `@` shortcut. - Example - ``` - fA fB @1#video fC --``` -+``` -+ - This indicates that `fC` only accepts inputs from `fA`, and of type video. - Example - ``` - gpac -i img.heif @#ItemID=200 vout --``` -+``` -+ - This indicates to connect to `vout` only PIDs with `ItemID` property equal to `200`. - Example - ``` - gpac -i vid.mp4 @#PID=1 vout --``` -+``` -+ - This indicates to connect to `vout` only PIDs with `ID` property equal to `1`. - Example - ``` - gpac -i vid.mp4 @#Width=640 vout --``` -+``` -+ - This indicates to connect to `vout` only PIDs with `Width` property equal to `640`. - Example - ``` - gpac -i vid.mp4 @#Width-640 vout - ``` -+ - This indicates to connect to `vout` only PIDs with `Width` property less than `640` - Example - ``` - gpac -i vid.mp4 @#ID=ItemID#ItemNumber=1 vout --``` -+``` -+ - This will connect to `vout` only PID with an ID property equal to ItemID property (keep items, discard tracks) and an Item number of 1 (first item). - - Multiple fragment can be specified to check for multiple PID properties. - Example - ``` - gpac -i vid.mp4 @#Width=640#Height+380 vout --``` -+``` -+ - This indicates to connect to `vout` only PIDs with `Width` property equal to `640` and `Height` greater than `380`. - - __Warning: If a PID directly connects to one or more explicitly loaded filters, no further dynamic link resolution will be done to connect it to other filters with no sourceID set. Link directives should be carefully setup.__ -@@ -333,7 +396,8 @@ __Warning: If a PID directly connects to one or more explicitly loaded filters, - Example - ``` - fA @ reframer fB --``` -+``` -+ - If `fB` accepts inputs provided by `fA` but `reframer` does not, this will link `fA` PID to `fB` filter since `fB` has no sourceID. - Since the PID is connected, the filter engine will not try to solve a link between `fA` and `reframer`. - -@@ -341,36 +405,44 @@ An exception is made for local files: by default, a local file destination will - Example - ``` - gpac -i file.mp4 -o dump.mp4 --``` -+``` -+ - This will prevent direct connection of PID of type `file` to dst `file.mp4`, remultiplexing the file. - - The special option `nomux` is used to allow direct connections (ignored for non-sink filters). - Example - ``` - gpac -i file.mp4 -o dump.mp4:nomux --``` -+``` -+ - This will result in a direct file copy. - - This only applies to local files destination. For pipes, sockets or other file outputs (HTTP, ROUTE): -+ - - direct copy is enabled by default - - `nomux=0` can be used to force remultiplex -+ - - ## Sub-session tagging - Filters may be assigned to a sub-session using `:FS=N`, with `N` a positive integer. - Filters belonging to different sub-sessions may only link to each-other: -+ - - if explicitly allowed through sourceID directives (`@` or `SID`) - - or if they have the same sub-session identifier -+ - - This is mostly used for _implicit mode_ in `gpac`: each first source filter specified after a sink filter will trigger a new sub-session. - Example - ``` - gpac -i in1.mp4 -i in2.mp4 -o out1.mp4 -o out2.mp4 --``` -+``` -+ - This will result in both inputs multiplexed in both outputs. - Example - ``` - gpac -i in1.mp4 -o out1.mp4 -i in2.mp4 -o out2.mp4 --``` -+``` -+ - This will result in in1 mixed to out1 and in2 mixed to out2, these last two filters belonging to a different sub-session. - - # Arguments inheriting -@@ -379,24 +451,28 @@ Unless explicitly disabled (see [-max-chain](core_options/#max-chain)), the filt - Example - ``` - gpac -i file.mp4:OPT -o file.aac -o file.264 --``` -+``` -+ - This will pass the `:OPT` to all filters loaded between the source and the two destinations. - Example - ``` - gpac -i file.mp4 -o file.aac:OPT -o file.264 - ``` -+ - This will pass the `:OPT` to all filters loaded between the source and the file.aac destination. - _Note: the destination arguments inherited are the arguments placed __AFTER__ the `dst=` option._ - Example - ``` - gpac -i file.mp4 fout:OPTFOO:dst=file.aac:OPTBAR --``` -+``` -+ - This will pass the `:OPTBAR` to all filters loaded between `file.mp4` source and `file.aac` destination, but not `OPTFOO`. - Arguments inheriting can be stopped by using the keyword `gfloc`: arguments after the keyword will not be inherited. - Example - ``` - gpac -i file.mp4 -o file.aac:OPTFOO:gfloc:OPTBAR -o file.264 - ``` -+ - This will pass `:OPTFOO` to all filters loaded between `file.mp4` source and `file.aac` destination, but not `OPTBAR` - Arguments are by default tracked to check if they were used by the filter chain, and a warning is thrown if this is not the case. - It may be useful to specify arguments which may not be consumed depending on the graph resolution; the specific keyword `gfopt` indicates that arguments after the keyword will not be tracked. -@@ -404,6 +480,7 @@ Example - ``` - gpac -i file.mp4 -o file.aac:OPTFOO:gfopt:OPTBAR -o file.264 - ``` -+ - This will warn if `OPTFOO` is not consumed, but will not track `OPTBAR`. - - A filter may be assigned a name (for inspection purposes, not inherited) using `:N=name` option. This name is not used in link resolution and may be changed at runtime by the filter instance. -@@ -417,16 +494,18 @@ A filter may also be assigned an inherited tag (any string) using `:ITAG=name` o - - Destination URLs can be dynamically constructed using templates. Pattern `$KEYWORD$` is replaced in the template with the resolved value and `$KEYWORD%%0Nd$` is replaced in the template with the resolved integer, padded with up to N zeros if needed. - `KEYWORD` is __case sensitive__, and may be present multiple times in the string. Supported `KEYWORD`: --* num: replaced by file number if defined, 0 otherwise --* PID: ID of the source PID --* URL: URL of source file --* File: path on disk for source file; if not found, use URL if set, or PID name otherwise --* Type: name of stream type of PID (`video`, `audio` ...) --* OType: same as `Type` but uses original type when stream is encrypted (e.g. move from `crypt` to `video`) --* p4cc=ABCD: uses PID property with 4CC value `ABCD` --* pname=VAL: uses PID property with name `VAL` --* cts, dts, dur, sap: uses properties of first packet in PID at template resolution time --* OTHER: locates property 4CC for the given name, or property name if no 4CC matches. -+ -+- num: replaced by file number if defined, 0 otherwise -+- PID: ID of the source PID -+- URL: URL of source file -+- File: path on disk for source file; if not found, use URL if set, or PID name otherwise -+- Type: name of stream type of PID (`video`, `audio` ...) -+- OType: same as `Type` but uses original type when stream is encrypted (e.g. move from `crypt` to `video`) -+- p4cc=ABCD: uses PID property with 4CC value `ABCD` -+- pname=VAL: uses PID property with name `VAL` -+- cts, dts, dur, sap: uses properties of first packet in PID at template resolution time -+- OTHER: locates property 4CC for the given name, or property name if no 4CC matches. -+ - - `$$` is an escape for $ - -@@ -435,6 +514,7 @@ Example - ``` - gpac -i dump.yuv:size=640x360 vcrop:wnd=0x0x320x180 c=avc:b=1M @2 c=avc:b=750k -o dump_$CropOrigin$x$Width$x$Height$.264 - ``` -+ - This will create a cropped version of the source, encoded in AVC at 1M, and a full version of the content in AVC at 750k. Outputs will be `dump_0x0x320x180.264` for the cropped version and `dump_0x0x640x360.264` for the non-cropped one. - - # Cloning filters -@@ -444,30 +524,35 @@ Example - ``` - gpac -i img.heif -o dump_$ItemID$.jpg - ``` -+ - In this case, only one item (likely the first declared in the file) will connect to the destination. - Other items will not be connected since the destination only accepts one input PID. - Example - ``` - gpac -i img.heif -o dump_$ItemID$.jpg --``` -+``` -+ - In this case, the destination will be cloned for each item, and all will be exported to different JPEGs thanks to URL templating. - Example - ``` - gpac -i vid.mpd c=avc:FID=1 -o transcode.mpd:SID=1 --``` -+``` -+ - In this case, the encoder will be cloned for each video PIDs in the source, and the destination will only use PIDs coming from the encoders. - - When implicit linking is enabled, all filters are by default clonable. This allows duplicating the processing for each PIDs of the same type. - Example - ``` - gpac -i dual_audio resample:osr=48k c=aac -o dst --``` -+``` -+ - The `resampler` filter will be cloned for each audio PID, and the encoder will be cloned for each resampler output. - You can explicitly deactivate the cloning instructions: - Example - ``` - gpac -i dual_audio resample:osr=48k:clone=0 c=aac -o dst --``` -+``` -+ - The first audio will connect to the `resample` filter, the second to the `enc` filter and the `resample` output will connect to a clone of the `enc` filter. - - # Templating filter chains -@@ -481,14 +566,16 @@ Example - ``` - gpac -i source.ts -o file_$ServiceID$.mp4:SID=*#ServiceID=* - gpac -i source.ts -o file_$ServiceID$.mp4:SID=#ServiceID= --``` -+``` -+ - In this case, each new `ServiceID` value found when connecting PIDs to the destination will create a new destination file. - - Cloning in implicit linking mode applies to output as well: - Example - ``` - gpac -i dual_audio -o dst_$PID$.aac --``` -+``` -+ - Each audio track will be dumped to aac (potentially reencoding if needed). - - # Assigning PID properties -@@ -496,10 +583,12 @@ Each audio track will be dumped to aac (potentially reencoding if needed). - It is possible to define properties on output PIDs that will be declared by a filter. This allows tagging parts of the graph with different properties than other parts (for example `ServiceID`). The syntax is the same as filter option, and uses the fragment separator to identify properties, e.g. `#Name=Value`. - This sets output PIDs property (4cc, built-in name or any name) to the given value. Value can be omitted for boolean (defaults to true, e.g. `:#Alpha`). - Non built-in properties are parsed as follows: -+ - - `file@FOO` will be declared as string with a value set to the content of `FOO`. - - `bxml@FOO` will be declared as data with a value set to the binarized content of `FOO`. - - `FOO` will be declared as string with a value set to `FOO`. - - `TYPE@FOO` will be parsed according to `TYPE`. If the type is not recognized, the entire value is copied as string. See `gpac -h props` for defined types. -+ - - User-assigned PID properties on filter `fA` will be inherited by all filters dynamically loaded to solve `fA -> fB` connection. - If `fB` also has user-assigned PID properties, these only apply starting from `fB` in the chain and are not inherited by filters between `fA` and `fB`. -@@ -509,7 +598,8 @@ __Warning: Properties are not filtered and override the properties of the filter - Example - ``` - gpac -i v1.mp4:#ServiceID=4 -i v2.mp4:#ServiceID=2 -o dump.ts --``` -+``` -+ - This will multiplex the streams in `dump.ts`, using `ServiceID` 4 for PIDs from `v1.mp4` and `ServiceID` 2 for PIDs from `v2.mp4`. - - PID properties may be conditionally assigned by checking other PID properties. The syntax uses parenthesis (not configurable) after the property assignment sign: -@@ -526,33 +616,48 @@ _Note: When set, the default value (empty condition) always matches the PID, the - Example - ``` - gpac -i source.mp4:#MyProp=(audio)"Super Audio",(video)"Super Video" --``` -+``` -+ - This will assign property `MyProp` to `Super Audio` for audio PIDs and to `Super Video` for video PIDs. - Example - ``` - gpac -i source.mp4:#MyProp=(audio1)"Super Audio" - ``` -+ - This will assign property `MyProp` to `Super Audio` for first audio PID declared. - Example - ``` - gpac -i source.mp4:#MyProp=(Width+1280)HD --``` -+``` -+ - This will assign property `MyProp` to `HD` for PIDs with property `Width` greater than 1280. - - The property value can use templates with the following keywords: --* $GINC(init[,inc]) or @GINC(...): replaced by integer for each new output PID of the filter (see specific filter options for details on syntax) --* PROP (enclosed between `$` or `@`): replaced by serialized value of property `PROP` (name or 4CC) of the PID or with empty string if no such property -+ -+- $GINC(init[,inc]) or @GINC(...): replaced by integer for each new output PID of the filter (see specific filter options for details on syntax) -+- PROP (enclosed between `$` or `@`): replaced by serialized value of property `PROP` (name or 4CC) of the PID or with empty string if no such property -+ - Example - ``` - gpac -i source.ts:#ASID=$PID$ --``` -+``` -+ - This will assign DASH AdaptationSet ID to the PID ID value. - Example - ``` - gpac -i source.ts:#RepresentationID=$ServiceID$ --``` -+``` -+ - This will assign DASH Representation ID to the PID ServiceID value. - -+A property can also be removed by not specifying any value. Conditional removal is possible using the above syntax. -+Example -+``` -+gpac -i source.ts:#FOO= -+``` -+ -+This will remove the `FOO` property on the output PID. -+ - # Using option files - - It is possible to use a file to define options of a filter, by specifying the target file name as an option without value, i.e. `:myopts.txt`. -@@ -560,14 +665,17 @@ It is possible to use a file to define options of a filter, by specifying the ta - __Warning: Only local files are allowed.__ - - An option file is a simple text file containing one or more options or PID properties on one or more lines. -+ - - A line beginning with "//" is a comment and is ignored (not configurable). - - A line beginning with ":" indicates an escaped option (the entire line is parsed as a single option). -+ - Options in an option file may point to other option files, with a maximum redirection level of 5. - An option file declaration (`filter:myopts.txt`) follows the same inheritance rules as regular options. - Example - ``` - gpac -i source.mp4:myopts.txt:foo=bar -o dst - ``` -+ - Any filter loaded between `source.mp4` and `dst` will inherit both `myopts.txt` and `foo` options and will resolve options and PID properties given in `myopts.txt`. - - # Ignoring filters at run-time -@@ -578,17 +686,20 @@ When the PID codec ID matches one of the specified codec, the filter is replaced - Example - ``` - -i src c=avc:b=1m:ccp -o mux --``` -+``` -+ - This will replace the encoder filter with a reframer if the input PID is in AVC|H264 format, or uses the encoder for other visual PIDs. - Example - ``` - -i src c=avc:b=1m:ccp=avc,hevc -o mux - ``` -+ - This will replace the encoder filter with a reframer if the input PID is in AVC|H264 or HEVC format, or uses the encoder for other visual PIDs. - Example - ``` - -i src cecrypt:cfile=drm.xml:ccp=aac -o mux --``` -+``` -+ - This will replace the encryptor filter with a reframer if the input PID is in AAC format, or uses the encryptor for other PIDs. - - # Specific filter options -@@ -598,56 +709,67 @@ Some specific keywords are replaced when processing filter options. - __Warning: These keywords do not apply to PID properties. Multiple keywords cannot be defined for a single option.__ - - Defined keywords: --* $GSHARE: replaced by system path to GPAC shared directory (e.g. /usr/share/gpac) --* $GJS: replaced by the first path from global share directory and paths set through [-js-dirs](core_options/#js-dirs) that contains the file name following the macro, e.g. $GJS/source.js --* $GDOCS: replaced by system path to: -- - application document directory for iOS -- - `EXTERNAL_STORAGE` environment variable if present or `/sdcard` otherwise for Android -- - user home directory for other platforms --* $GLANG: replaced by the global config language option [-lang](core_options/#lang) --* $GUA: replaced by the global config user agent option [-user-agent](core_options/#user-agent) --* $GINC(init_val[,inc]): replaced by `init_val` and increment `init_val` by `inc` (positive or negative number, 1 if not specified) each time a new filter using this string is created. -+ -+- $GSHARE: replaced by system path to GPAC shared directory (e.g. /usr/share/gpac) -+- $GJS: replaced by the first path from global share directory and paths set through [-js-dirs](core_options/#js-dirs) that contains the file name following the macro, e.g. $GJS/source.js -+- $GDOCS: replaced by system path to: -+ -+ - application document directory for iOS -+ - `EXTERNAL_STORAGE` environment variable if present or `/sdcard` otherwise for Android -+ - user home directory for other platforms -+ -+- $GLANG: replaced by the global config language option [-lang](core_options/#lang) -+- $GUA: replaced by the global config user agent option [-user-agent](core_options/#user-agent) -+- $GINC(init_val[,inc]): replaced by `init_val` and increment `init_val` by `inc` (positive or negative number, 1 if not specified) each time a new filter using this string is created. -+ - - The `$GINC` construct can be used to dynamically assign numbers in filter chains: - Example - ``` - gpac -i source.ts tssplit @#ServiceID= -o dump_$GINC(10,2).ts --``` -+``` -+ - This will dump first service in dump_10.ts, second service in dump_12.ts, etc... - - As seen previously, the following options may be set on any filter, but are not visible in individual filter help: --* FID: filter identifier --* SID: filter source(s) (string value) --* N=NAME: filter name (string value) --* FS: sub-session identifier (unsigned int value) --* RSID: require sourceID to be present on target filters (no value) --* TAG: filter tag (string value) --* ITAG: filter inherited tag (string value) --* FBT: buffer time in microseconds (unsigned int value) --* FBU: buffer units (unsigned int value) --* FBD: decode buffer time in microseconds (unsigned int value) --* clone: explicitly enable/disable filter cloning flag (no value) --* nomux: enable/disable direct file copy (no value) --* gfreg: preferred filter registry names for link solving (string value) --* gfloc: following options are local to filter declaration, not inherited (no value) --* gfopt: following options are not tracked (no value) --* gpac: argument separator for URLs (no value) --* ccp: filter replacement control (string list value) --* NCID: ID of netcap configuration to use (string) --* LT: set additionnal log tools and levels for the filter usin same syntax as -logs, e.g. `:LT=filter@debug` (string value) --* DBG: debug missing input PID property (`=pid`), missing input packet property (`=pck`) or both (`=all`) -+ -+- FID: filter identifier -+- SID: filter source(s) (string value) -+- N=NAME: filter name (string value) -+- FS: sub-session identifier (unsigned int value) -+- RSID: require sourceID to be present on target filters (no value) -+- TAG: filter tag (string value) -+- ITAG: filter inherited tag (string value) -+- FBT: buffer time in microseconds (unsigned int value) -+- FBU: buffer units (unsigned int value) -+- FBD: decode buffer time in microseconds (unsigned int value) -+- clone: explicitly enable/disable filter cloning flag (no value) -+- nomux: enable/disable direct file copy (no value) -+- gfreg: preferred filter registry names for link solving (string value) -+- gfloc: following options are local to filter declaration, not inherited (no value) -+- gfopt: following options are not tracked (no value) -+- gpac: argument separator for URLs (no value) -+- ccp: filter replacement control (string list value) -+- NCID: ID of netcap configuration to use (string) -+- LT: set additionnal log tools and levels for the filter usin same syntax as -logs, e.g. `:LT=filter@debug` (string value) -+- DBG: debug missing input PID property (`=pid`), missing input packet property (`=pck`) or both (`=all`) -+ - - The buffer control options are used to change the default buffering of PIDs of a filter: -+ - - `FBT` controls the maximum buffer time of output PIDs of a filter - - `FBU` controls the maximum number of packets in buffer of output PIDs of a filter when timing is not available - - `FBD` controls the maximum buffer time of input PIDs of a decoder filter, ignored for other filters -+ - - If another filter sends a buffer requirement messages, the maximum value of `FBT` (resp. `FBD`) and the user requested buffer time will be used for output buffer time (resp. decoding buffer time). - - The options `FBT`, `FBU`, `FBD` and `DBG` can be set: --* per filter instance: `fA reframer:FBU=2` --* per filter class for the run: `--reframer@FBU=2` --* in the GPAC config file in a per-filter section: `[filter@reframer]FBU=2` -+ -+- per filter instance: `fA reframer:FBU=2` -+- per filter class for the run: `--reframer@FBU=2` -+- in the GPAC config file in a per-filter section: `[filter@reframer]FBU=2` -+ - - The default values are defined by the session default parameters `-buffer-gen`, `buffer-units` and `-buffer-dec`. - -diff --git a/docs/Filters/filters_properties.md b/docs/Filters/filters_properties.md -index a8b1f0e3..b8fc355e 100644 ---- a/docs/Filters/filters_properties.md -+++ b/docs/Filters/filters_properties.md -@@ -1,6 +1,6 @@ - - --# GPAC Built-in properties -+# GPAC Built-in properties {:data-level="all"} - - - ## Built-in property types -@@ -41,8 +41,10 @@ alay | channel layout configuration, string or int value from ISO/IEC 23091-3 - ## Built-in properties for PIDs and packets, pixel formats and audio formats - - Flags can be: --* D: droppable property, see [GSF multiplexer](gsfmx) filter help for more info --* P: property applying to packet -+ -+- D: droppable property, see [GSF multiplexer](gsfmx) filter help for more info -+- P: property applying to packet -+ - - Name | type | Flags | Description | 4CC - --- | --- | --- | --- | --- -@@ -55,7 +57,8 @@ ServiceID | uint | D | ID of parent service | PSID - ClockID | uint | D | ID of clock reference PID | CKID - DependencyID | uint | | ID of layer depended on | DPID - SubLayer | bool | | PID is a sublayer of the stream depended on rather than an enhancement layer | DPSL --PlaybackMode | uint | D | Playback mode supported:
    * 0: no time control
    * 1: play/pause/seek,speed=1
    * 2: play/pause/seek,speed>=0
    * 3: play/pause/seek, reverse playback | PBKM -+PlaybackMode | uint | D | Playback mode supported:
    -+- 0: no time control
    - 1: play/pause/seek,speed=1
    - 2: play/pause/seek,speed>=0
    - 3: play/pause/seek, reverse playback | PBKM - Scalable | bool | | Scalable stream | SCAL - TileBase | bool | | Tile base stream | SABT - TileID | uint | | ID of the tile for hvt1/hvt2 PIDs | PTID -@@ -79,7 +82,7 @@ TimeshiftDepth | frac | D | Depth of the timeshift buffer | PTSD - TimeshiftTime | dbl | D | Time in the timeshift buffer in seconds - changes are signaled through PID info (no reconfigure) | PTST - TimeshiftState | uint | D | State of timeshift buffer: 0 is OK, 1 is underflow, 2 is overflow - changes are signaled through PID info (no reconfigure) | PTSS - Timescale | uint | | Media timescale (a timestamp delta of N is N/timescale seconds) | TIMS --ProfileLevel | uint | D | MPEG-4 profile and level | PRPL -+ProfileLevel | uint | D | Profile and level indication | PRPL - DecoderConfig | mem | | Decoder configuration data | DCFG - DecoderConfigEnhancement | mem | | Decoder configuration data of the enhancement layer(s). Also used by 3GPP/Apple text streams to give the full sample description table used in SDP. | ECFG - DSISuperset | bool | | Decoder config is a superset of previous decoder config | DCFS -@@ -104,7 +107,7 @@ BitDepthLuma | uint | | Bit depth for luma components | YBPS - BitDepthChroma | uint | | Bit depth for chroma components | CBPS - FPS | frac | | Video framerate | VFPF - Interlaced | bool | | Video is interlaced | VILC --SAR | frac | | Sample (i.e. pixel) aspect ratio | PSAR -+SAR | frac | | Sample (i.e. pixel) aspect ratio (negative values mean no SAR and removal of info in containers) | PSAR - MaxWidth | uint | | Maximum width (video / text / graphics) of all enhancement layers | MWID - MaxHeight | uint | | Maximum height (video / text / graphics) of all enhancement layers | MHEI - ZOrder | sint | | Z-order of the video, from 0 (first) to max int (last) | VZIX -@@ -117,7 +120,8 @@ CropOrigin | v2di | | Position in source window, X,Y indicate coordinates in so - OriginalSize | v2di | | Original resolution of video | VOWH - SRD | v4di | | Position and size of the video in the referential given by SRDRef | SRD - SRDRef | v2di | | Width and Height of the SRD referential | SRDR --SRDMap | uintl | | Mapping of input videos in reconstructed video, expressed as {Ox,Oy,Ow,Oh,Dx,Dy,Dw,Dh} per input, with:
    * Ox,Oy,Ow,Oh: position and size of the input video (usually matching its `SRD` property), expressed in the output referential given by `SRDRef`
    * Dx,Dy,Dw,Dh: Position and Size of the input video in the reconstructed output, expressed in the output referential given by `SRDRef` | SRDM -+SRDMap | uintl | | Mapping of input videos in reconstructed video, expressed as {Ox,Oy,Ow,Oh,Dx,Dy,Dw,Dh} per input, with:
    -+- Ox,Oy,Ow,Oh: position and size of the input video (usually matching its `SRD` property), expressed in the output referential given by `SRDRef`
    - Dx,Dy,Dw,Dh: Position and Size of the input video in the reconstructed output, expressed in the output referential given by `SRDRef` | SRDM - Alpha | bool | | Video in this PID is an alpha map | VALP - Mirror | uint | | Mirror mode (as bit mask with flags 0: no mirror, 1: along Y-axis, 2: along X-axis) | VMIR - Rotate | uint | | Video rotation as value*90 degree anti-clockwise | VROT -@@ -178,7 +182,8 @@ CENC_SAI | mem | P | CENC SAI for the packet, formatted as (char(IV_Size))IV(u16 - KeyInfo | mem | | Multi key info formatted as:
    `is_mkey(u8);`
    nb_keys(u16);
    [
    IV_size(u8);
    KID(bin128);
    if (!IV_size) {;
    const_IV_size(u8);
    constIV(const_IV_size);
    }
    ]
    ` | CBIV ` - CENCPattern | frac | | CENC crypt pattern, CENC pattern, skip as frac.num crypt as frac.den | CPTR - CENCStore | 4cc | | Storage location 4CC of SAI data | CSTR --CENCstsdMode | uint | | Mode for CENC sample description when using clear samples:
    * 0: single sample description is used
    * 1: a clear clone of the sample description is created, inserted before the CENC sample description
    * 2: a clear clone of the sample description is created, inserted after the CENC sample description | CSTM -+CENCstsdMode | uint | | Mode for CENC sample description when using clear samples:
    -+- 0: single sample description is used
    - 1: a clear clone of the sample description is created, inserted before the CENC sample description
    - 2: a clear clone of the sample description is created, inserted after the CENC sample description | CSTM - AMRModeSet | uint | | ModeSet for AMR and AMR-WideBand | AMST - SubSampleInfo | mem | | Binary blob describing N subsamples of the sample, formatted as N [(u32)flags(u32)size(u32)codec_param(u8)priority(u8) discardable]. Subsamples for a given flag MUST appear in order, however flags can be interleaved | SUBS - NALUMaxSize | uint | | Max size of NAL units in stream - changes are signaled through PID info change (no reconfigure) | NALS -@@ -282,7 +287,8 @@ EQRClamp | v4di | D | Clamping of frame for EQR as 0.32 fixed point (x is top, y - SceneNode | bool | | PID is a scene node decoder (AFX BitWrapper in BIFS) | PSND - OrigCryptoScheme | 4cc | | Original crypto scheme on a decrypted PID | POCS - TSBSegs | uint | D | Time shift in number of segments for HAS streams, only set by dashin and dasher filters | PTSN --IsManifest | uint | D | PID is a HAS manifest (MSB=1 if live)
    * 0: not a manifest
    * 1: DASH manifest
    * 2: HLS manifest
    * 3: GHI(X) manifest | PHSM -+IsManifest | uint | D | PID is a HAS manifest (MSB=1 if live)
    -+- 0: not a manifest
    - 1: DASH manifest
    - 2: HLS manifest
    - 3: GHI(X) manifest | PHSM - Sparse | bool | D | PID has potentially empty times between packets | PSPA - CharSet | str | D | Character set for input text PID | PCHS - ForcedSub | uint | D | PID or Packet is forced sub
    0: not forced
    1: forced frame
    2: all frames are forced (PID only) | PFCS -@@ -521,6 +527,7 @@ dvbs | DVB Subtitles - dvbs | DVB-TeleText - div3 | MS-MPEG4 V3 - caf | Apple Lossless Audio -+dnx | AViD DNxHD - - # Audio channel layout code points (ISO/IEC 23091-3) - -@@ -530,17 +537,17 @@ mono | 1 | 0x0000000000000004 - stereo | 2 | 0x0000000000000003 - 3/0.0 | 3 | 0x0000000000000007 - 3/1.0 | 4 | 0x0000000000000407 --3/2.0 | 5 | 0x0000000000000307 --3/2.1 | 6 | 0x000000000000030f --5/2.1 | 7 | 0x000000000000030f -+3/2.0 | 5 | 0x0000000000000037 -+3/2.1 | 6 | 0x000000000000003f -+5/2.1 | 7 | 0x000000000000003f - 1+1 | 8 | 0x0000000000000003 - 2/1.0 | 9 | 0x0000000000000403 - 2/2.0 | 10 | 0x0000000000000033 - 3/3.1 | 11 | 0x000000000000043f - 3/4.1 | 12 | 0x000000000000033f - 11/11.2 | 13 | 0x000000003ffe67cf --5/2.1 | 14 | 0x000000000006030f --5/5.2 | 15 | 0x000000000606630f -+5/2.1 | 14 | 0x000000000006003f -+5/5.2 | 15 | 0x000000000606603f - 5/4.1 | 16 | 0x000000000036003f - 6/5.1 | 17 | 0x00000000023e003f - 6/7.1 | 18 | 0x00000600023e003f -@@ -838,12 +845,28 @@ gmem | fin httpin | n/a - gfio | fin | fout - http | httpin | httpout - https | httpin | httpout -+dict | httpin | n/a -+ftp | httpin | n/a -+ftps | httpin | n/a -+gopher | httpin | n/a -+gophers | httpin | n/a -+imap | httpin | n/a -+imaps | httpin | n/a -+mqtt | httpin | n/a -+pop3 | httpin | n/a -+pop3s | httpin | n/a -+rtsp | httpin rtpin | rtspout -+smb | httpin | n/a -+smbs | httpin | n/a -+smtp | httpin | n/a -+smtps | httpin | n/a -+telnet | httpin | n/a -+tftp | httpin | n/a - tcp | sockin | sockout - udp | sockin | sockout - tcpu | sockin | sockout - udpu | sockin | sockout - rtp | rtpin | rtpout --rtsp | rtpin | rtspout - rtspu | rtpin | n/a - rtsph | rtpin | rtspout - satip | rtpin | n/a -diff --git a/docs/Filters/fin.md b/docs/Filters/fin.md -index 858c827d..ed2aa5e6 100644 ---- a/docs/Filters/fin.md -+++ b/docs/Filters/fin.md -@@ -1,6 +1,6 @@ - - --# File input -+# File input {:data-level="all"} - - Register name used to load filter: __fin__ - This filter may be automatically loaded during graph resolution. -@@ -15,6 +15,15 @@ The special file name `randsc` is used to generate random data with `0x000001` s - - The filter handles both files and GF_FileIO objects as input URL. - -+## Packet Injecting -+The filter can be used to inject a single packet instead of a file using (-pck)[] option. -+No specific properties are attached, except a timescale if (-ptime)[] is set. -+Example -+``` -+gpac fin:pck=str@"My Sample Text":ptime=2500/100:#CodecID=stxt:#StreamType=text -+``` -+This will declare the PID as WebVTT and send a single packet with payload `My Sample Text` and a timestamp value of 25 second. -+ - - # Options - -@@ -24,4 +33,5 @@ The filter handles both files and GF_FileIO objects as input URL. - __ext__ (cstr): override file extension - __mime__ (cstr): set file mime type - __pck__ (mem): data to use instead of file -+__ptime__ (frac, default: _0/0_): timing for data packet, ignored if den is 0 - -diff --git a/docs/Filters/flist.md b/docs/Filters/flist.md -index 70ebfc6c..c17d9dd2 100644 ---- a/docs/Filters/flist.md -+++ b/docs/Filters/flist.md -@@ -1,6 +1,6 @@ - - --# Sources concatenator -+# Sources concatenator {:data-level="all"} - - Register name used to load filter: __flist__ - This filter may be automatically loaded during graph resolution. -@@ -16,21 +16,27 @@ At each new source, the filter tries to remap input PIDs to already declared out - - The source list mode is activated by using `flist:srcs=f1[,f2]`, where f1 can be a file or a directory to enumerate. - The syntax for directory enumeration is: --* dir, dir/ or dir/*: enumerates everything in directory `dir` --* foo/*.png: enumerates all files with extension png in directory `foo` --* foo/*.png;*.jpg: enumerates all files with extension png or jpg in directory `foo` -+ -+- dir, dir/ or dir/*: enumerates everything in directory `dir` -+- foo/*.png: enumerates all files with extension png in directory `foo` -+- foo/*.png;*.jpg: enumerates all files with extension png or jpg in directory `foo` -+ - - The resulting file list can be sorted using [fsort](#fsort). - If the sort mode is `datex` and source files are images or single frame files, the following applies: -+ - - options [floop](#floop), [revert](#revert) and [fdur](#fdur) are ignored - - the files are sorted by modification time - - the first frame is assigned a timestamp of 0 - - each frame (coming from each file) is assigned a duration equal to the difference of modification time between the file and the next file - - the last frame is assigned the same duration as the previous one -+ - - When sorting by names: -+ - - shorter filenames are inserted before longer filenames - - alphabetical sorting is used if same filename length -+ - - # Playlist mode - -@@ -43,32 +49,40 @@ If the last URL played cannot be found in the playlist, the first URL in the pla - - When [ka](#ka) is used to keep refreshing the playlist on regular basis, the playlist must end with a new line. - Playlist refreshing will abort: -+ - - if the input playlist has a line not ending with a LF `(\n)` character, in order to avoid asynchronous issues when reading the playlist. - - if the input playlist has not been modified for the [timeout](#timeout) option value (infinite by default). - -+ - ## Playlist directives - A playlist directive line can contain zero or more directives, separated with space. The following directives are supported: --* repeat=N: repeats `N` times the content (hence played N+1). --* start=T: tries to play the file from start time `T` seconds (double format only). This may not work with some files/formats not supporting seeking. --* stop=T: stops source playback after `T` seconds (double format only). This works on any source (implemented independently from seek support). --* cat: specifies that the following entry should be concatenated to the previous source rather than opening a new source. This can optionally specify a byte range if desired, otherwise the full file is concatenated. -+ -+- repeat=N: repeats `N` times the content (hence played N+1). -+- start=T: tries to play the file from start time `T` seconds (double format only). This may not work with some files/formats not supporting seeking. -+- stop=T: stops source playback after `T` seconds (double format only). This works on any source (implemented independently from seek support). -+- cat: specifies that the following entry should be concatenated to the previous source rather than opening a new source. This can optionally specify a byte range if desired, otherwise the full file is concatenated. -+ - _Note: When sources are ISOBMFF files or segments on local storage or GF_FileIO objects, the concatenation will be automatically detected._ --* srange=T: when cat is set, indicates the start `T` (64 bit decimal, default 0) of the byte range from the next entry to concatenate. --* send=T: when cat is set, indicates the end `T` (64 bit decimal, default 0) of the byte range from the next entry to concatenate. --* props=STR: assigns properties described in `STR` to all PIDs coming from the listed sources on next line. `STR` is formatted according to `gpac -h doc` using the default parameter set. --* del: specifies that the source file(s) must be deleted once processed, true by default if [fdel](#fdel) is set. --* out=V: specifies splicing start time (cf below). --* in=V: specifies splicing end time (cf below). --* nosync: prevents timestamp adjustments when joining sources (implied if `cat` is set). --* keep: keeps spliced period in output (cf below). --* mark: only inject marker for the splice period and do not load any replacement content (cf below). --* sprops=STR: assigns properties described in `STR` to all PIDs of the main content during a splice (cf below). `STR` is formatted according to `gpac -h doc` using the default parameter set. --* chap=NAME: assigns chapter name at the start of next URL (filter always removes source chapter names). -+ -+- srange=T: when cat is set, indicates the start `T` (64 bit decimal, default 0) of the byte range from the next entry to concatenate. -+- send=T: when cat is set, indicates the end `T` (64 bit decimal, default 0) of the byte range from the next entry to concatenate. -+- props=STR: assigns properties described in `STR` to all PIDs coming from the listed sources on next line. `STR` is formatted according to `gpac -h doc` using the default parameter set. -+- del: specifies that the source file(s) must be deleted once processed, true by default if [fdel](#fdel) is set. -+- out=V: specifies splicing start time (cf below). -+- in=V: specifies splicing end time (cf below). -+- nosync: prevents timestamp adjustments when joining sources (implied if `cat` is set). -+- keep: keeps spliced period in output (cf below). -+- mark: only inject marker for the splice period and do not load any replacement content (cf below). -+- sprops=STR: assigns properties described in `STR` to all PIDs of the main content during a splice (cf below). `STR` is formatted according to `gpac -h doc` using the default parameter set. -+- chap=NAME: assigns chapter name at the start of next URL (filter always removes source chapter names). -+ - - The following global options (applying to the filter, not the sources) may also be set in the playlist: --* ka=N: force [ka](#ka) option to `N` millisecond refresh. --* floop=N: set [floop](#floop) option from within playlist. --* raw: set [raw](#raw) option from within playlist. -+ -+- ka=N: force [ka](#ka) option to `N` millisecond refresh. -+- floop=N: set [floop](#floop) option from within playlist. -+- raw: set [raw](#raw) option from within playlist. -+ - - The default behavior when joining sources is to realign the timeline origin of the new source to the maximum time in all PIDs of the previous sources. - This may create gaps in the timeline in case previous source PIDs are not of equal duration (quite common with most audio codecs). -@@ -97,13 +111,15 @@ __Warning: There shall be a single character, with value space (' '), before and - Example - ``` - src.mp4 @ reframer:rt=on --``` -+``` -+ - This will inject a reframer with real-time regulation between source and `flist` filter. - Example - ``` - src.mp4 @ reframer:saps=1 @1 reframer:saps=0,2,3 - src.mp4 @ reframer:saps=1 @-1 reframer:saps=0,2,3 - ``` -+ - This will inject a reframer filtering only SAP1 frames and a reframer filtering only non-SAP1 frames between source and `flist` filter - - Link options can be specified (see `gpac -h doc`). -@@ -111,6 +127,7 @@ Example - ``` - src.mp4 @#video reframer:rt=on - ``` -+ - This will inject a reframer with real-time regulation between video PID of source and `flist` filter. - - When using filter chains, the `flist` filter will only accept PIDs from the last declared filter in the chain. -@@ -119,13 +136,15 @@ Example - ``` - src.mp4 @#video reframer:rt=on @-1#audio - ``` -+ - This will inject a reframer with real-time regulation between video PID of source and `flist` filter, and will also allow audio PIDs from source to connect to `flist` filter. - - The empty link directive can also be used on the last declared filter - Example - ``` - src.mp4 @ reframer:rt=on @#audio --``` -+``` -+ - This will inject a reframer with real-time regulation between source and `flist` filter and only connect audio PIDs to `flist` filter. - - ## Splicing -@@ -136,11 +155,13 @@ Directive can be used for the main media except concatenation directives. - The splicing operations do not alter media frames and do not perform uncompressed domain operations such as cross-fade or mixing. - - The `out` (resp. `in`) directive specifies the media splice start (resp. end) time. The value can be formatted as follows: --* empty: the time is not yet assigned --* `now`: the time is resolved to the next SAP point in the media --* integer, float or fraction: set time in seconds --* `+VAL`: used for `in` only, specify the end point as delta in seconds from the start point (`VAL` can be integer, float or fraction) --* DATE: set splice time according to wall clock `DATE`, formatted as an `XSD dateTime` -+ -+- empty: the time is not yet assigned -+- `now`: the time is resolved to the next SAP point in the media -+- integer, float or fraction: set time in seconds -+- `+VAL`: used for `in` only, specify the end point as delta in seconds from the start point (`VAL` can be integer, float or fraction) -+- DATE: set splice time according to wall clock `DATE`, formatted as an `XSD dateTime` -+ - The splice times (except wall clock) are expressed in the source (main media) timing, not the reconstructed output timeline. - - When a splice begins (`out` time reached), the source items following the main media are played until the end of the splice or the end of the main media. -@@ -149,12 +170,16 @@ Sources used during the splice period can use directives such as `start`, `dur` - Once a splice is done (`in` time reached), the main media `out` splice time is reset to undefined. - - When the main media has undefined `out` or `in` splice times, the playlist is reloaded at each new main media packet to check for resolved values. -+ - - `out` can only be modified when no splice is active, otherwise it is ignored. If modified, it resets the next source to play to be the one following the modified main media. - - `in` can only be modified when a splice is active with an undefined end time, otherwise it is ignored. -+ - - When the main media is over: -+ - - if `repeat` directive is set, the main media is repeated, `in` and `out` set to their initial values and the next splicing content is the one following the main content, - - otherwise, the next source queued is the one following the last source played during the last splice period. -+ - - It is allowed to defined several main media in the playlist, but a main media is not allowed as media for a splice period. - -@@ -171,6 +196,7 @@ Example - #out=2 in=4 mark sprops=#xlink=http://foo.bar/ - src:#Period=main - ``` -+ - This will inject property xlink on the output PIDs in the splice zone (corresponding to period `main_2`) but not in the rest of the main media. - - Directives `mark`, `keep` and `sprops` are reset at the end of the splice period. -@@ -186,20 +212,22 @@ Directives `mark`, `keep` and `sprops` are reset at the end of the splice period - __ka__ (uint, default: _0_): keep playlist alive (disable loop), waiting for a new input to be added or `#end` directive to end playlist. The value specifies the refresh rate in ms - __timeout__ (luint, default: _-1_): timeout in ms after which the playlist is considered dead (`-1` means indefinitely) - __fsort__ (enum, default: _no_): sort list of files --* no: no sorting, use default directory enumeration of OS --* name: sort by alphabetical name --* size: sort by increasing size --* date: sort by increasing modification time --* datex: sort by increasing modification time -+ -+- no: no sorting, use default directory enumeration of OS -+- name: sort by alphabetical name -+- size: sort by increasing size -+- date: sort by increasing modification time -+- datex: sort by increasing modification time - - __sigcues__ (bool, default: _false_): inject `CueStart` property at each source begin (new or repeated) for DASHing - __fdel__ (bool, default: _false_): delete source files after processing in playlist mode (does not delete the playlist) - __keepts__ (bool, default: _false_): keep initial timestamps unmodified (no reset to 0) - __raw__ (enum, default: _no_): force input AV streams to be in raw format --* no: do not force decoding of inputs --* av: force decoding of audio and video inputs --* a: force decoding of audio inputs --* v: force decoding of video inputs -+ -+- no: do not force decoding of inputs -+- av: force decoding of audio and video inputs -+- a: force decoding of audio inputs -+- v: force decoding of video inputs - - __flush__ (bool, default: _false_): send a flush signal once playlist is done before entering keepalive - -diff --git a/docs/Filters/fout.md b/docs/Filters/fout.md -index 1ddae11f..006551db 100644 ---- a/docs/Filters/fout.md -+++ b/docs/Filters/fout.md -@@ -1,6 +1,6 @@ - - --# File output -+# File output {:data-level="all"} - - Register name used to load filter: __fout__ - This filter may be automatically loaded during graph resolution. -@@ -18,14 +18,17 @@ In this case it accepts ANY type of input PID, not just file ones. - # HTTP streaming recording - - When recording a DASH or HLS session, the number of segments to keep per quality can be set using [max_cache_segs](#max_cache_segs). -+ - - value `0` keeps everything (default behaviour) - - a negative value `N` will keep `-N` files regardless of the time-shift buffer value - - a positive value `N` will keep `MAX(N, time-shift buffer)` files -+ - - Example - ``` - gpac -i LIVE_MPD dashin:forward=file -o rec/$File$:max_cache_segs=3 --``` -+``` -+ - This will force keeping a maximum of 3 media segments while recording the DASH session. - - -@@ -39,14 +42,16 @@ This will force keeping a maximum of 3 media segments while recording the DASH s - __ext__ (cstr): set extension for graph resolution, regardless of file extension - __mime__ (cstr): set mime type for graph resolution - __cat__ (enum, default: _none_): cat each file of input PID rather than creating one file per filename --* none: never cat files --* auto: only cat if files have same names --* all: always cat regardless of file names -+ -+- none: never cat files -+- auto: only cat if files have same names -+- all: always cat regardless of file names - - __ow__ (enum, default: _yes_): overwrite output mode when concatenation is not used --* yes: override file if existing --* no: throw error if file existing --* ask: interactive prompt -+ -+- yes: override file if existing -+- no: throw error if file existing -+- ask: interactive prompt - - __mvbk__ (uint, default: _8192_): block size used when moving parts of the file around in patch mode - __redund__ (bool, default: _false_): keep redundant packet in output file -diff --git a/docs/Filters/ghidmx.md b/docs/Filters/ghidmx.md -index 896b6a57..d7272f3c 100644 ---- a/docs/Filters/ghidmx.md -+++ b/docs/Filters/ghidmx.md -@@ -1,6 +1,6 @@ - - --# GHI demultiplexer -+# GHI demultiplexer {:data-level="all"} - - Register name used to load filter: __ghidmx__ - This filter may be automatically loaded during graph resolution. -@@ -16,11 +16,13 @@ Example - ``` - gpac -i SRC [... -i SRCn] -o index.ghi:segdur=2 - ``` -+ - This constructs a binary index for the DASH session. - Example - ``` - gpac -i SRC -o index.ghix:segdur=2 --``` -+``` -+ - This constructs an XML index for the DASH session. - - __Warning: XML indexes should only be used for debug purposes as they can take a significant amount of time to be parsed.__ -@@ -33,13 +35,15 @@ The index can be used to generate manifest, child variants for HLS, init segment - Example - ``` - gpac -i index.ghi:gm=all -o dash/vod.mpd --``` -+``` -+ - This generates manifest(s) and init segment(s). - - Example - ``` - gpac -i index.ghi:rep=FOO:sn=10 -o dash/vod.mpd --``` -+``` -+ - This generates the 10th segment of representation with ID `FOO`. - - _Note: The manifest file(s) and init segment(s) are not written when generating a segment. The manifest target (mpd or m3u8) is only used to setup the filter chain and target output path._ -@@ -47,25 +51,31 @@ _Note: The manifest file(s) and init segment(s) are not written when generating - Example - ``` - gpac -i index.ghi:gm=main -o dash/vod.m3u8 --``` -+``` -+ - This generates main manifest only (MPD or master HLS playlist). - - Example - ``` - gpac -i index.ghi:gm=child:rep=FOO:out=BAR -o dash/vod.m3u8 --``` -+``` -+ - This generates child manifest for representation `FOO` in file `BAR`. - - Example - ``` - gpac -i index.ghi:gm=init:rep=FOO:out=BAR2 -o dash/vod.m3u8 - ``` -+ - This generates init segment for representation `FOO` in file `BAR2`. - - The filter outputs are PIDs using framed packets marked with segment boundaries and can be chained to other filters before entering the dasher (e.g. for encryption, transcode...). - - If representation IDs are not assigned during index creation, they default to the 1-based index of the source. You can check them using: --EX: `gpac -i src.ghi inspect:full` -+Example -+``` -+gpac -i src.ghi inspect:full -+``` - - # Muxed Representations - -@@ -73,42 +83,50 @@ The filter can be used to generate muxed representations, either at manifest gen - Example - ``` - gpac -i index.ghi:mux=A@V1@V2 -o dash/vod.mpd --``` -+``` -+ - This will generate a manifest muxing representations `A` with representations `V1` and `V2`. - - Example - ``` - gpac -i index.ghi:mux=A@V1@V2,T@V1@V2 -o dash/vod.mpd --``` -+``` -+ - This will generate a manifest muxing representations `A` and `T` with representations `V1` and `V2`. - - Example - ``` - gpac -i index.ghi:rep=V2:sn=5:mux=A@V2 -o dash/vod.mpd --``` -+``` -+ - This will generate the 5th segment containing representations `A` and `V2`. - - The filter does not store any state, it is the user responsibility to use consistent information across calls: -+ - - do not change segment templates - - do not change muxed representations to configurations not advertised in the generated manifests -+ - - # Recommendations - - Indexing supports fragmented and non-fragmented MP4, MPEG-2 TS and seekable inputs. -+ - - It is recommended to use fragmented MP4 as input format since this greatly reduces file loading times. - - If non-fragmented MP4 are used, it is recommended to use single-track files to decrease the movie box size and speedup parsing. - - MPEG-2 TS sources will be slower since they require PES reframing and AU reformating, resulting in more IOs than with mp4. - - other seekable sources will likely be slower (seeking, reframing) and are not recommended. -+ - - - # Options - - __gm__ (enum, default: _main_): manifest generation mode --* none: no manifest generation (implied if sn is not 0) --* all: generate all manifests and init segments --* main: generate main manifest (MPD or master M3U8) --* child: generate child playlist for HLS --* init: generate init segment -+ -+- none: no manifest generation (implied if sn is not 0) -+- all: generate all manifests and init segments -+- main: generate main manifest (MPD or master M3U8) -+- child: generate child playlist for HLS -+- init: generate init segment - - __force__ (bool, default: _false_): force loading sources in manifest generation for debug - __rep__ (str): representation to generate -diff --git a/docs/Filters/glpush.md b/docs/Filters/glpush.md -index 4cfb1c03..dcbcad0e 100644 ---- a/docs/Filters/glpush.md -+++ b/docs/Filters/glpush.md -@@ -1,9 +1,9 @@ - - --# GPU texture uploader -+# GPU texture uploader {:data-level="all"} - - Register name used to load filter: __glpush__ --This is a JavaScript filter, not checked during graph resolution and needs explicit loading. -+This is a JavaScript filter. It is not checked during graph resolution and needs explicit loading. - Author: GPAC team - - This filter pushes input video streams to GPU as OpenGL textures. It can be used to simulate hardware decoders dispatching OpenGL textures -diff --git a/docs/Filters/gpac_general.md b/docs/Filters/gpac_general.md -index 53726f93..a25583d0 100644 ---- a/docs/Filters/gpac_general.md -+++ b/docs/Filters/gpac_general.md -@@ -1,13 +1,15 @@ - --# General Usage of gpac -+# General Usage of gpac {:data-level="all"} - Usage: gpac [options] FILTER [LINK] FILTER [...] - gpac is GPAC's command line tool for setting up and running filter chains. - - _FILTER_: a single filter declaration (e.g., `-i file`, `-o dump`, `inspect`, ...), see [gpac -h doc](filters_general#filter-declaration-filter). - _[LINK]_: a link instruction (e.g., `@`, `@2`, `@2#StreamType=Visual`, ...), see [gpac -h doc](filters_general#explicit-links-between-filters-link). - _[options]_: one or more option strings, each starting with a `-` character. -- - an option using a single `-` indicates an option of gpac (see [gpac -hx](gpac_general#h)) or of libgpac (see [gpac -hx core](core_options)) -- - an option using `--` indicates a global filter or meta-filter (e.g. FFmpeg) option, e.g. `--block_size=1000` or `--profile=Baseline` (see [gpac -h doc](core_config#global-filter-options)) -+ -+ - an option using a single `-` indicates an option of gpac (see [gpac -hx](gpac_general#h)) or of libgpac (see [gpac -hx core](core_options)) -+ - an option using `--` indicates a global filter or meta-filter (e.g. FFmpeg) option, e.g. `--block_size=1000` or `--profile=Baseline` (see [gpac -h doc](core_config#global-filter-options)) -+ - - Filter declaration order may impact the link resolver which will try linking in declaration order. Most of the time for simple graphs, this has no impact. However, for complex graphs with no link declarations, this can lead to different results. - Options do not require any specific order, and may be present anywhere, including between link statements or filter declarations. -@@ -17,6 +19,8 @@ The session can be interrupted at any time using `ctrl+c`, which can also be use - - The possible options for gpac are: - -+__-mem-track__: enable memory tracker -+__-mem-track-stack__: enable memory tracker with stack dumping - __-ltf__: load test-unit filters (used for for unit tests only) - __-sloop__ (int): loop execution of session, creating a session at each loop, mainly used for testing. If no value is given, loops forever - __-runfor__ (int): run for the given amount of milliseconds, exit with full session flush -@@ -29,11 +33,13 @@ The possible options for gpac are: - __-qe__: enable quick exit (no mem cleanup) - __-k__: enable keyboard interaction from command line - __-r__ (string): enable reporting --* r: runtime reporting --* r=FA[,FB]: runtime reporting but only print given filters, e.g. `r=mp4mx` for ISOBMFF multiplexer only --* r=: only print final report -+ -+- r: runtime reporting -+- r=FA[,FB]: runtime reporting but only print given filters, e.g. `r=mp4mx` for ISOBMFF multiplexer only -+- r=: only print final report - - __-seps__ (string, default: __:=#,!@__): set the default character sets used to separate various arguments -+ - - the first char is used to separate argument names - - the second char, if present, is used to separate names and values - - the third char, if present, is used to separate fragments for PID sources -@@ -55,40 +61,45 @@ The possible options for gpac are: - __-step__: test step mode in non-blocking session - __-h__,__-help,-ha,-hx,-hh__ (string): print help. Use `-help` or `-h` for basic options, `-ha` for advanced options, `-hx` for expert options and `-hh` for all. - _Note: The `@` character can be used in place of the `*` character. String parameter can be:_ --* empty: print command line options help --* doc: print the general filter info --* alias: print the gpac alias syntax --* log: print the log system help --* core: print the supported libgpac core options. Use -ha/-hx/-hh for advanced/expert options --* cfg: print the GPAC configuration help --* net: print network interfaces --* prompt: print the GPAC prompt help when running in interactive mode (see [-k](gpac_general/#k) ) --* modules: print available modules --* module NAME: print info and options of module `NAME` --* creds: print credential help --* filters: print name of all available filters --* filters:*: print name of all available filters, including meta filters --* codecs: print the supported builtin codecs - use `-hx` to include unmapped codecs (ffmpeg, ...) --* formats: print the supported formats (`-ha`: print filter names, `-hx`: include meta filters (ffmpeg,...), `-hh`: print mime types) --* protocols: print the supported protocol schemes (`-ha`: print filter names, `-hx`: include meta filters (ffmpeg,...), `-hh`: print all) --* props: print the supported builtin PID and packet properties --* props PNAME: print the supported builtin PID and packet properties mentioning `PNAME` --* colors: print the builtin color names and their values --* layouts: print the builtin CICP audio channel layout names and their values --* links: print possible connections between each supported filters (use -hx to view src->dst cap bundle detail) --* links FNAME: print sources and sinks for filter `FNAME` (either builtin or JS filter) --* defer: print defer mode help --* FNAME: print filter `FNAME` info (multiple FNAME can be given) -- - For meta-filters, use `FNAME:INST`, e.g. `ffavin:avfoundation` -- - Use `*` to print info on all filters (_big output!_), `*:*` to print info on all filters including meta filter instances (_really big output!_) -- - By default only basic filter options and description are shown. Use `-ha` to show advanced options capabilities, `-hx` for expert options, `-hh` for all options and filter capabilities including on filters disabled in this build --* FNAME.OPT: print option `OPT` in filter `FNAME` --* OPT: look in filter names and options for `OPT` and suggest possible matches if none found. Use `-hx` to look for keyword in all option descriptions -+ -+- empty: print command line options help -+- doc: print the general filter info -+- alias: print the gpac alias syntax -+- log: print the log system help -+- core: print the supported libgpac core options. Use -ha/-hx/-hh for advanced/expert options -+- cfg: print the GPAC configuration help -+- net: print network interfaces -+- prompt: print the GPAC prompt help when running in interactive mode (see [-k](gpac_general/#k) ) -+- modules: print available modules -+- module NAME: print info and options of module `NAME` -+- creds: print credential help -+- filters: print name of all available filters -+- filters:*: print name of all available filters, including meta filters -+- codecs: print the supported builtin codecs - use `-hx` to include unmapped codecs (ffmpeg, ...) -+- formats: print the supported formats (`-ha`: print filter names, `-hx`: include meta filters (ffmpeg,...), `-hh`: print mime types) -+- protocols: print the supported protocol schemes (`-ha`: print filter names, `-hx`: include meta filters (ffmpeg,...), `-hh`: print all) -+- props: print the supported builtin PID and packet properties -+- props PNAME: print the supported builtin PID and packet properties mentioning `PNAME` -+- colors: print the builtin color names and their values -+- layouts: print the builtin CICP audio channel layout names and their values -+- links: print possible connections between each supported filters (use -hx to view src->dst cap bundle detail) -+- links FNAME: print sources and sinks for filter `FNAME` (either builtin or JS filter) -+- defer: print defer mode help -+- FNAME: print filter `FNAME` info (multiple FNAME can be given) -+ -+ - For meta-filters, use `FNAME:INST`, e.g. `ffavin:avfoundation` -+ - Use `*` to print info on all filters (_big output!_), `*:*` to print info on all filters including meta filter instances (_really big output!_) -+ - By default only basic filter options and description are shown. Use `-ha` to show advanced options capabilities, `-hx` for expert options, `-hh` for all options and filter capabilities including on filters disabled in this build -+ -+- FNAME.OPT: print option `OPT` in filter `FNAME` -+- OPT: look in filter names and options for `OPT` and suggest possible matches if none found. Use `-hx` to look for keyword in all option descriptions - - - __-p__ (string): use indicated profile for the global GPAC config. If not found, config file is created. If a file path is indicated, this will load profile from that file. Otherwise, this will create a directory of the specified name and store new config there. The following reserved names create a temporary profile (not stored on disk): --* 0: full profile --* n: null profile disabling shared modules/filters and system paths in config (may break GUI and other filters) -+ -+- 0: full profile -+- n: null profile disabling shared modules/filters and system paths in config (may break GUI and other filters) -+ - Appending `:reload` to the profile name will force recreating a new configuration file - - __-alias__ (string): assign a new alias or remove an alias. Can be specified several times. See [alias usage (-h alias)](#using-aliases) -@@ -102,6 +113,7 @@ Appending `:reload` to the profile name will force recreating a new configuratio - __-xopt__: unrecognized options and filters declaration following this option are ignored - used to pass arguments to GUI - - __-creds__ (string): setup credentials as used by servers -+__-rv__: return absolute value of GPAC internal error instead of 1 when error - - - The following libgpac core options allow customizing the filter session: -@@ -110,21 +122,24 @@ The following libgpac core options allow customizing the filter session: - __-full-link__: throw error if any PID in the filter graph cannot be linked - __-no-dynf__: disable dynamically loaded filters - __-no-block__ (Enum, default: __no__): disable blocking mode of filters --* no: enable blocking mode --* fanout: disable blocking on fan-out, unblocking the PID as soon as one of its destinations requires a packet --* all: disable blocking -+ -+- no: enable blocking mode -+- fanout: disable blocking on fan-out, unblocking the PID as soon as one of its destinations requires a packet -+- all: disable blocking - - __-no-reg__: disable regulation (no sleep) in session - __-no-reassign__: disable source filter reassignment in PID graph resolution - __-sched__ (Enum, default: __free__): set scheduler mode --* free: lock-free queues except for task list (default) --* lock: mutexes for queues when several threads --* freex: lock-free queues including for task lists (experimental) --* flock: mutexes for queues even when no thread (debug mode) --* direct: no threads and direct dispatch of tasks whenever possible (debug mode) -+ -+- free: lock-free queues except for task list (default) -+- lock: mutexes for queues when several threads -+- freex: lock-free queues including for task lists (experimental) -+- flock: mutexes for queues even when no thread (debug mode) -+- direct: no threads and direct dispatch of tasks whenever possible (debug mode) - - __-max-chain__ (int, default: __6__): set maximum chain length when resolving filter links. Default value covers for _[ in -> ] dmx -> reframe -> decode -> encode -> reframe -> mx [ -> out]_. Filter chains loaded for adaptation (e.g. pixel format change) are loaded after the link resolution. Setting the value to 0 disables dynamic link resolution. You will have to specify the entire chain manually - __-max-sleep__ (int, default: __50__): set maximum sleep time slot in milliseconds when regulation is enabled -+__-step-link__: load filters one by one when solvink a link instead of loading all filters for the solved path - __-threads__ (int): set N extra thread for the session. -1 means use all available cores - __-no-probe__: disable data probing on sources and relies on extension (faster load but more error-prone) - __-no-argchk__: disable tracking of argument usage (all arguments will be considered as used) -@@ -138,28 +153,35 @@ The following libgpac core options allow customizing the filter session: - The gpac command line can become quite complex when many sources or filters are used. In order to simplify this, an alias system is provided. - - To assign an alias, use the syntax `gpac -alias="NAME VALUE"`. --* `NAME`: shall be a single string, with no space. --* `VALUE`: the list of argument this alias replaces. If not set, the alias is destroyed -+ -+- `NAME`: shall be a single string, with no space. -+- `VALUE`: the list of argument this alias replaces. If not set, the alias is destroyed -+ - - When parsing arguments, the alias will be replace by its value. - Example - ``` - gpac -alias="output aout vout" --``` -+``` -+ - This allows later audio and video playback using `gpac -i src.mp4 output` - - Aliases can use arguments from the command line. The allowed syntaxes are: --* `@{a}`: replaced by the value of the argument with index `a` after the alias --* `@{a,b}`: replaced by the value of the arguments with index `a` and `b` --* `@{a:b}`: replaced by the value of the arguments between index `a` and `b` --* `@{-a,b}`: replaced by the value of the arguments with index `a` and `b`, inserting a list separator (comma by default) between them --* `@{-a:b}`: replaced by the value of the arguments between index `a` and `b`, inserting a list separator (comma by default) between them --* `@{+a,b}`: clones the parent word in the alias for `a` and `b`, replacing this pattern in each clone by the corresponding argument --* `@{+a:b}`: clones the parent word in the alias for each argument between index `a` and `b`, replacing this pattern in each clone by the corresponding argument -+ -+- `@{a}`: replaced by the value of the argument with index `a` after the alias -+- `@{a,b}`: replaced by the value of the arguments with index `a` and `b` -+- `@{a:b}`: replaced by the value of the arguments between index `a` and `b` -+- `@{-a,b}`: replaced by the value of the arguments with index `a` and `b`, inserting a list separator (comma by default) between them -+- `@{-a:b}`: replaced by the value of the arguments between index `a` and `b`, inserting a list separator (comma by default) between them -+- `@{+a,b}`: clones the parent word in the alias for `a` and `b`, replacing this pattern in each clone by the corresponding argument -+- `@{+a:b}`: clones the parent word in the alias for each argument between index `a` and `b`, replacing this pattern in each clone by the corresponding argument -+ - - The specified index can be: --* forward index: a strictly positive integer, 1 being the first argument after the alias --* backward index: the value 'n' (or 'N') to indicate the last argument on the command line. This can be followed by `-x` to rewind arguments (e.g. `@{n-1}` is the before last argument) -+ -+- forward index: a strictly positive integer, 1 being the first argument after the alias -+- backward index: the value 'n' (or 'N') to indicate the last argument on the command line. This can be followed by `-x` to rewind arguments (e.g. `@{n-1}` is the before last argument) -+ - - Before solving aliases, all option arguments are moved at the beginning of the command line. This implies that alias arguments cannot be options. - Arguments not used by any aliases are kept on the command line, other ones are removed -@@ -167,22 +189,26 @@ Arguments not used by any aliases are kept on the command line, other ones are r - Example - ``` - -alias="foo src=@{N} dst=test.mp4" --``` -+``` -+ - The command `gpac foo f1 f2` expands to `gpac src=f2 dst=test.mp4 f1` - Example - ``` - -alias="list: inspect src=@{+:N}" - ``` -+ - The command `gpac list f1 f2 f3` expands to `gpac inspect src=f1 src=f2 src=f3` - Example - ``` - -alias="list inspect src=@{+2:N}" - ``` -+ - The command `gpac list f1 f2 f3` expands to `gpac inspect src=f2 src=f3 f1` - Example - ``` - -alias="plist aout vout flist:srcs=@{-,N}" - ``` -+ - The command `gpac plist f1 f2 f3` expands to `gpac aout vout flist:srcs="f1,f2,f3"` - - Alias documentation can be set using `gpac -aliasdoc="NAME VALUE"`, with `NAME` the alias name and `VALUE` the documentation. -@@ -198,17 +224,19 @@ The file can be overwritten using the [-users](core_options/#users) option. - By default, this file does not exist until at least one user has been configured. - - The [creds](#creds) option allows inspecting or modifying the users and groups information. The syntax for the option value is: --* `show` or no value: prints the `users.cfg` file --* `reset`: deletes the `users.cfg` file (i.e. deletes all users and groups) --* `NAME`: show information of user `NAME` --* `+NAME`: adds user `NAME` --* `+NAME:I1=V1[,I2=V2]`: sets info `I1` with value `V1` to user `NAME`. The info name `password` resets password without prompt. --* `-NAME`: removes user `NAME` --* `_NAME`: force password change of user `NAME` --* `@NAME`: show information of group `NAME` --* `@+NAME[:u1[,u2]]`: adds group `NAME` if not existing and adds specified users to group --* `@-NAME:u1[,u2]`: removes specified users from group `NAME` --* `@-NAME`: removes group `NAME` -+ -+- `show` or no value: prints the `users.cfg` file -+- `reset`: deletes the `users.cfg` file (i.e. deletes all users and groups) -+- `NAME`: show information of user `NAME` -+- `+NAME`: adds user `NAME` -+- `+NAME:I1=V1[,I2=V2]`: sets info `I1` with value `V1` to user `NAME`. The info name `password` resets password without prompt. -+- `-NAME`: removes user `NAME` -+- `_NAME`: force password change of user `NAME` -+- `@NAME`: show information of group `NAME` -+- `@+NAME[:u1[,u2]]`: adds group `NAME` if not existing and adds specified users to group -+- `@-NAME:u1[,u2]`: removes specified users from group `NAME` -+- `@-NAME`: removes group `NAME` -+ - - By default all added users are members of the group `users`. - Passwords are not stored, only a SHA256 hash is stored. -@@ -223,12 +251,14 @@ rg=bar - ``` - - The following keys are defined per directory, but may be ignored by the server depending on its operation mode: --* ru: comma-separated list of user names with read access to the directory --* rg: comma-separated list of group names with read access to the directory --* wu: comma-separated list of user names with write access to the directory --* wg: comma-separated list of group names with write access to the directory --* mcast: comma-separated list of user names with multicast creation rights (RTSP server only) --* filters: comma-separated list of filter names for which the directory is valid. If not found or `all`, applies to all filters -+ -+- ru: comma-separated list of user names with read access to the directory -+- rg: comma-separated list of group names with read access to the directory -+- wu: comma-separated list of user names with write access to the directory -+- wg: comma-separated list of group names with write access to the directory -+- mcast: comma-separated list of user names with multicast creation rights (RTSP server only) -+- filters: comma-separated list of filter names for which the directory is valid. If not found or `all`, applies to all filters -+ - - Rights can be configured on sub-directories by adding sections for the desired directories. - Example -@@ -238,8 +268,74 @@ rg=bar - [d1/d2] - ru=foo - ``` -+ - With this configuration: -+ - - the directory `d1` will be readable by all members of group `bar` - - the directory `d1/d2` will be readable by user `foo` only -+ - - Servers in GPAC currently only support the `Basic` HTTP authentication scheme, and should preferably be run over TLS. -+ -+# Defer test mode -+ -+This mode can be used to test loading filters one by one and asking for link resolution explicitly. -+This is mostly used to reproduce how sessions are build in more complex applications. -+ -+The options `rl`, `pi`, `pl` and `pd` allow adressing a filter by index `F` in a list. -+ -+- if the option is suffixed with an `x` (e.g. `rlx=`), `F=0` means the last filter in the list of filters in the session -+- otherwise, `F=0` means the last filter declared before the option -+ -+ -+The relink options `-rl` and `-rlx` always flush the session (run until no more tasks are scheduled). -+The last run can be omitted. -+ -+Example -+``` -+gpac -dl -np -i SRC reframer -g -rl -g inspect -g -rl -+``` -+ -+This will load SRC and reframer, print the graph (no connection), relink SRC, print the graph (connection to reframer), insert inspect, print the graph (no connection), relink reframer and run. No play event is sent here. -+Example -+``` -+gpac -dl -np -i SRC reframer inspect:deep -g -rl=2 -g -rl -se -+``` -+ -+This will load SRC, reframer and inspect, print the graph (no connection), relink SRC, print the graph (connection to reframer), print the graph (no connection), relink reframer, send play and run. -+ -+Linking can be done once filters are loaded, using the syntax `@F@SRC` or `@@F@SRC`: -+ -+- `@F` indicates the destination filter using a 0-based index `F` starting from the last laoded filter, e.g. `@0` indicates the last loaded filter. -+- `@@F` indicates the target filter using a 0-based index `F` starting from the first laoded filter, e.g. `@@1` indicates the second loaded filter. -+- `@SRC`or `@@SRC`: same syntax as link directives -+ -+Sources MUST be set before relinking outputs using (-rl)[]. -+Example -+``` -+gpac -dl -i SRC F1 F2 [...] @1@2 @0@2 -+``` -+ -+This will set SRC as source to F1 and SRC as source to F2 after loading all filters. -+ -+The following options are used in defer mode: -+ -+__-dl__: enable defer linking mode for step-by-step graph building tests -+__-np__: prevent play event from sinks -+__-rl[=F]__ (string): relink outputs of filter `F` (default 1) -+__-wl[=F]__ (string): same as `-rl` but does not flush session) -+__-pi=[+|-][F[:i]]__ (string): print PID properties (all or of index `i`) of filter `F` (default 0) -+ -+- if prefixed with `-`: only list PIDs -+- if prefixed with `+`: also print PID info -+ -+__-pl=[+][F[:i]]@NAME__ (string): probe filter chain from filter `F` (default 0) to the given filter `NAME`: -+ -+- if prefixed with `+`: print all known chains and their priorities -+ -+__-pd=[F[:i]]__ (string): print possible PID destinations (all or of index `i`) of filter `F` (default 0) -+__-f__: flush session until no more tasks -+__-g__: print graph -+__-s__: print stats -+__-se__: send PLAY event from sinks (only done once) -+__-m__ (string): print message -diff --git a/docs/Filters/gsfdmx.md b/docs/Filters/gsfdmx.md -index fa3a6616..88a3408e 100644 ---- a/docs/Filters/gsfdmx.md -+++ b/docs/Filters/gsfdmx.md -@@ -1,6 +1,6 @@ - - --# GSF demultiplexer -+# GSF demultiplexer {:data-level="all"} - - Register name used to load filter: __gsfdmx__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/gsfmx.md b/docs/Filters/gsfmx.md -index 6cbd3c9e..3a7520c1 100644 ---- a/docs/Filters/gsfmx.md -+++ b/docs/Filters/gsfmx.md -@@ -1,6 +1,6 @@ - - --# GSF Multiplexer -+# GSF Multiplexer {:data-level="all"} - - Register name used to load filter: __gsfmx__ - This filter may be automatically loaded during graph resolution. -@@ -60,9 +60,10 @@ This will DASH the source, store the manifest file and the media streams with th - __sigbo__ (bool, default: _false_): signal byte offset - __sigdts__ (bool, default: _true_): signal decoding timestamp - __dbg__ (enum, default: _no_): set debug mode --* no: disable debug --* nodata: force packet size to 0 --* nopck: skip packet -+ -+- no: disable debug -+- nodata: force packet size to 0 -+- nopck: skip packet - - __key__ (mem): encrypt packets using given key - __IV__ (mem): set IV for encryption - a constant IV is used to keep packet overhead small (cbcs-like) -diff --git a/docs/Filters/hevcmerge.md b/docs/Filters/hevcmerge.md -index 6ad04ac0..9323549e 100644 ---- a/docs/Filters/hevcmerge.md -+++ b/docs/Filters/hevcmerge.md -@@ -1,6 +1,6 @@ - - --# HEVC Tile merger -+# HEVC Tile merger {:data-level="all"} - - Register name used to load filter: __hevcmerge__ - This filter may be automatically loaded during graph resolution. -@@ -22,8 +22,10 @@ If `CropOrigin` is used, it shall be set on all input sources. - If positive coordinates are used, they specify absolute positioning in pixels of the tiles. The coordinates are automatically adjusted to the next multiple of max CU width and height. - If negative coordinates are used, they specify relative positioning (e.g. `0x-1` indicates to place the tile below the tile 0x0). - In this mode, it is the caller responsibility to set coordinates so that all tiles in a column have the same width and only the last row/column uses non-multiple of max CU width/height values. The filter will complain and abort if this is not respected. -+ - - If an horizontal blank is detected in the layout, an empty column in the tiling grid will be inserted. - - If a vertical blank is detected in the layout, it is ignored. -+ - - - ## Spatial Relationship Description (SRD) -@@ -32,31 +34,39 @@ The filter will create an `SRDMap` property in the output PID if `SRDRef` and `S - The `SRDMap` allows forwarding the logical sources `SRD` in the merged PID. - The output PID `SRDRef` is set to the output video size. - The input `SRDRef` and `SRD` are usually specified in DASH MPD, but can be manually assigned to inputs. -+ - - `SRDRef` gives the size of the referential used for the input `SRD` (usually matches the original video size, but not always) - - `SRD` gives the size and position of the input in the original video, expressed in `SRDRef` referential of the input. -+ - The inputs do not need to have matching `SRDRef` - .EX src1:SRD=0x0x640x480:SRDRef=1280x720 - This indicates that `src1` contains a video located at 0,0, with a size of 640x480 pixels in a virtual source of 1280x720 pixels. - Example - ``` - src2:SRD=640x0x640x480:SRDRef=1280x720 --``` -+``` -+ - This indicates that `src1` contains a video located at 640,0, with a size of 640x480 pixels in a virtual source of 1280x720 pixels. - - Each merged input is described by 8 integers in the output `SRDMap`: -+ - - the source `SRD` is rescaled in the output `SRDRef` to form the first part (4 integers) of the `SRDMap` (i.e. _where was the input ?_) - - the source location in the reconstructed video forms the second part (4 integers) of the `SRDMap` (i.e. _where are the input pixels in the output ?_) -+ - - Assuming the two sources are encoded at 320x240 and merged as src2 above src1, the output will be a 320x480 video with a `SRDMap` of {0,160,160,240,0,0,320,240,0,0,160,240,0,240,320,240} - _Note: merged inputs are always listed in `SRDMap` in their tile order in the output bitstream._ - - Alternatively to using `SRD` and `SRDRef`, it is possible to specify `CropOrigin` property on the inputs, in which case: -+ - - the `CropOrigin` gives the location in the source - - the input size gives the size in the source, and no rescaling of referential is done -+ - Example - ``` - src1:CropOrigin=0x0 src1:CropOrigin=640x0 --``` -+``` -+ - Assuming the two sources are encoded at 320x240 and merged as src1 above src2, the output will be a 320x480 video with a `SRDMap` of `{0,0,320,240,0,0,320,240,640,0,320,240,0,240,320,240}` - - -diff --git a/docs/Filters/hevcsplit.md b/docs/Filters/hevcsplit.md -index b494b5fc..62e84792 100644 ---- a/docs/Filters/hevcsplit.md -+++ b/docs/Filters/hevcsplit.md -@@ -1,6 +1,6 @@ - - --# HEVC tile splitter -+# HEVC tile splitter {:data-level="all"} - - Register name used to load filter: __hevcsplit__ - This filter is not checked during graph resolution and needs explicit loading. -diff --git a/docs/Filters/httpin.md b/docs/Filters/httpin.md -index 0a521eec..5e44cb52 100644 ---- a/docs/Filters/httpin.md -+++ b/docs/Filters/httpin.md -@@ -1,6 +1,6 @@ - - --# HTTP input -+# HTTP input {:data-level="all"} - - Register name used to load filter: __httpin__ - This filter may be automatically loaded during graph resolution. -@@ -19,13 +19,14 @@ _Note: Unless disabled at session level (see [-no-probe](core_options/#no-probe) - __src__ (cstr): URL of source content - __block_size__ (uint, default: _100000_): block size used to read file - __cache__ (enum, default: _none_): set cache mode --* auto: cache to disk if content length is known, no cache otherwise --* disk: cache to disk, discard once session is no longer used --* keep: cache to disk and keep --* mem: stores to memory, discard once session is no longer used --* mem_keep: stores to memory, keep after session is reassigned but move to `mem` after first download --* none: no cache --* none_keep: stores to memory, keep after session is reassigned but move to `none` after first download -+ -+- auto: cache to disk if content length is known, no cache otherwise -+- disk: cache to disk, discard once session is no longer used -+- keep: cache to disk and keep -+- mem: stores to memory, discard once session is no longer used -+- mem_keep: stores to memory, keep after session is reassigned but move to `mem` after first download -+- none: no cache -+- none_keep: stores to memory, keep after session is reassigned but move to `none` after first download - - __range__ (lfrac, default: _0-0_): set byte range, as fraction - __ext__ (cstr): override file extension -diff --git a/docs/Filters/httpout.md b/docs/Filters/httpout.md -index 52218a7c..05825afc 100644 ---- a/docs/Filters/httpout.md -+++ b/docs/Filters/httpout.md -@@ -1,21 +1,25 @@ - - --# HTTP Server -+# HTTP Server {:data-level="all"} - - Register name used to load filter: __httpout__ - This filter may be automatically loaded during graph resolution. - - The HTTP output filter can act as: -+ - - a simple HTTP server - - an HTTP server sink - - an HTTP server file sink - - an HTTP _client_ sink - - an HTTP server _source_ -+ - - The server currently handles GET, HEAD, PUT, POST, DELETE methods, and basic OPTIONS support. - Single or multiple byte ranges are supported for both GET and PUT/POST methods, in all server modes. -+ - - for GET, the resulting body is a single-part body formed by the concatenated byte ranges as requested (no overlap checking). - - for PUT/POST, the received data is pushed to the target file according to the byte ranges specified in the client request. -+ - - - __Warning: the partial PUT request is RFC2616 compliant but not compliant with RFC7230. PATCH method is not yet implemented in GPAC.__ -@@ -26,14 +30,17 @@ When multiple read directories are specified, the server root `/` contains the l - When a write directory is specified, the upload resource name identifies a file in this directory (the write directory name is not present in the URL). - - A directory rule file (cf `gpac -h creds`) can be specified in [rdirs](#rdirs) but NOT in [wdir](#wdir). When rules are used: -+ - - if a directory has a `name` rule, it will be used in the URL - - otherwise, the directory is directly available under server root `/` - - read and write access rights are checked -+ - Example - ``` - [foodir] - name=bar --``` -+``` -+ - Content `RES` of this directory is exposed as `http://SERVER/bar/RES`. - - Listing can be enabled on server using [dlist](#dlist). -@@ -53,18 +60,21 @@ Example - ``` - gpac httpout:rdirs=outcoming - ``` -+ - This sets up a read-only server. - - Example - ``` - gpac httpout:wdir=incoming --``` -+``` -+ - This sets up a write-only server. - - Example - ``` - gpac httpout:rdirs=outcoming:wdir=incoming:port=8080 --``` -+``` -+ - This sets up a read-write server running on [port](#port) 8080. - - -@@ -76,13 +86,15 @@ This mode is mostly useful to setup live HTTP streaming of media sessions such a - Example - ``` - gpac -i MP3_SOURCE -o http://localhost/live.mp3 --hold --``` -+``` -+ - In this example, the server waits for client requests on `/live.mp3` and will then push each input packet to all connected clients. - If the source is not real-time, you can inject a reframer filter performing realtime regulation. - Example - ``` - gpac -i MP3_SOURCE reframer:rt=on -o http://localhost/live.mp3 --``` -+``` -+ - In this example, the server will push each input packet to all connected clients, or trash the packet if no connected clients. - - In this mode, ICECast meta-data can be inserted using [ice](#ice). The default inserted values are `ice-audio-info`, `icy-br`, `icy-pub` (set to 1) and `icy-name` if input `ServiceName` property is set. -@@ -90,7 +102,8 @@ The server will also look for any property called `ice-*` on the input PID and i - Example - ``` - gpac -i source.mp3:#ice-Genre=CoolRock -o http://IP/live.mp3 --ice --``` -+``` -+ - This will inject the header `ice-Genre: CoolRock` in the response. - Once one complete input file is sent, it is no longer available for download unless [reopen](#reopen) is set and input PID is not over. - -@@ -102,26 +115,33 @@ This mode should not be used with multiple files muxers such as DASH or HLS. - In this mode, the filter will write input PIDs to files in the first read directory specified, acting as a file output sink. - The filter uses a read directory in this mode, which must be writable. - Upon client GET request, the server will check if the requested URL matches the name of a file currently being written by the server. -+ - - If so, the server will: -- - send the content using HTTP chunk transfer mode, starting with what is already written on disk -- - push remaining data to the client as soon as received while writing it to disk, until source file is done -+ -+ - send the content using HTTP chunk transfer mode, starting with what is already written on disk -+ - push remaining data to the client as soon as received while writing it to disk, until source file is done -+ - - If not so, the server will simply send the file from the disk as a regular HTTP session, without chunk transfer. -+ - - This mode is typically used for origin server in HAS sessions where clients may request files while they are being produced (low latency DASH). - Example - ``` - gpac -i SOURCE reframer:rt=on -o http://localhost:8080/live.mpd --rdirs=temp --dmode=dynamic --cdur=0.1 --``` -+``` -+ - In this example, a real-time dynamic DASH session with chunks of 100ms is created, writing files to `temp`. A client connecting to the live edge will receive segments as they are produced using HTTP chunk transfer. - - The server can store incoming files to memory mode by setting the read directory to `gmem`. - In this mode, [max_cache_segs](#max_cache_segs) is always at least 1. - - If [max_cache_segs](#max_cache_segs) value `N` is not 0, each incoming PID will store at most: -+ - - `MIN(N, time-shift depth)` files if stored in memory - - `-N` files if stored locally and `N` is negative - - `MAX(N, time-shift depth)` files if stored locally and `N` is positive - - unlimited otherwise (files stored locally, `N` is positive and no time-shift info) -+ - - - # HTTP client sink -@@ -132,7 +152,8 @@ The filter uses no read or write directories in this mode. - Example - ``` - gpac -i SOURCE -o http://targethost:8080/live.mpd:gpac:hmode=push --``` -+``` -+ - In this example, the filter will send PUT methods to the server running on [port](#port) 8080 at `targethost` location (IP address or name). - - -@@ -144,7 +165,8 @@ The filter uses no read or write directories in this mode, and uploaded data is - Example - ``` - gpac httpout:hmode=source vout aout --``` -+``` -+ - In this example, the filter will try to play uploaded files through video and audio output. - - -@@ -158,16 +180,21 @@ The server currently only operates in either HTTPS or HTTP mode and cannot run b - # Multiple destinations on single server - - When running in server mode, multiple HTTP outputs with same URL/port may be used: -+ - - the first loaded HTTP output filter with same URL/port will be reused - - all httpout options of subsequent httpout filters, except [dst](#dst) will be ignored, other options will be inherited as usual -+ - - Example - ``` - gpac -i dash.mpd dashin:forward=file:FID=D1 dashin:forward=segb:FID=D2 -o http://localhost:80/live.mpd:SID=D1:rdirs=dash -o http://localhost:80/live_rw.mpd:SID=D2:sigfrag - ``` -+ - This will: -+ - - load the HTTP server and forward (through `D1`) the dash session to this server using `live.mpd` as manifest name - - reuse the HTTP server and regenerate the manifest (through `D2` and `sigfrag` option), using `live_rw.mpd` as manifest name -+ - - - # Options -@@ -187,9 +214,10 @@ This will: - __cache_control__ (str): specify the `Cache-Control` string to add (`none` disable cache control and ETag) - __hold__ (bool, default: _false_): hold packets until one client connects - __hmode__ (enum, default: _default_): filter operation mode, ignored if [wdir](#wdir) is set --* default: run in server mode --* push: run in client mode using PUT or POST --* source: use server as source filter on incoming PUT/POST -+ -+- default: run in server mode -+- push: run in client mode using PUT or POST -+- source: use server as source filter on incoming PUT/POST - - __timeout__ (uint, default: _30_): timeout in seconds for persistent connections (0 disable timeout) - __ext__ (cstr): set extension for graph resolution, regardless of file extension -@@ -199,9 +227,10 @@ This will: - __dlist__ (bool, default: _false_): enable HTML listing for GET requests on directories - __sutc__ (bool, default: _false_): insert server UTC in response headers as `Server-UTC: VAL_IN_MS` - __cors__ (enum, default: _auto_): insert CORS header allowing all domains --* off: disable CORS --* on: enable CORS --* auto: enable CORS when `Origin` is found in request -+ -+- off: disable CORS -+- on: enable CORS -+- auto: enable CORS when `Origin` is found in request - - __reqlog__ (str): provide short log of the requests indicated in this option (comma separated list, `*` for all) regardless of HTTP log settings. Value `REC` logs file writing start/end. If prefix `-` is set, do not log request end - __ice__ (bool, default: _false_): insert ICE meta-data in response headers in sink mode -diff --git a/docs/Filters/imgdec.md b/docs/Filters/imgdec.md -index ff117c49..ef9c91fe 100644 ---- a/docs/Filters/imgdec.md -+++ b/docs/Filters/imgdec.md -@@ -1,6 +1,6 @@ - - --# PNG/JPG decoder -+# PNG/JPG decoder {:data-level="all"} - - Register name used to load filter: __imgdec__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/inspect.md b/docs/Filters/inspect.md -index 5bfc9691..5394ab87 100644 ---- a/docs/Filters/inspect.md -+++ b/docs/Filters/inspect.md -@@ -1,6 +1,6 @@ - - --# Inspect packets -+# Inspect packets {:data-level="all"} - - Register name used to load filter: __inspect__ - This filter is not checked during graph resolution and needs explicit loading. -@@ -16,55 +16,60 @@ _Note: specifying [xml](#xml), [analyze](#analyze), [fmt](#fmt) or using `-for-t - - The packet inspector can be configured to dump specific properties of packets using [fmt](#fmt). - When the option is not present, all properties are dumped. Otherwise, only properties identified by `$TOKEN$` are printed. You may use '$', '@' or '%' for `TOKEN` separator. `TOKEN` can be: --* pn: packet (frame in framed mode) number --* dts: decoding time stamp in stream timescale, N/A if not available --* ddts: difference between current and previous packets decoding time stamp in stream timescale, N/A if not available --* cts: composition time stamp in stream timescale, N/A if not available --* dcts: difference between current and previous packets composition time stamp in stream timescale, N/A if not available --* ctso: difference between composition time stamp and decoding time stamp in stream timescale, N/A if not available --* dur: duration in stream timescale --* frame: framing status -- * interface: complete AU, interface object (no size info). Typically a GL texture -- * frame_full: complete AU -- * frame_start: beginning of frame -- * frame_end: end of frame -- * frame_cont: frame continuation (not beginning, not end) --* sap or rap: SAP type of the frame --* ilace: interlacing flag (0: progressive, 1: top field, 2: bottom field) --* corr: corrupted packet flag --* seek: seek flag --* bo: byte offset in source, N/A if not available --* roll: roll info --* crypt: crypt flag --* vers: carousel version number --* size: size of packet --* csize: total size of packets received so far --* crc: 32 bit CRC of packet --* lf or n: insert new line --* t: insert tab --* data: hex dump of packet (_big output!_) or as string if legal UTF-8 --* lp: leading picture flag --* depo: depends on other packet flag --* depf: is depended on other packet flag --* red: redundant coding flag --* start: packet composition time as HH:MM:SS.ms --* startc: packet composition time as HH:MM:SS,ms --* end: packet end time as HH:MM:SS.ms --* endc: packet end time as HH:MM:SS,ms --* ck: clock type used for PCR discontinuities --* pcr: MPEG-2 TS last PCR, n/a if not available --* pcrd: difference between last PCR and decoding time, n/a if no PCR available --* pcrc: difference between last PCR and composition time, n/a if no PCR available --* P4CC: 4CC of packet property --* PropName: Name of packet property --* pid.P4CC: 4CC of PID property --* pid.PropName: Name of PID property --* fn: Filter name -+ -+- pn: packet (frame in framed mode) number -+- dts: decoding time stamp in stream timescale, N/A if not available -+- ddts: difference between current and previous packets decoding time stamp in stream timescale, N/A if not available -+- cts: composition time stamp in stream timescale, N/A if not available -+- dcts: difference between current and previous packets composition time stamp in stream timescale, N/A if not available -+- ctso: difference between composition time stamp and decoding time stamp in stream timescale, N/A if not available -+- dur: duration in stream timescale -+- frame: framing status -+ -+ - interface: complete AU, interface object (no size info). Typically a GL texture -+ - frame_full: complete AU -+ - frame_start: beginning of frame -+ - frame_end: end of frame -+ - frame_cont: frame continuation (not beginning, not end) -+ -+- sap or rap: SAP type of the frame -+- ilace: interlacing flag (0: progressive, 1: top field, 2: bottom field) -+- corr: corrupted packet flag -+- seek: seek flag -+- bo: byte offset in source, N/A if not available -+- roll: roll info -+- crypt: crypt flag -+- vers: carousel version number -+- size: size of packet -+- csize: total size of packets received so far -+- crc: 32 bit CRC of packet -+- lf or n: insert new line -+- t: insert tab -+- data: hex dump of packet (_big output!_) or as string if legal UTF-8 -+- lp: leading picture flag -+- depo: depends on other packet flag -+- depf: is depended on other packet flag -+- red: redundant coding flag -+- start: packet composition time as HH:MM:SS.ms -+- startc: packet composition time as HH:MM:SS,ms -+- end: packet end time as HH:MM:SS.ms -+- endc: packet end time as HH:MM:SS,ms -+- ck: clock type used for PCR discontinuities -+- pcr: MPEG-2 TS last PCR, n/a if not available -+- pcrd: difference between last PCR and decoding time, n/a if no PCR available -+- pcrc: difference between last PCR and composition time, n/a if no PCR available -+- P4CC: 4CC of packet property -+- PropName: Name of packet property -+- pid.P4CC: 4CC of PID property -+- pid.PropName: Name of PID property -+- fn: Filter name -+ - - Example - ``` - fmt="PID $pid.ID$ packet $pn$ DTS $dts$ CTS $cts$ $lf$" --``` -+``` -+ - This dumps packet number, cts and dts as follows: `PID 1 packet 10 DTS 100 CTS 108 \n` - - An unrecognized keyword or missing property will resolve to an empty string. -@@ -81,23 +86,27 @@ Example - ``` - gpac -i SRC reframer:rt=on inspect:buffer=10000:rbuffer=1000:mbuffer=30000:speed=2 - ``` -+ - This will play the session at 2x speed, using 30s of maximum buffering, consuming packets after 10s of media are ready and rebuffering if less than 1s of media. - - - # Options - --__log__ (str, default: _stdout_, Enum: _any|stderr|stdout|GLOG|null): set probe log filename --* _any: target file path and name --* stderr: dump to stderr --* stdout: dump to stdout --* GLOG: use GPAC logs `app@info` --* null: silent mode -+__log__ (str, default: _stdout_, Enum: _any|stderr|stdout|GLOG|TL|null): set probe log filename -+ -+- _any: target file path and name -+- stderr: dump to stderr -+- stdout: dump to stdout -+- GLOG: use GPAC logs `app@info` -+- TL: use GPAC log tool `TL` at level `info` -+- null: silent mode - - __mode__ (enum, default: _pck_): dump mode --* pck: dump full packet --* blk: dump packets before reconstruction --* frame: force reframer --* raw: dump source packets without demultiplexing -+ -+- pck: dump full packet -+- blk: dump packets before reconstruction -+- frame: force reframer -+- raw: dump source packets without demultiplexing - - __interleave__ (bool, default: _true_): dump packets as they are received on each PID. If false, logs are reported for each PID at end of session - __deep__ (bool, default: _false_, updatable): dump packets along with PID state change, implied when [fmt](#fmt) is set -@@ -113,10 +122,11 @@ This will play the session at 2x speed, using 30s of maximum buffering, consumin - __start__ (dbl, default: _0.0_): set playback start offset. A negative value means percent of media duration with -1 equal to duration - __dur__ (frac, default: _0/0_): set inspect duration - __analyze__ (enum, default: _off_, updatable): analyze sample content (NALU, OBU), similar to `-bsdbg` option of reframer filters --* off: no analyzing --* on: simple analyzing --* bs: log bitstream syntax (all elements read from bitstream) --* full: log bitstream syntax and bit sizes signaled as `(N)` after field value, except 1-bit fields (omitted) -+ -+- off: no analyzing -+- on: simple analyzing -+- bs: log bitstream syntax (all elements read from bitstream) -+- full: log bitstream syntax and bit sizes signaled as `(N)` after field value, except 1-bit fields (omitted) - - __xml__ (bool, default: _false_, updatable): use xml formatting (implied if (-analyze]() is set) and disable [fmt](#fmt) - __crc__ (bool, default: _false_, updatable): dump crc of samples of subsamples (NALU or OBU) when analyzing -@@ -125,14 +135,16 @@ This will play the session at 2x speed, using 30s of maximum buffering, consumin - __buffer__ (uint, default: _0_): set playback buffer in ms - __mbuffer__ (uint, default: _0_): set max buffer occupancy in ms. If less than buffer, use buffer - __rbuffer__ (uint, default: _0_, updatable): rebuffer trigger in ms. If 0 or more than buffer, disable rebuffering -+__stats__ (bool, default: _false_): compute statistics for PIDs - __test__ (enum, default: _no_, updatable): skip predefined set of properties, used for test mode --* no: no properties skipped --* noprop: all properties/info changes on PID are skipped, only packets are dumped --* network: URL/path dump, cache state, file size properties skipped (used for hashing network results) --* netx: same as network but skip track duration and templates (used for hashing progressive load of fmp4) --* encode: same as network plus skip decoder config (used for hashing encoding results) --* encx: same as encode and skip bitrates, media data size and co --* nocrc: disable packet CRC dump --* nobr: skip bitrate -+ -+- no: no properties skipped -+- noprop: all properties/info changes on PID are skipped, only packets are dumped -+- network: URL/path dump, cache state, file size properties skipped (used for hashing network results) -+- netx: same as network but skip track duration and templates (used for hashing progressive load of fmp4) -+- encode: same as network plus skip decoder config (used for hashing encoding results) -+- encx: same as encode and skip bitrates, media data size and co -+- nocrc: disable packet CRC dump -+- nobr: skip bitrate - - -diff --git a/docs/Filters/j2kdec.md b/docs/Filters/j2kdec.md -index cfba259c..794d820e 100644 ---- a/docs/Filters/j2kdec.md -+++ b/docs/Filters/j2kdec.md -@@ -1,6 +1,6 @@ - - --# OpenJPEG2000 decoder -+# OpenJPEG2000 decoder {:data-level="all"} - - Register name used to load filter: __j2kdec__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/jpgenc.md b/docs/Filters/jpgenc.md -index fd87c2d6..2143f58b 100644 ---- a/docs/Filters/jpgenc.md -+++ b/docs/Filters/jpgenc.md -@@ -1,6 +1,6 @@ - - --# JPG encoder -+# JPG encoder {:data-level="all"} - - Register name used to load filter: __jpgenc__ - This filter may be automatically loaded during graph resolution. -@@ -11,9 +11,10 @@ This filter encodes a single uncompressed video PID to JPEG using libjpeg. - # Options - - __dctmode__ (enum, default: _fast_): type of DCT used --* slow: precise but slow integer DCT --* fast: less precise but faster integer DCT --* float: float DCT -+ -+- slow: precise but slow integer DCT -+- fast: less precise but faster integer DCT -+- float: float DCT - - __quality__ (uint, default: _100_, minmax: 0-100, updatable): compression quality - -diff --git a/docs/Filters/jsf.md b/docs/Filters/jsf.md -index 3802832f..7ce75d63 100644 ---- a/docs/Filters/jsf.md -+++ b/docs/Filters/jsf.md -@@ -1,6 +1,6 @@ - - --# JavaScript filter -+# JavaScript filter {:data-level="all"} - - Register name used to load filter: __jsf__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/lsrdec.md b/docs/Filters/lsrdec.md -index b1003020..fd57a89f 100644 ---- a/docs/Filters/lsrdec.md -+++ b/docs/Filters/lsrdec.md -@@ -1,6 +1,6 @@ - - --# MPEG-4 LASeR decoder -+# MPEG-4 LASeR decoder {:data-level="all"} - - Register name used to load filter: __lsrdec__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/m2psdmx.md b/docs/Filters/m2psdmx.md -index 5df6830e..9b64b735 100644 ---- a/docs/Filters/m2psdmx.md -+++ b/docs/Filters/m2psdmx.md -@@ -1,6 +1,6 @@ - - --# MPEG PS demultiplexer -+# MPEG PS demultiplexer {:data-level="all"} - - Register name used to load filter: __m2psdmx__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/m2tsdmx.md b/docs/Filters/m2tsdmx.md -index 21cffbf5..f9d91a6e 100644 ---- a/docs/Filters/m2tsdmx.md -+++ b/docs/Filters/m2tsdmx.md -@@ -1,6 +1,6 @@ - - --# MPEG-2 TS demultiplexer -+# MPEG-2 TS demultiplexer {:data-level="all"} - - Register name used to load filter: __m2tsdmx__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/m2tsmx.md b/docs/Filters/m2tsmx.md -index d21080fb..f06de81b 100644 ---- a/docs/Filters/m2tsmx.md -+++ b/docs/Filters/m2tsmx.md -@@ -1,6 +1,6 @@ - - --# MPEG-2 TS multiplexer -+# MPEG-2 TS multiplexer {:data-level="all"} - - Register name used to load filter: __m2tsmx__ - This filter may be automatically loaded during graph resolution. -@@ -12,11 +12,13 @@ This filter multiplexes one or more input PIDs into a MPEG-2 Transport Stream mu - The MPEG-2 TS multiplexer assigns M2TS PID for media streams using the PID of the PMT plus the stream index. - For example, the default config creates the first program with a PMT PID 100, the first stream will have a PID of 101. - Streams are grouped in programs based on input PID property ServiceID if present. If absent, stream will go in the program with service ID as indicated by [sid](#sid) option. -+ - - [name](#name) option is overridden by input PID property `ServiceName`. - - [provider](#provider) option is overridden by input PID property `ServiceProvider`. - - [pcr_offset](#pcr_offset) option is overridden by input PID property `"tsmux:pcr_offset"` - - [first_pts](#first_pts) option is overridden by input PID property `"tsmux:force_pts"` - - [temi](#temi) option is overridden by input PID property `"tsmux:temi"` -+ - - # Time and External Media Information (TEMI) - -@@ -24,47 +26,58 @@ The [temi](#temi) option allows specifying a list of URLs or timeline IDs to ins - One or more TEMI timeline can be specified per PID. - The syntax is a comma-separated list of one or more TEMI description. - Each TEMI description is formatted as ID_OR_URL or #OPT1[#OPT2]#ID_OR_URL. Options are: --* S`N`: indicate the target service with ID `N` --* T`N`: set timescale to use (default: PID timescale) --* D`N`: set delay in ms between two TEMI url descriptors (default 1000) --* O`N`: set offset (max 64 bits) to add to TEMI timecodes (default 0). If timescale is not specified, offset value is in ms, otherwise in timescale units. --* I`N`: set initial value (max 64 bits) of TEMI timecodes. If not set, initial value will match first packet CTS. If timescale is not specified, value is in PID timescale units, otherwise in specified timescale units. --* P`N`: indicate target PID in program. Possible values are -- * `V`: only insert for video streams. -- * `A`: only insert for audio streams. -- * `T`: only insert for text streams. -- * N: only insert for stream with index `N` (0-based) in the program. --* L`C`: set 64bit timecode signaling. Possible values for `C` are: -- * `A`: automatic switch between 32 and 64 bit depending on timecode value (default if not specified). -- * `Y`: use 64 bit signaling only. -- * `N`: use 32 bit signaling only and wrap around timecode value. --* N: insert NTP timestamp in TEMI timeline descriptor --* ID_OR_URL: If number, indicate the TEMI ID to use for external timeline. Otherwise, give the URL to insert -+ -+- S`N`: indicate the target service with ID `N` -+- T`N`: set timescale to use (default: PID timescale) -+- D`N`: set delay in ms between two TEMI url descriptors (default 1000) -+- O`N`: set offset (max 64 bits) to add to TEMI timecodes (default 0). If timescale is not specified, offset value is in ms, otherwise in timescale units. -+- I`N`: set initial value (max 64 bits) of TEMI timecodes. If not set, initial value will match first packet CTS. If timescale is not specified, value is in PID timescale units, otherwise in specified timescale units. -+- P`N`: indicate target PID in program. Possible values are -+ -+ - `V`: only insert for video streams. -+ - `A`: only insert for audio streams. -+ - `T`: only insert for text streams. -+ - N: only insert for stream with index `N` (0-based) in the program. -+ -+- L`C`: set 64bit timecode signaling. Possible values for `C` are: -+ -+ - `A`: automatic switch between 32 and 64 bit depending on timecode value (default if not specified). -+ - `Y`: use 64 bit signaling only. -+ - `N`: use 32 bit signaling only and wrap around timecode value. -+ -+- N: insert NTP timestamp in TEMI timeline descriptor -+- ID_OR_URL: If number, indicate the TEMI ID to use for external timeline. Otherwise, give the URL to insert -+ - - Example - ``` - temi="url" --``` -+``` -+ - Inserts a TEMI URL+timecode in the each stream of each program. - Example - ``` - temi="#P0#url,#P1#4" - ``` -+ - Inserts a TEMI URL+timecode in the first stream of all programs and an external TEMI with ID 4 in the second stream of all programs. - Example - ``` - temi="#P0#2,#P0#url,#P1#4" --``` -+``` -+ - Inserts a TEMI with ID 2 and a TEMI URL+timecode in the first stream of all programs, and an external TEMI with ID 4 in the second stream of all programs. - Example - ``` - temi="#S20#4,#S10#URL" --``` -+``` -+ - Inserts an external TEMI with ID 4 in the each stream of program with ServiceID 20 and a TEMI URL in each stream of program with ServiceID 10. - Example - ``` - temi="#N#D500#PV#T30000#4" --``` -+``` -+ - Inserts an external TEMI with ID 4 and timescale 30000, NTP injection and carousel of 500 ms in the video stream of all programs. - - __Warning: multipliers (k,m,g) are not supported in TEMI options.__ -@@ -72,9 +85,11 @@ __Warning: multipliers (k,m,g) are not supported in TEMI options.__ - # Adaptive Streaming - - In DASH and HLS mode: -+ - - the PCR is always initialized at 0, and [flush_rap](#flush_rap) is automatically set. - - unless `nb_pack` is specified, 200 TS packets will be used as pack output in DASH mode. - - `pes_pack=none` is forced since some demultiplexers have issues with non-aligned ADTS PES. -+ - - The filter watches the property `FileNumber` on incoming packets to create new files, or new segments in DASH mode. - The filter will look for property `M2TSRA` set on the input stream. -@@ -95,9 +110,10 @@ In LATM mux mode, the decoder configuration is inserted at the given [repeat_rat - __first_pts__ (luint, default: _0_): force PTS value of first packet, in 90kHz - __pcr_offset__ (luint, default: _-1_): offset all timestamps from PCR by V, in 90kHz (default value is computed based on input media) - __mpeg4__ (enum, default: _none_): force usage of MPEG-4 signaling (IOD and SL Config) --* none: disables 4on2 --* full: sends AUs as SL packets over section for OD, section/pes for scene (cf bifs_pes) --* scene: sends only scene streams as 4on2 but uses regular PES without SL for audio and video -+ -+- none: disables 4on2 -+- full: sends AUs as SL packets over section for OD, section/pes for scene (cf bifs_pes) -+- scene: sends only scene streams as 4on2 but uses regular PES without SL for audio and video - - __pmt_version__ (uint, default: _200_): set version number of the PMT - __disc__ (bool, default: _false_): set the discontinuity marker for the first packet of each stream -@@ -106,15 +122,17 @@ In LATM mux mode, the decoder configuration is inserted at the given [repeat_rat - __max_pcr__ (uint, default: _100_): set max interval in ms between 2 PCR - __nb_pack__ (uint, default: _4_): pack N TS packets in output packets - __pes_pack__ (enum, default: _audio_): set AU to PES packing mode --* audio: will pack only multiple audio AUs in a PES --* none: make exactly one AU per PES --* all: will pack multiple AUs per PES for all streams -+ -+- audio: will pack only multiple audio AUs in a PES -+- none: make exactly one AU per PES -+- all: will pack multiple AUs per PES for all streams - - __realtime__ (bool, default: _false_): use real-time output - __bifs_pes__ (enum, default: _off_): select BIFS streams packetization (PES vs sections) --* on: uses BIFS PES --* off: uses BIFS sections --* copy: uses BIFS PES but removes timestamps in BIFS SL and only carries PES timestamps -+ -+- on: uses BIFS PES -+- off: uses BIFS sections -+- copy: uses BIFS PES but removes timestamps in BIFS SL and only carries PES timestamps - - __flush_rap__ (bool, default: _false_): force flushing mux program when RAP is found on video, and injects PAT and PMT before the next video PES begin - __pcr_only__ (bool, default: _false_): enable PCR-only TS packets -diff --git a/docs/Filters/maddec.md b/docs/Filters/maddec.md -index 65da8b7b..03a656c2 100644 ---- a/docs/Filters/maddec.md -+++ b/docs/Filters/maddec.md -@@ -1,6 +1,6 @@ - - --# MAD decoder -+# MAD decoder {:data-level="all"} - - Register name used to load filter: __maddec__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/mcdec.md b/docs/Filters/mcdec.md -index bfd409e5..8ed51dd0 100644 ---- a/docs/Filters/mcdec.md -+++ b/docs/Filters/mcdec.md -@@ -1,6 +1,6 @@ - - --# MediaCodec decoder -+# MediaCodec decoder {:data-level="all"} - - Register name used to load filter: __mcdec__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/mp4dmx.md b/docs/Filters/mp4dmx.md -index 94e5da06..667c71e9 100644 ---- a/docs/Filters/mp4dmx.md -+++ b/docs/Filters/mp4dmx.md -@@ -1,6 +1,6 @@ - - --# ISOBMFF/QT demultiplexer -+# ISOBMFF/QT demultiplexer {:data-level="all"} - - Register name used to load filter: __mp4dmx__ - This filter may be automatically loaded during graph resolution. -@@ -11,24 +11,32 @@ Input ISOBMFF/QT can be regular or fragmented, and available as files or as raw - # Track Selection - - The filter can use fragment identifiers of source to select a single track for playback. The allowed fragments are: -- * #audio: only use the first audio track -- * #video: only use the first video track -- * #auxv: only use the first auxiliary video track -- * #pict: only use the first picture track -- * #text: only use the first text track -- * #trackID=VAL: only use the track with given ID -- * #itemID=VAL: only use the item with given ID -- * #ID=VAL: only use the track/item with given ID -- * #VAL: only use the track/item with given ID -+ -+ - #audio: only use the first audio track -+ - #video: only use the first video track -+ - #auxv: only use the first auxiliary video track -+ - #pict: only use the first picture track -+ - #text: only use the first text track -+ - #trackID=VAL: only use the track with given ID -+ - #itemID=VAL: only use the item with given ID -+ - #ID=VAL: only use the track/item with given ID -+ - #VAL: only use the track/item with given ID -+ - - # Scalable Tracks - - When scalable tracks are present in a file, the reader can operate in 3 modes using [smode](#smode) option: --* smode=single: resolves all extractors to extract a single bitstream from a scalable set. The highest level is used -+ -+- smode=single: resolves all extractors to extract a single bitstream from a scalable set. The highest level is used -+ - In this mode, there is no enhancement decoder config, only a base one resulting from the merge of the layers configurations --* smode=split: all extractors are removed and every track of the scalable set is declared. In this mode, each enhancement track has no base decoder config -+ -+- smode=split: all extractors are removed and every track of the scalable set is declared. In this mode, each enhancement track has no base decoder config -+ - and an enhancement decoder config. --* smode=splitx: extractors are kept in the bitstream, and every track of the scalable set is declared. In this mode, each enhancement track has a base decoder config -+ -+- smode=splitx: extractors are kept in the bitstream, and every track of the scalable set is declared. In this mode, each enhancement track has a base decoder config -+ - (copied from base) and an enhancement decoder config. This is mostly used for DASHing content. - - __Warning: smode=splitx will result in extractor NAL units still present in the output bitstream, which shall only be true if the output is ISOBMFF based__ -@@ -39,26 +47,29 @@ __Warning: smode=splitx will result in extractor NAL units still present in the - __src__ (cstr): local file name of source content (only used when explicitly loading the filter) - __allt__ (bool, default: _false_): load all tracks even if unknown media type - __edits__ (enum, default: _auto_): do not use edit lists --* auto: track delay and no edit list when possible --* no: ignore edit list --* strict: use edit list even if only signaling a delay -+ -+- auto: track delay and no edit list when possible -+- no: ignore edit list -+- strict: use edit list even if only signaling a delay - - __itt__ (bool, default: _false_): convert all items of root meta into a single PID - __itemid__ (bool, default: _true_): keep item IDs in PID properties - __smode__ (enum, default: _split_): load mode for scalable/tile tracks --* split: each track is declared, extractors are removed --* splitx: each track is declared, extractors are kept --* single: a single track is declared (highest level for scalable, tile base for tiling) -+ -+- split: each track is declared, extractors are removed -+- splitx: each track is declared, extractors are kept -+- single: a single track is declared (highest level for scalable, tile base for tiling) - - __alltk__ (bool, default: _false_): declare disabled tracks - __frame_size__ (uint, default: _1024_): frame size for raw audio samples (dispatches frame_size samples per packet) - __expart__ (bool, default: _false_): expose cover art as a dedicated video PID - __sigfrag__ (bool, default: _false_): signal fragment and segment boundaries of source on output packets, fails if source is not fragmented - __tkid__ (str): declare only track based on given param --* integer value: declares track with the given ID --* audio: declares first audio track --* video: declares first video track --* 4CC: declares first track with matching 4CC for handler type -+ -+- integer value: declares track with the given ID -+- audio: declares first audio track -+- video: declares first video track -+- 4CC: declares first track with matching 4CC for handler type - - __stsd__ (uint, default: _0_): only extract sample mapped to the given sample description index (0 means extract all) - __nocrypt__ (bool): signal encrypted tracks as non encrypted (mostly used for export) -@@ -67,19 +78,24 @@ __Warning: smode=splitx will result in extractor NAL units still present in the - __mstore_samples__ (uint, default: _50_): minimum number of samples to be present before purging sample tables when reading from memory stream (pipe etc...), 0 means purge as soon as possible - __strtxt__ (bool, default: _false_): load text tracks (apple/tx3g) as MPEG-4 streaming text tracks - __xps_check__ (enum, default: _auto_): parameter sets extraction mode from AVC/HEVC/VVC samples --* keep: do not inspect sample (assumes input file is compliant when generating DASH/HLS/CMAF) --* rem: removes all inband xPS and notify configuration changes accordingly --* auto: resolves to `keep` for `smode=splitx` (dasher mode), `rem` otherwise -+ -+- keep: do not inspect sample (assumes input file is compliant when generating DASH/HLS/CMAF) -+- rem: removes all inband xPS and notify configuration changes accordingly -+- auto: resolves to `keep` for `smode=splitx` (dasher mode), `rem` otherwise - - __nodata__ (enum, default: _no_): control sample data loading --* no: regular load --* yes: skip data loading --* fake: allocate sample but no data copy -+ -+- no: regular load -+- yes: skip data loading -+- fake: allocate sample but no data copy - - __lightp__ (bool, default: _false_): load minimal set of properties - __initseg__ (str): local init segment name when input is a single ISOBMFF segment - __ctso__ (sint): value to add to CTS offset for tracks using negative ctts -+ - - set to `-1` to use the `cslg` box info or the minimum cts offset present in the track - - set to `-2` to use the minimum cts offset present in the track (`cslg` ignored) - -+__norw__ (bool, default: _false_): skip reformating of samples - should only be used when rewriting fragments -+__keepc__ (bool, default: _false_): keep corrupted samples - should only be used in multicast modes - -diff --git a/docs/Filters/mp4mx.md b/docs/Filters/mp4mx.md -index 3882a40f..fa25ec0e 100644 ---- a/docs/Filters/mp4mx.md -+++ b/docs/Filters/mp4mx.md -@@ -1,6 +1,6 @@ - - --# ISOBMFF/QT multiplexer -+# ISOBMFF/QT multiplexer {:data-level="all"} - - Register name used to load filter: __mp4mx__ - This filter may be automatically loaded during graph resolution. -@@ -29,9 +29,11 @@ gpac -i source.jpg:#ItemID=1 -o file.mp4 - The [store](#store) option allows controlling if the file is fragmented or not, and when not fragmented, how interleaving is done. For cases where disk requirements are tight and fragmentation cannot be used, it is recommended to use either `flat` or `fstart` modes. - - The [vodcache](#vodcache) option allows controlling how DASH onDemand segments are generated: -+ - - If set to `on`, file data is stored to a temporary file on disk and flushed upon completion, no padding is present. - - If set to `insert`, SIDX/SSIX will be injected upon completion of the file by shifting bytes in file. In this case, no padding is required but this might not be compatible with all output sinks and will take longer to write the file. - - If set to `replace`, SIDX/SSIX size will be estimated based on duration and DASH segment length, and padding will be used in the file _before_ the final SIDX. If input PIDs have the properties `DSegs` set, this will used be as the number of segments. -+ - The `on` and `insert` modes will produce exactly the same file, while the mode `replace` may inject a `free` box before the sidx. - - -@@ -43,7 +45,8 @@ Per PID box patch can be specified through the PID property `boxpatch`. - Example - ``` - gpac -i source:#boxpatch=myfile.xml -o mux.mp4 --``` -+``` -+ - Per Item box patch can be specified through the PID property `boxpatch`. - Example - ``` -@@ -60,19 +63,23 @@ When tagging is enabled, the filter will watch the property `CoverArt` and all c - The built-in tag names are indicated by `MP4Box -h tags`. - QT tags can be specified using `qtt_NAME` property names, and will be added using formatting specified in `MP4Box -h tags`. - Other tag class may be specified using `tag_NAME` property names, and will be added if [tags](#tags) is set to `all` using: -+ - - `NAME` as a box 4CC if `NAME` is four characters long - - `NAME` as a box 4CC if `NAME` is 3 characters long, and will be prefixed by 0xA9 - - the CRC32 of the `NAME` as a box 4CC if `NAME` is not four characters long -+ - - - # User data - - The filter will look for the following PID properties to create user data entries: --* `udtab`: set the track user-data box to the property value which _must_ be a serialized box array blob --* `mudtab`: set the movie user-data box to the property value which _must_ be a serialized box array blob --* `udta_U4CC`: set track user-data box entry of type `U4CC` to property value --* `mudta_U4CC`: set movie user-data box entry of type `U4CC` to property value --* `tkgp_T4CC`: set/remove membership to track group with type `T4CC` and ID given by property value. A negative value N removes from track group with ID -N -+ -+- `udtab`: set the track user-data box to the property value which _must_ be a serialized box array blob -+- `mudtab`: set the movie user-data box to the property value which _must_ be a serialized box array blob -+- `udta_U4CC`: set track user-data box entry of type `U4CC` to property value -+- `mudta_U4CC`: set movie user-data box entry of type `U4CC` to property value -+- `tkgp_T4CC`: set/remove membership to track group with type `T4CC` and ID given by property value. A negative value N removes from track group with ID -N -+ - - Example - ``` -@@ -84,14 +91,18 @@ gpac -i src.mp4:#mudtab=data@box.bin -o tag.mp4 - # Custom sample group descriptions and sample auxiliary info - - The filter watches the following custom data properties on incoming packets: --* `grp_A4CC`: maps packet to sample group description of type `A4CC` and entry set to property payload --* `grp_A4CC_param`: same as above and sets sample to group `grouping_type_parameter` to `param` --* `sai_A4CC`: adds property payload as sample auxiliary information of type `A4CC` --* `sai_A4CC_param`: same as above and sets `aux_info_type_parameter`to `param` -+ -+- `grp_A4CC`: maps packet to sample group description of type `A4CC` and entry set to property payload -+- `grp_A4CC_param`: same as above and sets sample to group `grouping_type_parameter` to `param` -+- `sai_A4CC`: adds property payload as sample auxiliary information of type `A4CC` -+- `sai_A4CC_param`: same as above and sets `aux_info_type_parameter`to `param` -+ - - The property `grp_EMSG` consists in one or more `EventMessageBox` as defined in MPEG-DASH. -+ - - in fragmented mode, presence of this property in a packet will start a new fragment, with the boxes written before the `moof` - - in regular mode, an internal sample group of type `EMSG` is currently used for `emsg` box storage -+ - - - # Notes -@@ -103,25 +114,30 @@ Example - ``` - -i unkn.mkv:#ISOMSubtype=VIUK:#DSIWrap=cfgv -o t.mp4 - ``` -+ - This will wrap the unknown stream using `VIUK` code point in `stsd` and wrap any decoder configuration data in a `cfgv` box. - - If [pad_sparse](#pad_sparse) is set, the filter watches the property `Sparse` on incoming PID to decide whether empty packets should be injected to keep packet duration info. - Such packets are only injected when a whole in the timeline is detected. -+ - - if `Sparse` is absent, empty packet is inserted for unknown text and metadata streams - - if `Sparse` is true, empty packet is inserted for all stream types - - if `Sparse` is false, empty packet is never injected -+ - - The default media type used for a PID can be overriden using property `StreamSubtype`. - Example - ``` - -i src.srt:#StreamSubtype=sbtl [-i ...] -o test.mp4 - ``` -+ - This will force the text stream to use `sbtl` handler type instead of default `text` one. - Subtitle streams may be used as chapters by setting the property `IsChap` on the desired PID. - Example - ``` - -i src.srt:#IsChap [-i ...] -o test.mp4 - ``` -+ - This will force the text stream to be used as a QT chapter track. - - -@@ -130,44 +146,49 @@ This will force the text stream to be used as a QT chapter track. - __m4sys__ (bool, default: _false_): force MPEG-4 Systems signaling of tracks - __dref__ (bool, default: _false_): only reference data from source file - not compatible with all media sources - __ctmode__ (enum, default: _auto_): set composition offset mode for video tracks --* auto: if fragmenting an ISOBMFF source, use source settings otherwise resolve to `edit` --* edit: uses edit lists to shift first frame to presentation time 0 --* noedit: ignore edit lists and does not shift timeline --* negctts: uses ctts v1 with possibly negative offsets and no edit lists -+ -+- auto: if fragmenting an ISOBMFF source, use source settings otherwise resolve to `edit` -+- edit: uses edit lists to shift first frame to presentation time 0 -+- noedit: ignore edit lists and does not shift timeline -+- negctts: uses ctts v1 with possibly negative offsets and no edit lists - - __dur__ (frac, default: _0_): only import the specified duration. If negative, specify the number of coded frames to import - __pack3gp__ (uint, default: _1_): pack a given number of 3GPP audio frames in one sample - __importer__ (bool, default: _false_): compatibility with old importer, displays import progress - __pack_nal__ (bool, default: _false_): repack NALU size length to minimum possible size for NALU-based video (AVC/HEVC/...) - __xps_inband__ (enum, default: _no_): use inband (in sample data) parameter set for NALU-based video (AVC/HEVC/...) --* no: parameter sets are not inband, several sample descriptions might be created --* pps: picture parameter sets are inband, all other parameter sets are in sample description --* all: parameter sets are inband, no parameter sets in sample description --* both: parameter sets are inband, signaled as inband, and also first set is kept in sample description --* mix: creates non-standard files using single sample entry with first PSs found, and moves other PS inband --* auto: keep source config, or defaults to no if source is not ISOBMFF -+ -+- no: parameter sets are not inband, several sample descriptions might be created -+- pps: picture parameter sets are inband, all other parameter sets are in sample description -+- all: parameter sets are inband, no parameter sets in sample description -+- both: parameter sets are inband, signaled as inband, and also first set is kept in sample description -+- mix: creates non-standard files using single sample entry with first PSs found, and moves other PS inband -+- auto: keep source config, or defaults to no if source is not ISOBMFF - - __store__ (enum, default: _inter_): file storage mode --* inter: perform precise interleave of the file using [cdur](#cdur) (requires temporary storage of all media) --* flat: write samples as they arrive and `moov` at end (fastest mode) --* fstart: write samples as they arrive and `moov` before `mdat` --* tight: uses per-sample interleaving of all tracks (requires temporary storage of all media) --* frag: fragments the file using cdur duration --* sfrag: fragments the file using cdur duration but adjusting to start with SAP1/3 -+ -+- inter: perform precise interleave of the file using [cdur](#cdur) (requires temporary storage of all media) -+- flat: write samples as they arrive and `moov` at end (fastest mode) -+- fstart: write samples as they arrive and `moov` before `mdat` -+- tight: uses per-sample interleaving of all tracks (requires temporary storage of all media) -+- frag: fragments the file using cdur duration -+- sfrag: fragments the file using cdur duration but adjusting to start with SAP1/3 - - __cdur__ (frac, default: _-1/1_): chunk duration for flat and interleaving modes or fragment duration for fragmentation modes --* 0: no specific interleaving but moov first --* negative: defaults to 1.0 unless overridden by storage profile -+ -+- 0: no specific interleaving but moov first -+- negative: defaults to 1.0 unless overridden by storage profile - - __moovts__ (sint, default: _600_): timescale to use for movie. A negative value picks the media timescale of the first track added - __moof_first__ (bool, default: _true_): generate fragments starting with moof then mdat - __abs_offset__ (bool, default: _false_): use absolute file offset in fragments rather than offsets from moof - __fsap__ (bool, default: _true_): split truns in video fragments at SAPs to reduce file size - __subs_sidx__ (sint, default: _-1_): number of subsegments per sidx --* 0: single sidx --* >0: hierarchical or daisy-chained sidx --* <0: disables sidx --* -2: removes sidx if present in source PID -+ -+- 0: single sidx -+- >0: hierarchical or daisy-chained sidx -+- <0: disables sidx -+- -2: removes sidx if present in source PID - - __m4cc__ (str): 4 character code of empty box to append at the end of a segment (DASH mode) or of a fragment (non-DASH mode) - __chain_sidx__ (bool, default: _false_): use daisy-chaining of SIDX -@@ -178,34 +199,40 @@ This will force the text stream to be used as a QT chapter track. - __nofragdef__ (bool, default: _false_): disable default flags in fragments - __straf__ (bool, default: _false_): use a single traf per moof (smooth streaming and co) - __strun__ (bool, default: _false_): use a single trun per traf (smooth streaming and co) -+__prft__ (bool, default: _true_): set `prft` box at segment start, disabled if not fragmented mode - __psshs__ (enum, default: _moov_): set `pssh` boxes store mode --* moof: in first moof of each segments --* moov: in movie box --* both: in movie box and in first moof of each segment --* none: pssh is discarded -+ -+- moof: in first moof of each segments -+- moov: in movie box -+- both: in movie box and in first moof of each segment -+- none: pssh is discarded - - __sgpd_traf__ (bool, default: _false_): store sample group descriptions in traf (duplicated for each traf). If not used, sample group descriptions are stored in the movie box - __vodcache__ (enum, default: _replace_): enable temp storage for VoD dash modes --* on: use temp storage of complete file for sidx and ssix injection --* insert: insert sidx and ssix by shifting bytes in output file --* replace: precompute pace requirements for sidx and ssix and rewrite file range at end -+ -+- on: use temp storage of complete file for sidx and ssix injection -+- insert: insert sidx and ssix by shifting bytes in output file -+- replace: precompute pace requirements for sidx and ssix and rewrite file range at end - - __noinit__ (bool, default: _false_): do not produce initial `moov, used for DASH bitstream switching mode` - __tktpl__ (enum, default: _yes_): use track box from input if any as a template to create new track --* no: disables template --* yes: clones the track (except edits and decoder config) --* udta: only loads udta -+ -+- no: disables template -+- yes: clones the track (except edits and decoder config) -+- udta: only loads udta - - __mudta__ (enum, default: _yes_): use `udta` and other `moov` extension boxes from input if any --* no: disables import --* yes: clones all extension boxes --* udta: only loads udta -+ -+- no: disables import -+- yes: clones all extension boxes -+- udta: only loads udta - - __mvex__ (bool, default: _false_): set `mvex` boxes after `trak` boxes - __sdtp_traf__ (enum, default: _no_): use `sdtp` box in `traf` box rather than using flags in trun sample entries --* no: do not use `sdtp` --* sdtp: use `sdtp` box to indicate sample dependencies and do not write info in `trun` sample flags --* both: use `sdtp` box to indicate sample dependencies and also write info in `trun` sample flags -+ -+- no: do not use `sdtp` -+- sdtp: use `sdtp` box to indicate sample dependencies and do not write info in `trun` sample flags -+- both: use `sdtp` box to indicate sample dependencies and also write info in `trun` sample flags - - __trackid__ (uint, default: _0_): track ID of created track for single track. Default 0 uses next available trackID - __fragdur__ (bool, default: _false_): fragment based on fragment duration rather than CTS. Mostly used for `MP4Box -frag` option -@@ -213,11 +240,12 @@ This will force the text stream to be used as a QT chapter track. - __styp__ (str): set segment `styp` major brand (and optionally version) to the given 4CC[.version] - __mediats__ (sint, default: _0_): set media timescale. A value of 0 means inherit from PID, a value of -1 means derive from samplerate or frame rate - __ase__ (enum, default: _v0_): set audio sample entry mode for more than stereo layouts --* v0: use v0 signaling but channel count from stream, recommended for backward compatibility --* v0s: use v0 signaling and force channel count to 2 (stereo) if more than 2 channels --* v1: use v1 signaling, ISOBMFF style (will mux raw PCM as ISOBMFF style) --* v1qt: use v1 signaling, QTFF style --* v2qt: use v2 signaling, QTFF style (lpcm entry type) -+ -+- v0: use v0 signaling but channel count from stream, recommended for backward compatibility -+- v0s: use v0 signaling and force channel count to 2 (stereo) if more than 2 channels -+- v1: use v1 signaling, ISOBMFF style (will mux raw PCM as ISOBMFF style) -+- v1qt: use v1 signaling, QTFF style -+- v2qt: use v2 signaling, QTFF style (lpcm entry type) - - __ssix__ (bool, default: _false_): create `ssix` box when `sidx` box is present, level 1 mapping I-frames byte ranges, level 0xFF mapping the rest - __ccst__ (bool, default: _false_): insert coding constraint box for video tracks -@@ -227,12 +255,13 @@ This will force the text stream to be used as a QT chapter track. - __saio32__ (bool, default: _false_): use 32 bit offset for side data location instead of 64 bit offset - __tfdt64__ (bool, default: _false_): use 64 bit tfdt and sidx even for 32 bits timestamps - __compress__ (enum, default: _no_): set top-level box compression mode --* no: disable box compression --* moov: compress only moov box (uses cmov for QT) --* moof: compress only moof boxes --* sidx: compress moof and sidx boxes --* ssix: compress moof, sidx and ssix boxes --* all: compress moov, moof, sidx and ssix boxes -+ -+- no: disable box compression -+- moov: compress only moov box (uses cmov for QT) -+- moof: compress only moof boxes -+- sidx: compress moof and sidx boxes -+- ssix: compress moof, sidx and ssix boxes -+- all: compress moov, moof, sidx and ssix boxes - - __fcomp__ (bool, default: _false_): force using compress box even when compressed size is larger than uncompressed - __otyp__ (bool, default: _false_): inject original file type when using compressed boxes -@@ -245,34 +274,38 @@ This will force the text stream to be used as a QT chapter track. - __forcesync__ (bool, default: _false_): force all SAP types to be considered sync samples (might produce non-compliant files) - __refrag__ (bool, default: _false_): use track fragment defaults from initial file if any rather than computing them from PID properties (used when processing standalone segments/fragments) - __itags__ (enum, default: _strict_): tag injection mode --* none: do not inject tags --* strict: only inject recognized itunes tags --* all: inject all possible tags -+ -+- none: do not inject tags -+- strict: only inject recognized itunes tags -+- all: inject all possible tags - - __keep_utc__ (bool, default: _false_): force all new files and tracks to keep the source UTC creation and modification times - __pps_inband__ (bool, default: _no_): when [xps_inband](#xps_inband) is set, inject PPS in each non SAP 1/2/3 sample - __moovpad__ (uint, default: _0_): insert `free` box of given size after `moov` for future in-place editing - __cmaf__ (enum, default: _no_): use CMAF guidelines (turns on `mvex`, `truns_first`, `strun`, `straf`, `tfdt_traf`, `chain_sidx` and restricts `subs_sidx` to -1 or 0) --* no: CMAF not enforced --* cmfc: use CMAF `cmfc` guidelines --* cmf2: use CMAF `cmf2` guidelines (turns on `nofragdef`) -+ -+- no: CMAF not enforced -+- cmfc: use CMAF `cmfc` guidelines -+- cmf2: use CMAF `cmf2` guidelines (turns on `nofragdef`) - - __pad_sparse__ (bool, default: _true_): inject sample with no data (size 0) to keep durations in unknown sparse text and metadata tracks - __force_dv__ (bool, default: _false_): force DV sample entry types even when AVC/HEVC compatibility is signaled - __dvsingle__ (bool, default: _false_): ignore DolbyVision profile 8 in xps inband mode if profile 5 is already set - __tsalign__ (bool, default: _true_): enable timeline realignment to 0 for first sample - if false, this will keep original timing with empty edit (possibly long) at begin) - __chapm__ (enum, default: _both_): chapter storage mode --* off: disable chapters --* tk: use chapter track (QT-style) --* udta: use user-data box chapters --* both: use both chapter tracks and udta -+ -+- off: disable chapters -+- tk: use chapter track (QT-style) -+- udta: use user-data box chapters -+- both: use both chapter tracks and udta - - __patch_dts__ (bool, default: _false_): patch previous samples duration when dts do not increase monotonically - __uncv__ (enum, default: _prof_): use uncv (ISO 23001-17) for raw video --* off: disabled (always the case when muxing to QT) --* gen: enabled, do not write profile --* prof: enabled and write profile if known --* tiny: enabled and write reduced version if profile known and compatible -+ -+- off: disabled (always the case when muxing to QT) -+- gen: enabled, do not write profile -+- prof: enabled and write profile if known -+- tiny: enabled and write reduced version if profile known and compatible - - __trunv1__ (bool, default: _false_): force using version 1 of trun regardless of media type or CMAF brand - __rsot__ (bool, default: _false_): inject redundant sample timing information when present -diff --git a/docs/Filters/mpeghdec.md b/docs/Filters/mpeghdec.md -index a1b2f271..df6f580b 100644 ---- a/docs/Filters/mpeghdec.md -+++ b/docs/Filters/mpeghdec.md -@@ -1,6 +1,6 @@ - - --# MPEG-H Audio decoder -+# MPEG-H Audio decoder {:data-level="all"} - - Register name used to load filter: __mpeghdec__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/nhmlr.md b/docs/Filters/nhmlr.md -index 35daf657..c33d166e 100644 ---- a/docs/Filters/nhmlr.md -+++ b/docs/Filters/nhmlr.md -@@ -1,6 +1,6 @@ - - --# NHML reader -+# NHML reader {:data-level="all"} - - Register name used to load filter: __nhmlr__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/nhmlw.md b/docs/Filters/nhmlw.md -index ca157206..34895651 100644 ---- a/docs/Filters/nhmlw.md -+++ b/docs/Filters/nhmlw.md -@@ -1,6 +1,6 @@ - - --# NHML writer -+# NHML writer {:data-level="all"} - - Register name used to load filter: __nhmlw__ - This filter may be automatically loaded during graph resolution. -@@ -17,8 +17,9 @@ NHML documentation is available at https://wiki.gpac.io/xmlformats/NHML-Format - __nhmlonly__ (bool, default: _false_): only dump NHML info, not media - __pckp__ (bool, default: _false_): full NHML dump - __chksum__ (enum, default: _none_): insert frame checksum --* none: no checksum --* crc: CRC32 checksum --* sha1: SHA1 checksum -+ -+- none: no checksum -+- crc: CRC32 checksum -+- sha1: SHA1 checksum - - -diff --git a/docs/Filters/nhntr.md b/docs/Filters/nhntr.md -index 902258b5..25fc2eb4 100644 ---- a/docs/Filters/nhntr.md -+++ b/docs/Filters/nhntr.md -@@ -1,6 +1,6 @@ - - --# NHNT reader -+# NHNT reader {:data-level="all"} - - Register name used to load filter: __nhntr__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/nhntw.md b/docs/Filters/nhntw.md -index dcc9c220..23503a96 100644 ---- a/docs/Filters/nhntw.md -+++ b/docs/Filters/nhntw.md -@@ -1,6 +1,6 @@ - - --# NHNT writer -+# NHNT writer {:data-level="all"} - - Register name used to load filter: __nhntw__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/nvdec.md b/docs/Filters/nvdec.md -index 4634a2f2..e140a63e 100644 ---- a/docs/Filters/nvdec.md -+++ b/docs/Filters/nvdec.md -@@ -1,6 +1,6 @@ - - --# NVidia decoder -+# NVidia decoder {:data-level="all"} - - Register name used to load filter: __nvdec__ - This filter may be automatically loaded during graph resolution. -@@ -8,26 +8,29 @@ This filter may be automatically loaded during graph resolution. - This filter decodes MPEG-2, MPEG-4 Part 2, AVC|H264 and HEVC streams through NVidia decoder. It allows GPU frame dispatch or direct frame copy. - If the SDK is not available, the configuration key `nvdec@disabled` will be written in configuration file to avoid future load attempts. - --The absolute path to cuda lib can be set using the `cuda_lib` option in `core` or `temp` section of the config file, e.g. `-cfg=temp:cuda_lib=PATH_TO_CUDA`. --The absolute path to cuvid lib can be set using the `cuvid_lib` option in `core` or `temp` section of the config file, e.g. `-cfg=temp:cuvid_lib=PATH_TO_CUDA`. -- -+The absolute path to cuda lib can be set using the `cuda_lib` option in `core` or `temp` section of the config file, e.g. `-cfg=temp:cuda_lib=PATH_TO_CUDA` -+The absolute path to cuvid lib can be set using the `cuvid_lib` option in `core` or `temp` section of the config file, e.g. `-cfg=temp:cuvid_lib=PATH_TO_CUDA` -+ - - # Options - - __num_surfaces__ (uint, default: _20_): number of hardware surfaces to allocate - __unload__ (enum, default: _no_): decoder unload mode --* no: keep inactive decoder alive --* destroy: destroy inactive decoder --* reuse: detach decoder from inactive PIDs and reattach to active ones -+ -+- no: keep inactive decoder alive -+- destroy: destroy inactive decoder -+- reuse: detach decoder from inactive PIDs and reattach to active ones - - __vmode__ (enum, default: _cuvid_): video decoder backend --* cuvid: use dedicated video engines directly --* cuda: use a CUDA-based decoder if faster than dedicated engines --* dxva: go through DXVA internally if possible (requires D3D9) -+ -+- cuvid: use dedicated video engines directly -+- cuda: use a CUDA-based decoder if faster than dedicated engines -+- dxva: go through DXVA internally if possible (requires D3D9) - - __fmode__ (enum, default: _gl_): frame output mode --* copy: each frame is copied and dispatched --* single: frame data is only retrieved when used, single memory space for all frames (not safe if multiple consumers) --* gl: frame data is mapped to an OpenGL texture -+ -+- copy: each frame is copied and dispatched -+- single: frame data is only retrieved when used, single memory space for all frames (not safe if multiple consumers) -+- gl: frame data is mapped to an OpenGL texture - - -diff --git a/docs/Filters/odfdec.md b/docs/Filters/odfdec.md -index d6072528..03d24248 100644 ---- a/docs/Filters/odfdec.md -+++ b/docs/Filters/odfdec.md -@@ -1,6 +1,6 @@ - - --# MPEG-4 OD decoder -+# MPEG-4 OD decoder {:data-level="all"} - - Register name used to load filter: __odfdec__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/oggdmx.md b/docs/Filters/oggdmx.md -index 092e2621..cede61d9 100644 ---- a/docs/Filters/oggdmx.md -+++ b/docs/Filters/oggdmx.md -@@ -1,6 +1,6 @@ - - --# OGG demultiplexer -+# OGG demultiplexer {:data-level="all"} - - Register name used to load filter: __oggdmx__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/oggmx.md b/docs/Filters/oggmx.md -index b547ee5a..ae0ece91 100644 ---- a/docs/Filters/oggmx.md -+++ b/docs/Filters/oggmx.md -@@ -1,6 +1,6 @@ - - --# OGG multiplexer -+# OGG multiplexer {:data-level="all"} - - Register name used to load filter: __oggmx__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/ohevcdec.md b/docs/Filters/ohevcdec.md -index 61a4d4f3..85da0a28 100644 ---- a/docs/Filters/ohevcdec.md -+++ b/docs/Filters/ohevcdec.md -@@ -1,6 +1,6 @@ - - --# OpenHEVC decoder -+# OpenHEVC decoder {:data-level="all"} - - Register name used to load filter: __ohevcdec__ - This filter may be automatically loaded during graph resolution. -@@ -11,9 +11,10 @@ This filter decodes HEVC and LHVC (HEVC scalable extensions) from one or more PI - # Options - - __threading__ (enum, default: _frame_): set threading mode --* frameslice: parallel decoding of both frames and slices --* frame: parallel decoding of frames --* slice: parallel decoding of slices -+ -+- frameslice: parallel decoding of both frames and slices -+- frame: parallel decoding of frames -+- slice: parallel decoding of slices - - __nb_threads__ (uint, default: _0_): set number of threads (if 0, uses number of cores minus one) - __no_copy__ (bool, default: _false_): directly dispatch internal decoded frame without copy -diff --git a/docs/Filters/osvcdec.md b/docs/Filters/osvcdec.md -index 6cffa2f3..c7b26e2c 100644 ---- a/docs/Filters/osvcdec.md -+++ b/docs/Filters/osvcdec.md -@@ -1,6 +1,6 @@ - - --# OpenSVC decoder -+# OpenSVC decoder {:data-level="all"} - - Register name used to load filter: __osvcdec__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/pin.md b/docs/Filters/pin.md -index ad1f29b4..9f58ebc5 100644 ---- a/docs/Filters/pin.md -+++ b/docs/Filters/pin.md -@@ -1,6 +1,6 @@ - - --# pipe input -+# pipe input {:data-level="all"} - - Register name used to load filter: __pin__ - This filter may be automatically loaded during graph resolution. -@@ -35,13 +35,17 @@ The filter can create the pipe if not found using [mkp](#mkp). On windows hosts, - On non windows hosts, the created pipe will delete the pipe file upon filter destruction. - - Input pipes can be setup to run forever using [ka](#ka). In this case: -+ - - any potential pipe close on the writing side will be ignored - - pipeline flushing will be triggered upon pipe close if [sigflush](#sigflush) is set - - final end of stream will be triggered upon session close. -+ - - This can be useful to pipe raw streams from different process into gpac: --* Receiver side: `gpac -i pipe://mypipe:ext=.264:mkp:ka` --* Sender side: `cat raw1.264 > mypipe && gpac -i raw2.264 -o pipe://mypipe:ext=.264` -+ -+- Receiver side: `gpac -i pipe://mypipe:ext=.264:mkp:ka` -+- Sender side: `cat raw1.264 > mypipe && gpac -i raw2.264 -o pipe://mypipe:ext=.264` -+ - The pipeline flush is signaled as EOS while keeping the stream active. - This is typically needed for mux filters waiting for EOS to flush their data. - -diff --git a/docs/Filters/pngenc.md b/docs/Filters/pngenc.md -index 58151bf3..c7396f23 100644 ---- a/docs/Filters/pngenc.md -+++ b/docs/Filters/pngenc.md -@@ -1,6 +1,6 @@ - - --# PNG encoder -+# PNG encoder {:data-level="all"} - - Register name used to load filter: __pngenc__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/pout.md b/docs/Filters/pout.md -index aace8f97..e318a4ea 100644 ---- a/docs/Filters/pout.md -+++ b/docs/Filters/pout.md -@@ -1,6 +1,6 @@ - - --# pipe output -+# pipe output {:data-level="all"} - - Register name used to load filter: __pout__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/probe.md b/docs/Filters/probe.md -index 08eeac3b..463fe9cf 100644 ---- a/docs/Filters/probe.md -+++ b/docs/Filters/probe.md -@@ -1,6 +1,6 @@ - - --# Probe source -+# Probe source {:data-level="all"} - - Register name used to load filter: __probe__ - This filter is not checked during graph resolution and needs explicit loading. -@@ -14,10 +14,11 @@ It is up to the app developer to query input PIDs of the prober and take appropr - # Options - - __log__ (str, default: _stdout_, Enum: _any|stderr|stdout|GLOG|null): set probe log filename to print number of streams --* _any: target file path and name --* stderr: dump to stderr --* stdout: dump to stdout --* GLOG: use GPAC logs `app@info` --* null: silent mode -+ -+- _any: target file path and name -+- stderr: dump to stderr -+- stdout: dump to stdout -+- GLOG: use GPAC logs `app@info` -+- null: silent mode - - -diff --git a/docs/Filters/reframer.md b/docs/Filters/reframer.md -index d0a78723..4aac59df 100644 ---- a/docs/Filters/reframer.md -+++ b/docs/Filters/reframer.md -@@ -1,17 +1,19 @@ - - --# Media Reframer -+# Media Reframer {:data-level="all"} - - Register name used to load filter: __reframer__ - This filter is not checked during graph resolution and needs explicit loading. - Filters of this class can connect to each-other. - - This filter provides various tools on inputs: -+ - - ensure reframing (1 packet = 1 Access Unit) - - optionally force decoding - - real-time regulation - - packet filtering based on SAP types or frame numbers - - time-range extraction and splitting -+ - - This filter forces input PIDs to be properly framed (1 packet = 1 Access Unit). - It is typically needed to force remultiplexing in file to file operations when source and destination files use the same format. -@@ -51,12 +53,14 @@ gpac -i m.mp4 reframer:rt=on -o live.mpd:dynamic - - The filter can perform time range extraction of the source using [xs](#xs) and [xe](#xe) options. - The formats allowed for times specifiers are: --* 'T'H:M:S, 'T'M:S: specify time in hours, minutes, seconds --* 'T'H:M:S.MS, 'T'M:S.MS, 'T'S.MS: specify time in hours, minutes, seconds and milliseconds --* INT, FLOAT, NUM/DEN: specify time in seconds (number or fraction) --* 'D'INT, 'D'FLOAT, 'D'NUM/DEN: specify end time as offset to start time in seconds (number or fraction) - only valid for [xe](#xe) --* 'F'NUM: specify time as frame number, 1 being first --* XML DateTime: specify absolute UTC time -+ -+- 'T'H:M:S, 'T'M:S: specify time in hours, minutes, seconds -+- 'T'H:M:S.MS, 'T'M:S.MS, 'T'S.MS: specify time in hours, minutes, seconds and milliseconds -+- INT, FLOAT, NUM/DEN: specify time in seconds (number or fraction) -+- 'D'INT, 'D'FLOAT, 'D'NUM/DEN: specify end time as offset to start time in seconds (number or fraction) - only valid for [xe](#xe) -+- 'F'NUM: specify time as frame number, 1 being first -+- XML DateTime: specify absolute UTC time -+ - - In this mode, the timestamps are rewritten to form a continuous timeline, unless [xots](#xots) is set. - When multiple ranges are given, the filter will try to seek if needed and supported by source. -@@ -65,32 +69,40 @@ Example - ``` - gpac -i m.mp4 reframer:xs=T00:00:10,T00:01:10,T00:02:00:xe=T00:00:20,T00:01:20 [dst] - ``` -+ - This will extract the time ranges [10s,20s], [1m10s,1m20s] and all media starting from 2m - - If no end range is found for a given start range: -+ - - if a following start range is set, the end range is set to this next start - - otherwise, the end range is open -+ - - Example - ``` - gpac -i m.mp4 reframer:xs=0,10,25:xe=5,20 [dst] --``` -+``` -+ - This will extract the time ranges [0s,5s], [10s,20s] and all media starting from 25s - Example - ``` - gpac -i m.mp4 reframer:xs=0,10,25 [dst] --``` -+``` -+ - This will extract the time ranges [0s,10s], [10s,25s] and all media starting from 25s - - It is possible to signal range boundaries in output packets using [splitrange](#splitrange). - This will expose on the first packet of each range in each PID the following properties: --* `FileNumber`: starting at 1 for the first range, to be used as replacement for $num$ in templates --* `FileSuffix`: corresponding to `StartRange_EndRange` or `StartRange` for open ranges, to be used as replacement for $FS$ in templates -+ -+- `FileNumber`: starting at 1 for the first range, to be used as replacement for $num$ in templates -+- `FileSuffix`: corresponding to `StartRange_EndRange` or `StartRange` for open ranges, to be used as replacement for $FS$ in templates -+ - - Example - ``` - gpac -i m.mp4 reframer:xs=T00:00:10,T00:01:10:xe=T00:00:20:splitrange -o dump_$FS$.264 [dst] - ``` -+ - This will create two output files dump_T00.00.10_T00.02.00.264 and dump_T00.01.10.264. - _Note: The `:` and `/` characters are replaced by `.` in `FileSuffix` property._ - -@@ -102,18 +114,23 @@ Example - ``` - gpac -i m.mp4 reframer:xs=0,30::props=#Period=P1,#Period=P2:#foo=bar [dst] - ``` -+ - This will assign to output PIDs --* during the range [0,30]: property `Period` to `P1` --* during the range [30, end]: properties `Period` to `P2` and property `foo` to `bar` -+ -+- during the range [0,30]: property `Period` to `P1` -+- during the range [30, end]: properties `Period` to `P2` and property `foo` to `bar` -+ - - For uncompressed audio PIDs, input frame will be split to closest audio sample number. - - When [xround](#xround) is set to `seek`, the following applies: -+ - - a single range shall be specified - - the first I-frame preceding or matching the range start is used as split point - - all packets before range start are marked as seek points - - packets overlapping range start are forwarded with a `SkipBegin` property set to the amount of media to skip - - packets overlapping range end are forwarded with an adjusted duration to match the range end -+ - This mode is typically used to extract a range in a frame/sample accurate way, rather than a GOP-aligned way. - - When [xround](#xround) is not set to `seek`, compressed audio streams will still use seek mode. -@@ -123,8 +140,10 @@ This can be avoided using [no_audio_seek](#no_audio_seek), but this will introdu - # UTC-based range extraction - - The filter can perform range extraction based on UTC time rather than media time. In this mode, the end time must be: --* a UTC date: range extraction will stop after this date --* a time in second: range extraction will stop after the specified duration -+ -+- a UTC date: range extraction will stop after this date -+- a time in second: range extraction will stop after the specified duration -+ - - The UTC reference is specified using [utc_ref](#utc_ref). - If UTC signal from media source is used, the filter will probe for [utc_probe](#utc_probe) before considering the source has no UTC signal. -@@ -135,10 +154,12 @@ The properties `SenderNTP` and, if absent, `UTC` of source packets are checked f - - The filter can perform splitting of the source using [xs](#xs) option. - The additional formats allowed for [xs](#xs) option are: --* `SAP`: split source at each SAP/RAP --* `D`VAL: split source by chunks of `VAL` seconds --* `D`NUM/DEN: split source by chunks of `NUM/DEN` seconds --* `S`VAL: split source by chunks of estimated size `VAL` bytes (can use property multipliers, e.g. `m`) -+ -+- `SAP`: split source at each SAP/RAP -+- `D`VAL: split source by chunks of `VAL` seconds -+- `D`NUM/DEN: split source by chunks of `NUM/DEN` seconds -+- `S`VAL: split source by chunks of estimated size `VAL` bytes (can use property multipliers, e.g. `m`) -+ - - _Note: In these modes, [splitrange](#splitrange) and [xadjust](#xadjust) are implicitly set._ - -@@ -147,28 +168,31 @@ _Note: In these modes, [splitrange](#splitrange) and [xadjust](#xadjust) are imp - - __exporter__ (bool, default: _false_): compatibility with old exporter, displays export results - __rt__ (enum, default: _off_, updatable): real-time regulation mode of input --* off: disables real-time regulation --* on: enables real-time regulation, one clock per PID --* sync: enables real-time regulation one clock for all PIDs -+ -+- off: disables real-time regulation -+- on: enables real-time regulation, one clock per PID -+- sync: enables real-time regulation one clock for all PIDs - - __saps__ (uintl, Enum: 0|1|2|3|4, updatable): list of SAP types (0,1,2,3,4) to forward, other packets are dropped (forwarding only sap 0 will break the decoding) - - __refs__ (bool, default: _false_, updatable): forward only frames used as reference frames, if indicated in the input stream - __speed__ (dbl, default: _0.0_, updatable): speed for real-time regulation mode, a value of 0 uses speed from play commands - __raw__ (enum, default: _no_): force input AV streams to be in raw format --* no: do not force decoding of inputs --* av: force decoding of audio and video inputs --* a: force decoding of audio inputs --* v: force decoding of video inputs -+ -+- no: do not force decoding of inputs -+- av: force decoding of audio and video inputs -+- a: force decoding of audio inputs -+- v: force decoding of video inputs - - __frames__ (sintl, updatable): drop all except listed frames (first being 1). A negative value `-V` keeps only first frame every `V` frames - __xs__ (strl): extraction start time(s) - __xe__ (strl): extraction end time(s). If less values than start times, the last time interval extracted is an open range - __xround__ (enum, default: _before_): adjust start time of extraction range to I-frame --* before: use first I-frame preceding or matching range start --* seek: see filter help --* after: use first I-frame (if any) following or matching range start --* closest: use I-frame closest to range start -+ -+- before: use first I-frame preceding or matching range start -+- seek: see filter help -+- after: use first I-frame (if any) following or matching range start -+- closest: use I-frame closest to range start - - __xadjust__ (bool, default: _false_): adjust end time of extraction range to be before next I-frame - __xots__ (bool, default: _false_): keep original timestamps after extraction -@@ -180,16 +204,18 @@ _Note: In these modes, [splitrange](#splitrange) and [xadjust](#xadjust) are imp - __no_audio_seek__ (bool, default: _false_): disable seek mode on audio streams (no change of priming duration) - __probe_ref__ (bool, default: _false_): allow extracted range to be longer in case of B-frames with reference frames presented outside of range - __utc_ref__ (enum, default: _any_): set reference mode for UTC range extraction --* local: use UTC of local host --* any: use UTC of media, or UTC of local host if not found in media after probing time --* media: use UTC of media (abort if none found) -+ -+- local: use UTC of local host -+- any: use UTC of media, or UTC of local host if not found in media after probing time -+- media: use UTC of media (abort if none found) - - __utc_probe__ (uint, default: _5000_): timeout in milliseconds to try to acquire UTC reference from media - __copy__ (bool, default: _false_, updatable): try copying frame interface into packets - __cues__ (enum, default: _no_, updatable): cue filtering mode --* no: do no filter frames based on cue info --* segs: only forward frames marked as segment start --* frags: only forward frames marked as fragment start -+ -+- no: do no filter frames based on cue info -+- segs: only forward frames marked as segment start -+- frags: only forward frames marked as fragment start - - __rmseek__ (bool, default: _false_, updatable): remove seek flag of all sent packets - -diff --git a/docs/Filters/resample.md b/docs/Filters/resample.md -index ca1d2dfe..e5e3210a 100644 ---- a/docs/Filters/resample.md -+++ b/docs/Filters/resample.md -@@ -1,6 +1,6 @@ - - --# Audio resampler -+# Audio resampler {:data-level="all"} - - Register name used to load filter: __resample__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/restamp.md b/docs/Filters/restamp.md -index e9a1a828..46ed0456 100644 ---- a/docs/Filters/restamp.md -+++ b/docs/Filters/restamp.md -@@ -1,6 +1,6 @@ - - --# Packet timestamp rewriter -+# Packet timestamp rewriter {:data-level="all"} - - Register name used to load filter: __restamp__ - This filter is not checked during graph resolution and needs explicit loading. -@@ -11,13 +11,16 @@ This filter rewrites timing (offsets and rate) of packets. - The delays (global or per stream class) can be either positive (stream presented later) or negative (stream presented sooner). - - The specified [fps](#fps) can be either 0, positive or negative. -+ - - if 0 or if the stream is audio, stream rate is not modified. - - otherwise if negative, stream rate is multiplied by `-fps.num/fps.den`. - - otherwise if positive and the stream is not video, stream rate is not modified. - - otherwise (video PID), constant frame rate is assumed and: -- - if [rawv=no](#rawv=no), video frame rate is changed to the specified rate (speed-up or slow-down). -- - if [rawv=force](#rawv=force), input video stream is decoded and video frames are dropped/copied to match the new rate. -- - if [rawv=dyn](#rawv=dyn), input video stream is decoded if not all-intra and video frames are dropped/copied to match the new rate. -+ -+ - if [rawv=no](#rawv=no), video frame rate is changed to the specified rate (speed-up or slow-down). -+ - if [rawv=force](#rawv=force), input video stream is decoded and video frames are dropped/copied to match the new rate. -+ - if [rawv=dyn](#rawv=dyn), input video stream is decoded if not all-intra and video frames are dropped/copied to match the new rate. -+ - - _Note: frames are simply copied or dropped with no motion compensation._ - -@@ -34,9 +37,10 @@ is set to the last computed timestamp plus the minimum packet duration for the s - __delay_t__ (frac, default: _0/1_, updatable): delay to add to text streams - __delay_o__ (frac, default: _0/1_, updatable): delay to add to other streams - __rawv__ (enum, default: _no_): copy video frames --* no: no raw frame copy/drop --* force: force decoding all video streams --* dyn: decoding video streams if not all intra -+ -+- no: no raw frame copy/drop -+- force: force decoding all video streams -+- dyn: decoding video streams if not all intra - - __tsinit__ (lfrac, default: _-1/1_): initial timestamp to resync to, negative values disables resync - __align__ (uint, default: _0_): timestamp alignment threshold (0 disables alignment) - see filter help -diff --git a/docs/Filters/rewind.md b/docs/Filters/rewind.md -index 7ec1b63a..73f6ff9f 100644 ---- a/docs/Filters/rewind.md -+++ b/docs/Filters/rewind.md -@@ -1,6 +1,6 @@ - - --# Audio/Video rewinder -+# Audio/Video rewinder {:data-level="all"} - - Register name used to load filter: __rewind__ - This filter is not checked during graph resolution and needs explicit loading. -diff --git a/docs/Filters/rfac3.md b/docs/Filters/rfac3.md -index 3ff4332a..5a25aa95 100644 ---- a/docs/Filters/rfac3.md -+++ b/docs/Filters/rfac3.md -@@ -1,6 +1,6 @@ - - --# AC3 reframer -+# AC3 reframer {:data-level="all"} - - Register name used to load filter: __rfac3__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/rfadts.md b/docs/Filters/rfadts.md -index b307f238..be969fd6 100644 ---- a/docs/Filters/rfadts.md -+++ b/docs/Filters/rfadts.md -@@ -1,6 +1,6 @@ - - --# ADTS reframer -+# ADTS reframer {:data-level="all"} - - Register name used to load filter: __rfadts__ - This filter may be automatically loaded during graph resolution. -@@ -14,14 +14,16 @@ This filter parses AAC files/data and outputs corresponding audio PID and frames - __index__ (dbl, default: _1.0_): indexing window length - __ovsbr__ (bool, default: _false_): force oversampling SBR (does not multiply timescales by 2) - __sbr__ (enum, default: _no_): set SBR signaling --* no: no SBR signaling at all --* imp: backward-compatible SBR signaling (audio signaled as AAC-LC) --* exp: explicit SBR signaling (audio signaled as AAC-SBR) -+ -+- no: no SBR signaling at all -+- imp: backward-compatible SBR signaling (audio signaled as AAC-LC) -+- exp: explicit SBR signaling (audio signaled as AAC-SBR) - - __ps__ (enum, default: _no_): set PS signaling --* no: no PS signaling at all --* imp: backward-compatible PS signaling (audio signaled as AAC-LC) --* exp: explicit PS signaling (audio signaled as AAC-PS) -+ -+- no: no PS signaling at all -+- imp: backward-compatible PS signaling (audio signaled as AAC-LC) -+- exp: explicit PS signaling (audio signaled as AAC-PS) - - __expart__ (bool, default: _false_): expose pictures as a dedicated video PID - __aacchcfg__ (sint, default: _0_): set AAC channel configuration to this value if missing from ADTS header, use negative value to always override -diff --git a/docs/Filters/rfamr.md b/docs/Filters/rfamr.md -index 4bb6622e..607cf107 100644 ---- a/docs/Filters/rfamr.md -+++ b/docs/Filters/rfamr.md -@@ -1,6 +1,6 @@ - - --# AMR/EVRC reframer -+# AMR/EVRC reframer {:data-level="all"} - - Register name used to load filter: __rfamr__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/rfav1.md b/docs/Filters/rfav1.md -index 84e170db..8d2b7fc9 100644 ---- a/docs/Filters/rfav1.md -+++ b/docs/Filters/rfav1.md -@@ -1,6 +1,6 @@ - - --# AV1/IVF/VP9 reframer -+# AV1/IVF/VP9 reframer {:data-level="all"} - - Register name used to load filter: __rfav1__ - This filter may be automatically loaded during graph resolution. -@@ -17,8 +17,9 @@ This filter parses AV1 OBU, AV1 AnnexB or IVF with AV1 or VP9 files/data and out - __notime__ (bool, default: _false_): ignore input timestamps, rebuild from 0 - __temporal_delim__ (bool, default: _false_): keep temporal delimiters in reconstructed frames - __bsdbg__ (enum, default: _off_): debug OBU parsing in `media@debug logs` --* off: not enabled --* on: enabled --* full: enable with number of bits dumped -+ -+- off: not enabled -+- on: enabled -+- full: enable with number of bits dumped - - -diff --git a/docs/Filters/rfflac.md b/docs/Filters/rfflac.md -index 93e2d465..1f167471 100644 ---- a/docs/Filters/rfflac.md -+++ b/docs/Filters/rfflac.md -@@ -1,6 +1,6 @@ - - --# FLAC reframer -+# FLAC reframer {:data-level="all"} - - Register name used to load filter: __rfflac__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/rfh263.md b/docs/Filters/rfh263.md -index 090bfad4..68a72dc6 100644 ---- a/docs/Filters/rfh263.md -+++ b/docs/Filters/rfh263.md -@@ -1,6 +1,6 @@ - - --# H263 reframer -+# H263 reframer {:data-level="all"} - - Register name used to load filter: __rfh263__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/rfimg.md b/docs/Filters/rfimg.md -index aafea614..7c602601 100644 ---- a/docs/Filters/rfimg.md -+++ b/docs/Filters/rfimg.md -@@ -1,6 +1,6 @@ - - --# JPG/J2K/PNG/BMP reframer -+# JPG/J2K/PNG/BMP reframer {:data-level="all"} - - Register name used to load filter: __rfimg__ - This filter may be automatically loaded during graph resolution. -@@ -8,8 +8,10 @@ This filter may be automatically loaded during graph resolution. - This filter parses JPG/J2K/PNG/BMP files/data and outputs corresponding visual PID and frames. - - The following extensions for PNG change the pixel format for RGBA images: --* pngd: use RGB+depth map pixel format --* pngds: use RGB+depth(7bits)+shape(MSB of alpha channel) pixel format -+ -+- pngd: use RGB+depth map pixel format -+- pngds: use RGB+depth(7bits)+shape(MSB of alpha channel) pixel format -+ - - No options - -diff --git a/docs/Filters/rflatm.md b/docs/Filters/rflatm.md -index fa59a0a0..a3ac8754 100644 ---- a/docs/Filters/rflatm.md -+++ b/docs/Filters/rflatm.md -@@ -1,6 +1,6 @@ - - --# LATM reframer -+# LATM reframer {:data-level="all"} - - Register name used to load filter: __rflatm__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/rfmhas.md b/docs/Filters/rfmhas.md -index 3194593f..cf49afc2 100644 ---- a/docs/Filters/rfmhas.md -+++ b/docs/Filters/rfmhas.md -@@ -1,6 +1,6 @@ - - --# MPEH-H Audio Stream reframer -+# MPEH-H Audio Stream reframer {:data-level="all"} - - Register name used to load filter: __rfmhas__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/rfmp3.md b/docs/Filters/rfmp3.md -index a8455510..8f21fc25 100644 ---- a/docs/Filters/rfmp3.md -+++ b/docs/Filters/rfmp3.md -@@ -1,6 +1,6 @@ - - --# MP3 reframer -+# MP3 reframer {:data-level="all"} - - Register name used to load filter: __rfmp3__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/rfmpgvid.md b/docs/Filters/rfmpgvid.md -index 72aff953..7359d26b 100644 ---- a/docs/Filters/rfmpgvid.md -+++ b/docs/Filters/rfmpgvid.md -@@ -1,6 +1,6 @@ - - --# M1V/M2V/M4V reframer -+# M1V/M2V/M4V reframer {:data-level="all"} - - Register name used to load filter: __rfmpgvid__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/rfnalu.md b/docs/Filters/rfnalu.md -index ec642285..4febe4c8 100644 ---- a/docs/Filters/rfnalu.md -+++ b/docs/Filters/rfnalu.md -@@ -1,6 +1,6 @@ - - --# AVC/HEVC reframer -+# AVC/HEVC reframer {:data-level="all"} - - Register name used to load filter: __rfnalu__ - This filter may be automatically loaded during graph resolution. -@@ -16,9 +16,10 @@ _Note: The filter uses negative CTS offsets: CTS is correct, but some frames may - __index__ (dbl, default: _-1.0_): indexing window length. If 0, bitstream is not probed for duration. A negative value skips the indexing if the source file is larger than 20M (slows down importers) unless a play with start range > 0 is issued - __explicit__ (bool, default: _false_): use explicit layered (SVC/LHVC) import - __strict_poc__ (enum, default: _off_): delay frame output of an entire GOP to ensure CTS info is correct when POC suddenly changes --* off: disable GOP buffering --* on: enable GOP buffering, assuming no error in POC --* error: enable GOP buffering and try to detect lost frames -+ -+- off: disable GOP buffering -+- on: enable GOP buffering, assuming no error in POC -+- error: enable GOP buffering and try to detect lost frames - - __nosei__ (bool, default: _false_): remove all sei messages - __nosvc__ (bool, default: _false_): remove all SVC/MVC/LHVC data -@@ -31,25 +32,28 @@ _Note: The filter uses negative CTS offsets: CTS is correct, but some frames may - __audelim__ (bool, default: _false_): keep Access Unit delimiter in payload - __notime__ (bool, default: _false_): ignore input timestamps, rebuild from 0 - __dv_mode__ (enum, default: _auto_): signaling for DolbyVision --* none: never signal DV profile --* auto: signal DV profile if RPU or EL are found --* clean: do not signal and remove RPU and EL NAL units --* single: signal DV profile if RPU are found and remove EL NAL units -+ -+- none: never signal DV profile -+- auto: signal DV profile if RPU or EL are found -+- clean: do not signal and remove RPU and EL NAL units -+- single: signal DV profile if RPU are found and remove EL NAL units - - __dv_profile__ (uint, default: _0_): profile for DolbyVision (currently defined profiles are 4, 5, 7, 8, 9), 0 for auto-detect - __dv_compatid__ (enum, default: _auto_): cross-compatibility ID for DolbyVision --* auto: auto-detect --* none: no cross-compatibility --* hdr10: CTA HDR10, as specified by EBU TR 03 --* bt709: SDR BT.709 --* hlg709: HLG BT.709 gamut in ITU-R BT.2020 --* hlg2100: HLG BT.2100 gamut in ITU-R BT.2020 --* bt2020: SDR BT.2020 --* brd: Ultra HD Blu-ray Disc HDR -+ -+- auto: auto-detect -+- none: no cross-compatibility -+- hdr10: CTA HDR10, as specified by EBU TR 03 -+- bt709: SDR BT.709 -+- hlg709: HLG BT.709 gamut in ITU-R BT.2020 -+- hlg2100: HLG BT.2100 gamut in ITU-R BT.2020 -+- bt2020: SDR BT.2020 -+- brd: Ultra HD Blu-ray Disc HDR - - __bsdbg__ (enum, default: _off_): debug NAL parsing in `media@debug` logs --* off: not enabled --* on: enabled --* full: enable with number of bits dumped -+ -+- off: not enabled -+- on: enabled -+- full: enable with number of bits dumped - - -diff --git a/docs/Filters/rfpcm.md b/docs/Filters/rfpcm.md -index 7d5d3b98..d8fe952b 100644 ---- a/docs/Filters/rfpcm.md -+++ b/docs/Filters/rfpcm.md -@@ -1,6 +1,6 @@ - - --# PCM reframer -+# PCM reframer {:data-level="all"} - - Register name used to load filter: __rfpcm__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/rfprores.md b/docs/Filters/rfprores.md -index c0d985e2..c03e8366 100644 ---- a/docs/Filters/rfprores.md -+++ b/docs/Filters/rfprores.md -@@ -1,6 +1,6 @@ - - --# ProRes reframer -+# ProRes reframer {:data-level="all"} - - Register name used to load filter: __rfprores__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/rfqcp.md b/docs/Filters/rfqcp.md -index 5da4e3f1..a36e713e 100644 ---- a/docs/Filters/rfqcp.md -+++ b/docs/Filters/rfqcp.md -@@ -1,6 +1,6 @@ - - --# QCP reframer -+# QCP reframer {:data-level="all"} - - Register name used to load filter: __rfqcp__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/rfrawvid.md b/docs/Filters/rfrawvid.md -index b57c21d9..a74d8687 100644 ---- a/docs/Filters/rfrawvid.md -+++ b/docs/Filters/rfrawvid.md -@@ -1,6 +1,6 @@ - - --# RAW video reframer -+# RAW video reframer {:data-level="all"} - - Register name used to load filter: __rfrawvid__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/rfsrt.md b/docs/Filters/rfsrt.md -index e486f993..b79d8eee 100644 ---- a/docs/Filters/rfsrt.md -+++ b/docs/Filters/rfsrt.md -@@ -1,6 +1,6 @@ - - --# SRT reframer -+# SRT reframer {:data-level="all"} - - Register name used to load filter: __rfsrt__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/rftruehd.md b/docs/Filters/rftruehd.md -index a25072ca..a9f1f2d3 100644 ---- a/docs/Filters/rftruehd.md -+++ b/docs/Filters/rftruehd.md -@@ -1,6 +1,6 @@ - - --# TrueHD reframer -+# TrueHD reframer {:data-level="all"} - - Register name used to load filter: __rftruehd__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/routein.md b/docs/Filters/routein.md -index 1e7b6832..8c395b94 100644 ---- a/docs/Filters/routein.md -+++ b/docs/Filters/routein.md -@@ -1,31 +1,37 @@ - - --# ROUTE input -+# ROUTE input {:data-level="all"} - - Register name used to load filter: __routein__ - This filter may be automatically loaded during graph resolution. - --This filter is a receiver for ROUTE sessions (ATSC 3.0 and generic ROUTE) and DVB-MABR flute sessions. -+This filter is a receiver for file delivery over multicast. It currently supports ATSC 3.0, generic ROUTE and DVB-MABR flute. -+ - - ATSC 3.0 mode is identified by the URL `atsc://`. - - Generic ROUTE mode is identified by the URL `route://IP:PORT`. - - DVB-MABR mode is identified by the URL `mabr://IP:PORT` pointing to the bootstrap FLUTE channel carrying the multicast gateway configuration. -+ - - The filter can work in cached mode, source mode or standalone mode. - - # Cached mode - --The cached mode is the default filter behavior. It populates GPAC HTTP Cache with the received files, using `http://groute/serviceN/` as service root, `N being the ROUTE service ID.` -+The cached mode is the default filter behavior. It populates GPAC HTTP Cache with the received files, using `http://gmcast/serviceN/` as service root, `N being the multicast service ID.` - In cached mode, repeated files are always pushed to cache. - The maximum number of media segment objects in cache per service is defined by [nbcached](#nbcached); this is a safety used to force object removal in case DASH client timing is wrong and some files are never requested at cache level. - - The cached MPD is assigned the following headers: --* `x-route`: integer value, indicates the ROUTE service ID. --* `x-route-first-seg`: string value, indicates the name of the first segment (completely or currently being) retrieved from the broadcast. --* `x-route-ll`: boolean value, if yes indicates that the indicated first segment is currently being received (low latency signaling). --* `x-route-loop`: boolean value, if yes indicates a loop (e.g. pcap replay) in the service has been detected - only checked if [cloop](#cloop) is set. -+ -+- `x-mcast`: boolean value, if `yes` indicates the file comes from a multicast. -+- `x-mcast-first-seg`: string value, indicates the name of the first segment (completely or currently being) retrieved from the broadcast. -+- `x-mcast-ll`: boolean value, if yes indicates that the indicated first segment is currently being received (low latency signaling). -+- `x-mcast-loop`: boolean value, if yes indicates a loop (e.g. pcap replay) in the service has been detected - only checked if [cloop](#cloop) is set. -+ - - The cached files are assigned the following headers: --* `x-route`: boolean value, if yes indicates the file comes from an ROUTE session. -+ -+- `x-mcast`: boolean value, if `yes` indicates the file comes from a multicast. -+ - - If [max_segs](#max_segs) is set, file deletion event will be triggered in the filter chain. - -@@ -55,8 +61,10 @@ If [max_segs](#max_segs) is set, old files will be deleted. - # File Repair - - In case of losses or incomplete segment reception (during tune-in), the files are patched as follows: --* MPEG-2 TS: all lost ranges are adjusted to 188-bytes boundaries, and transformed into NULL TS packets. --* ISOBMFF: all top-level boxes are scanned, and incomplete boxes are transformed in `free` boxes, except mdat kept as is if [repair](#repair) is set to simple. -+ -+- MPEG-2 TS: all lost ranges are adjusted to 188-bytes boundaries, and transformed into NULL TS packets. -+- ISOBMFF: all top-level boxes are scanned, and incomplete boxes are transformed in `free` boxes, except mdat kept as is if [repair](#repair) is set to simple. -+ - - If [kc](#kc) option is set, corrupted files will be kept. If [fullseg](#fullseg) is not set and files are only partially received, they will be kept. - -@@ -68,7 +76,7 @@ Example - ``` - route add -net 224.0.23.60/32 -interface vboxnet0 - ``` --Then for each ROUTE service in the multicast: -+Then for each multicast service in the multicast: - Example - ``` - route add -net 239.255.1.4/32 -interface vboxnet0 -@@ -96,11 +104,14 @@ route add -net 239.255.1.4/32 -interface vboxnet0 - __rtimeout__ (uint, default: _1000_): default timeout in us to wait when gathering out-of-order packets - __fullseg__ (bool, default: _false_): only dispatch full segments in cache mode (always true for other modes) - __repair__ (enum, default: _simple_): repair mode for corrupted files --* no: no repair is performed --* simple: simple repair is performed (incomplete `mdat` boxes will be kept) --* strict: incomplete mdat boxes will be lost as well as preceding `moof` boxes --* full: HTTP-based repair of all lost packets -+ -+- no: no repair is performed -+- simple: simple repair is performed (incomplete `mdat` boxes will be kept) -+- strict: incomplete mdat boxes will be lost as well as preceding `moof` boxes -+- full: HTTP-based repair of all lost packets - - __repair_url__ (cstr): repair url - __max_sess__ (uint, default: _1_): max number of concurrent HTTP repair sessions -+__llmode__ (bool, default: _true_): enable low-latency access -+__dynsel__ (bool, default: _true_): dynamically enable and disable multicast groups based on their selection state - -diff --git a/docs/Filters/routeout.md b/docs/Filters/routeout.md -index 20519614..917f00b1 100644 ---- a/docs/Filters/routeout.md -+++ b/docs/Filters/routeout.md -@@ -1,6 +1,6 @@ - - --# ROUTE output -+# ROUTE output {:data-level="all"} - - Register name used to load filter: __routeout__ - This filter may be automatically loaded during graph resolution. -@@ -9,19 +9,25 @@ The ROUTE output filter is used to distribute a live file-based session using RO - The filter supports DASH and HLS inputs, ATSC3.0 signaling and generic ROUTE or DVB-MABR signaling. - - The filter is identified using the following URL schemes: --* `atsc://`: session is a full ATSC 3.0 session --* `route://IP:port`: session is a ROUTE session running on given multicast IP and port --* `mabr://IP:port`: session is a DVB-MABR session using FLUTE running on given multicast IP and port -+ -+- `atsc://`: session is a full ATSC 3.0 session -+- `route://IP:port`: session is a ROUTE session running on given multicast IP and port -+- `mabr://IP:port`: session is a DVB-MABR session using FLUTE running on given multicast IP and port -+ - - The filter only accepts input PIDs of type `FILE`. -+ - - HAS Manifests files are detected by file extension and/or MIME types, and sent as part of the signaling bundle or as LCT object files for HLS child playlists. - - HAS Media segments are detected using the `OrigStreamType` property, and send as LCT object files using the DASH template string. - - A PID without `OrigStreamType` property set is delivered as a regular LCT object file (called `raw` hereafter). -+ - - For `raw` file PIDs, the filter will look for the following properties: --* `ROUTEName`: set resource name. If not found, uses basename of URL --* `ROUTECarousel`: set repeat period. If not found, uses [carousel](#carousel). If 0, the file is only sent once --* `ROUTEUpload`: set resource upload time. If not found, uses [carousel](#carousel). If 0, the file will be sent as fast as possible. -+ -+- `ROUTEName`: set resource name. If not found, uses basename of URL -+- `ROUTECarousel`: set repeat period. If not found, uses [carousel](#carousel). If 0, the file is only sent once -+- `ROUTEUpload`: set resource upload time. If not found, uses [carousel](#carousel). If 0, the file will be sent as fast as possible. -+ - - When DASHing for ROUTE, DVB-MABR or single service ATSC, a file extension, either in [dst](#dst) or in [ext](#ext), may be used to identify the HAS session type (DASH or HLS). - Example -@@ -34,6 +40,7 @@ Example - ``` - "atsc://:ext=mpd", "route://IP:PORT/manifest.mpd" - ``` -+ - If multiple services with different formats are needed, you will need to explicit your filters: - Example - ``` -@@ -45,9 +52,11 @@ __Warning: When forwarding an existing DASH/HLS session, do NOT set any extensio - - By default, all streams in a service are assigned to a single multicast session, and differentiated by TSI (see [splitlct](#splitlct)). - TSI are assigned as follows: -+ - - signaling TSI is always 0 for ROUTE, 1 for DVB+Flute - - raw files are assigned TSI 1 and increasing number of TOI - - otherwise, the first PID found is assigned TSI 10, the second TSI 20 etc ... -+ - - Init segments and HLS child playlists are sent before each new segment, independently of [carousel](#carousel). - -@@ -58,13 +67,15 @@ By default, a single multicast IP is used for route sessions, each service will - The filter will look for `ROUTEIP` and `ROUTEPort` properties on the incoming PID. If not found, the default [ip](#ip) and [port](#port) will be used. - - ATSC 3.0 attributes set by using the following PID properties: --* ATSC3ShortServiceName: set the short service name, maxiumu of 7 characters. If not found, `ServiceName` is checked, otherwise default to `GPAC`. --* ATSC3MajorChannel: set major channel number of service. Default to 2. This really should be set and should not use the default. --* ATSC3MinorChannel: set minor channel number of service. Default of 1. --* ATSC3ServiceCat: set service category, default to 1 if not found. 1=Linear a/v service. 2=Linear audio only service. 3=App-based service. 4=ESg service. 5=EA service. 6=DRM service. --* ATSC3hidden: set if service is hidden. Boolean true or false. Default of false. --* ATSC3hideInGuide: set if service is hidden in ESG. Boolean true or false. Default of false. --* ATSC3configuration: set service configuration. Choices are Broadcast or Broadband. Default of Broadcast -+ -+- ATSC3ShortServiceName: set the short service name, maxiumu of 7 characters. If not found, `ServiceName` is checked, otherwise default to `GPAC`. -+- ATSC3MajorChannel: set major channel number of service. Default to 2. This really should be set and should not use the default. -+- ATSC3MinorChannel: set minor channel number of service. Default of 1. -+- ATSC3ServiceCat: set service category, default to 1 if not found. 1=Linear a/v service. 2=Linear audio only service. 3=App-based service. 4=ESg service. 5=EA service. 6=DRM service. -+- ATSC3hidden: set if service is hidden. Boolean true or false. Default of false. -+- ATSC3hideInGuide: set if service is hidden in ESG. Boolean true or false. Default of false. -+- ATSC3configuration: set service configuration. Choices are Broadcast or Broadband. Default of Broadcast -+ - - # ROUTE mode - -@@ -84,33 +95,41 @@ The FLUTE session always uses a symbol length of [mtu](#mtu) minus 44 bytes. - - # Low latency mode - --When using low-latency mode, the input media segments are not re-assembled in a single packet but are instead sent as they are received. -+When using low-latency mode (-llmode)(), the input media segments are not re-assembled in a single packet but are instead sent as they are received. - In order for the real-time scheduling of data chunks to work, each fragment of the segment should have a CTS and timestamp describing its timing. - If this is not the case (typically when used with an existing DASH session in file mode), the scheduler will estimate CTS and duration based on the stream bitrate and segment duration. The indicated bitrate is increased by [brinc](#brinc) percent for safety. - If this fails, the filter will trigger warnings and send as fast as possible. - _Note: The LCT objects are sent with no length (TOL header) assigned until the final segment size is known, potentially leading to a final 0-size LCT fragment signaling only the final size._ - -+In this mode, init segments and manifests are sent at the frequency given by property `ROUTECarousel` of the source PID if set or by (-carousel)[] option. -+Indicating `ROUTECarousel=0` will disable mid-segment repeating of manifests and init segments. -+ - # Examples - - Since the ROUTE filter only consumes files, it is required to insert: -+ - - the dash demultiplexer in file forwarding mode when loading a DASH session - - the dash multiplexer when creating a DASH session -+ - - Multiplexing an existing DASH session in route: - Example - ``` - gpac -i source.mpd dashin:forward=file -o route://225.1.1.0:6000/ --``` -+``` -+ - Multiplexing an existing DASH session in atsc: - Example - ``` - gpac -i source.mpd dashin:forward=file -o atsc:// - ``` -+ - Dashing and multiplexing in route: - Example - ``` - gpac -i source.mp4 dasher:profile=live -o route://225.1.1.0:6000/manifest.mpd --``` -+``` -+ - Dashing and multiplexing in route Low Latency: - Example - ``` -@@ -128,19 +147,32 @@ Example - ``` - gpac -i source.mpd -o route://225.1.1.0:6000/ - ``` -+ - This will only send the manifest file as a regular object and will not load the dash session. - Example - ``` - gpac -i source.mpd dashin:forward=file -o route://225.1.1.0:6000/manifest.mpd - ``` -+ - This will force the ROUTE multiplexer to only accept .mpd files, and will drop all segment files (same if [ext](#ext) is used). - Example - ``` - gpac -i source.mpd dasher -o route://225.1.1.0:6000/ - gpac -i source.mpd dasher -o route://225.1.1.0:6000/manifest.mpd - ``` -+ - These will demultiplex the input, re-dash it and send the output of the dasher to ROUTE - -+# Error simulation -+ -+It is possible to simulate errors with (-errsim)(). In this mode the LCT network sender implements a 2-state Markov chain: -+Example -+``` -+gpac -i source.mpd dasher -o route://225.1.1.0:6000/:errsim=1.0x98.0 -+``` -+ -+for a 1.0 percent chance to transition to error (not sending data over the network) and 98.0 to transition from error back to OK. -+ - - # Options - -@@ -155,9 +187,10 @@ These will demultiplex the input, re-dash it and send the output of the dasher t - __bsid__ (uint, default: _800_): ID for ATSC broadcast stream - __mtu__ (uint, default: _1472_): size of LCT MTU in bytes - __splitlct__ (enum, default: _off_): split mode for LCT channels --* off: all streams are in the same LCT channel --* type: each new stream type results in a new LCT channel --* all: all streams are in dedicated LCT channel, the first stream being used for STSID signaling -+ -+- off: all streams are in the same LCT channel -+- type: each new stream type results in a new LCT channel -+- all: all streams are in dedicated LCT channel, the first stream being used for STSID signaling - - __korean__ (bool, default: _false_): use Korean version of ATSC 3.0 spec instead of US - __llmode__ (bool, default: _false_): use low-latency mode -@@ -166,9 +199,13 @@ These will demultiplex the input, re-dash it and send the output of the dasher t - __runfor__ (uint, default: _0_): run for the given time in ms - __nozip__ (bool, default: _false_): do not zip signaling package (STSID+manifest) - __furl__ (bool, default: _false_): inject full URLs of source service in the signaling instead of stripped server path -+__flute__ (bool, default: _true_): use flute for DVB-MABR object delivery - __csum__ (enum, default: _meta_): send MD5 checksum for DVB flute --* no: do not send checksum --* meta: only send checksum for configuration files, manifests and init segments --* all: send checksum for everything -+ -+- no: do not send checksum -+- meta: only send checksum for configuration files, manifests and init segments -+- all: send checksum for everything - -+__recv_obj_timeout__ (uint, default: _50_): timeout period in ms before resorting to unicast repair -+__errsim__ (v2d, default: _0.0x100.0_): simulate errors using a 2-state Markov chain. Value are percentages - -diff --git a/docs/Filters/rtpin.md b/docs/Filters/rtpin.md -index 171b06fa..6cdb4eef 100644 ---- a/docs/Filters/rtpin.md -+++ b/docs/Filters/rtpin.md -@@ -1,20 +1,24 @@ - - --# RTP/RTSP/SDP input -+# RTP/RTSP/SDP input {:data-level="all"} - - Register name used to load filter: __rtpin__ - This filter may be automatically loaded during graph resolution. - - This filter handles SDP/RTSP/RTP input reading. It supports: -+ - - SDP file reading - - RTP direct url through `rtp://` protocol scheme - - RTSP session processing through `rtsp://` and `satip://` protocol schemes -+ - - The filter produces either PIDs with media frames, or file PIDs with multiplexed data (e.g. MPEG-2 TS). - The filter will use: -+ - - RTSP over HTTP tunnel if server port is 80 or 8080 or if protocol scheme is `rtsph://`. - - RTSP over TLS if server port is 322 or if protocol scheme is `rtsps://`. - - RTSP over HTTPS tunnel if server port is 443 and if protocol scheme is `rtsph://`. -+ - - The filter will attempt reconnecting in TLS mode after two consecutive initial connection failures. - -@@ -36,9 +40,10 @@ The filter will attempt reconnecting in TLS mode after two consecutive initial c - __default_port__ (uint, default: _554_, minmax: 0-65535): set default RTSP port - __satip_port__ (uint, default: _1400_, minmax: 0-65535): set default port for SATIP - __transport__ (enum, default: _auto_): set RTP over RTSP --* auto: set interleave on if HTTP tunnel is used, off otherwise and retry in interleaved mode if UDP timeout --* tcp: enable RTP over RTSP --* udp: disable RTP over RTSP -+ -+- auto: set interleave on if HTTP tunnel is used, off otherwise and retry in interleaved mode if UDP timeout -+- tcp: enable RTP over RTSP -+- udp: disable RTP over RTSP - - __udp_timeout__ (uint, default: _10000_): default timeout before considering UDP is down - __rtcp_timeout__ (uint, default: _5000_): default timeout for RTCP traffic in ms. After this timeout, playback will start out of sync. If 0 always wait for RTCP -@@ -49,6 +54,7 @@ The filter will attempt reconnecting in TLS mode after two consecutive initial c - __languages__ (str, default: _$GLANG_): user languages, by default solved from GPAC preferences - __stats__ (uint, default: _500_): update statistics to the user every given MS (0 disables reporting) - __max_sleep__ (sint, default: _1000_): set max sleep in milliseconds: -+ - - a negative value `-N` means to always sleep for `N` ms - - a positive value `N` means to sleep at most `N` ms but will sleep less if frame duration is shorter - -diff --git a/docs/Filters/rtpout.md b/docs/Filters/rtpout.md -index b0f6da8e..4facd8ff 100644 ---- a/docs/Filters/rtpout.md -+++ b/docs/Filters/rtpout.md -@@ -1,6 +1,6 @@ - - --# RTP Streamer -+# RTP Streamer {:data-level="all"} - - Register name used to load filter: __rtpout__ - This filter may be automatically loaded during graph resolution. -@@ -19,17 +19,22 @@ Example - ``` - gpac -i src -o rtp://localhost:1234/:ext=ts - ``` -+ - This will indicate that the RTP streamer expects a MPEG-2 TS mux as an input. - - # RTP Packets - - The RTP packets produced have a maximum payload set by the [mtu](#mtu) option (IP packet will be MTU + 40 bytes of IP+UDP+RTP headers). - The real-time scheduling algorithm works as follows: -+ - - first initialize the clock by: -- - computing the smallest timestamp for all input PIDs -- - mapping this media time to the system clock -+ -+ - computing the smallest timestamp for all input PIDs -+ - mapping this media time to the system clock -+ - - determine the earliest packet to send next on each input PID, adding [delay](#delay) if any - - finally compare the packet mapped timestamp _TS_ to the system clock _SC_. When _TS_ - _SC_ is less than [tt](#tt), the RTP packets for the source packet are sent -+ - - The filter does not check for RTCP timeout and will run until all input PIDs reach end of stream. - -diff --git a/docs/Filters/rtspout.md b/docs/Filters/rtspout.md -index e3c74c3a..3259390d 100644 ---- a/docs/Filters/rtspout.md -+++ b/docs/Filters/rtspout.md -@@ -1,6 +1,6 @@ - - --# RTSP Server -+# RTSP Server {:data-level="all"} - - Register name used to load filter: __rtspout__ - This filter may be automatically loaded during graph resolution. -@@ -24,6 +24,7 @@ Example - gpac -i source -o rtsp://myip/sessionname - gpac -i source -o rtsp://myip/sessionname - ``` -+ - In this mode, only one session is possible. It is possible to [loop](#loop) the input source(s). - - # Server mode -@@ -32,18 +33,22 @@ The filter can work as a regular RTSP server by specifying the [mounts](#mounts) - Example - ``` - gpac rtspout:mounts=mydir1,mydir2 --``` -+``` -+ - In this case, content `RES` from any of the specified directory is exposed as `rtsp://SERVER/RES` - - The [mounts](#mounts) option can also specify access rule file(s), see `gpac -h creds`. When rules are used: -+ - - if a directory has a `name` rule, it will be used in the URL - - otherwise, the directory is directly available under server root `/` - - only read access and multicast rights are checked -+ - Example - ``` - [foodir] - name=bar - ``` -+ - Content `RES` of this directory is exposed as `rtsp://SERVER/bar/RES`. - - -@@ -54,6 +59,7 @@ Example - ``` - gpac -i rtsp://localhost/?pipe://mynamepipe&myfile.mp4 [dst filters] - ``` -+ - The server will resolve this URL in a new session containing streams from `myfile.mp4` and streams from pipe `mynamepipe`. - When setting [runfor](#runfor) in server mode, the server will exit at the end of the last session being closed. - -@@ -109,16 +115,18 @@ The tunnel conforms to QT specification, and only HTTP 1.0 and 1.1 tunnels are s - __loop__ (bool, default: _true_): loop all streams in session (not always possible depending on source type) - __dynurl__ (bool, default: _false_): allow dynamic service assembly - __mcast__ (enum, default: _off_): control multicast setup of a session --* off: clients are never allowed to create a multicast --* on: clients can create multicast sessions --* mirror: clients can create a multicast session. Any later request to the same URL will use that multicast session -+ -+- off: clients are never allowed to create a multicast -+- on: clients can create multicast sessions -+- mirror: clients can create a multicast session. Any later request to the same URL will use that multicast session - - __quit__ (bool, default: _false_): exit server once first session is over (for test purposes) - __htun__ (bool, default: _true_): enable RTSP over HTTP tunnel - __trp__ (enum, default: _both_): transport mode --* both: allow TCP or UDP traffic --* udp: only allow UDP traffic --* tcp: only allow TCP traffic -+ -+- both: allow TCP or UDP traffic -+- udp: only allow UDP traffic -+- tcp: only allow TCP traffic - - __cert__ (str): certificate file in PEM format to use for TLS mode - __pkey__ (str): private key file in PEM format to use for TLS mode -diff --git a/docs/Filters/safdmx.md b/docs/Filters/safdmx.md -index 0c44ef6f..5a337b2a 100644 ---- a/docs/Filters/safdmx.md -+++ b/docs/Filters/safdmx.md -@@ -1,6 +1,6 @@ - - --# SAF demultiplexer -+# SAF demultiplexer {:data-level="all"} - - Register name used to load filter: __safdmx__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/scte35dec.md b/docs/Filters/scte35dec.md -index 3708fcfb..abc3b4ba 100644 ---- a/docs/Filters/scte35dec.md -+++ b/docs/Filters/scte35dec.md -@@ -1,6 +1,6 @@ - - --# SCTE35 decoder -+# SCTE35 decoder {:data-level="all"} - - Register name used to load filter: __scte35dec__ - This filter is not checked during graph resolution and needs explicit loading. -@@ -12,5 +12,10 @@ following segmentation as hinted by the graph. - - # Options - -+__mode__ (enum, default: _23001-18_): mode to operate in -+ -+- 23001-18: extract SCTE-35 markers as emib/emeb boxes for Event Tracks -+- passthrough: pass-through mode adding cue start property on splice points -+ - __segdur__ (frac, default: _1/1_): segmentation duration in seconds. 0/0 flushes immediately for each input packet (beware of the bitrate overhead) - -diff --git a/docs/Filters/sockin.md b/docs/Filters/sockin.md -index bdc1a47c..701444a8 100644 ---- a/docs/Filters/sockin.md -+++ b/docs/Filters/sockin.md -@@ -1,6 +1,6 @@ - - --# UDP/TCP input -+# UDP/TCP input {:data-level="all"} - - Register name used to load filter: __sockin__ - This filter may be automatically loaded during graph resolution. -@@ -9,18 +9,26 @@ This filter handles generic TCP and UDP input sockets. It can also probe for MPE - - Data format can be specified by setting either [ext](#ext) or [mime](#mime) options. If not set, the format will be guessed by probing the first data packet - -+ - - UDP sockets are used for source URLs formatted as `udp://NAME` - - TCP sockets are used for source URLs formatted as `tcp://NAME` - - UDP unix domain sockets are used for source URLs formatted as `udpu://NAME` - - TCP unix domain sockets are used for source URLs formatted as `tcpu://NAME` -+ - - When ports are specified in the URL and the default option separators are used (see `gpac -h doc`), the URL must either: -+ - - have a trailing '/', e.g. `udp://localhost:1234/[:opts]` - - use `gpac` separator, e.g. `udp://localhost:1234[:gpac:opts]` -+ - - When the socket is listening in keep-alive [ka](#ka) mode: -+ - - a single connection is allowed and a single output PID will be produced - - each connection close event will triger a pipeline flush -+ -+ -+On OSX with VM packet replay you will need to force multicast routing, e.g. `route add -net 239.255.1.4/32 -interface vboxnet0` - - - # Options -diff --git a/docs/Filters/sockout.md b/docs/Filters/sockout.md -index 401fba74..a31356de 100644 ---- a/docs/Filters/sockout.md -+++ b/docs/Filters/sockout.md -@@ -1,6 +1,6 @@ - - --# UDP/TCP output -+# UDP/TCP output {:data-level="all"} - - Register name used to load filter: __sockout__ - This filter may be automatically loaded during graph resolution. -@@ -11,14 +11,18 @@ In server mode, the filter can be instructed to keep running at the end of the s - In server mode, the default behavior is to keep input packets when no more clients are connected; this can be adjusted though the [kp](#kp) option, however there is no realtime regulation of how fast packets are dropped. - If your sources are not real time, consider adding a real-time scheduler in the chain (cf reframer filter), or set the send [rate](#rate) option. - -+ - - UDP sockets are used for destinations URLs formatted as `udp://NAME` - - TCP sockets are used for destinations URLs formatted as `tcp://NAME` - - UDP unix domain sockets are used for destinations URLs formatted as `udpu://NAME` - - TCP unix domain sockets are used for destinations URLs formatted as `tcpu://NAME` -+ - - When ports are specified in the URL and the default option separators are used (see `gpac -h doc`), the URL must either: -+ - - have a trailing '/', e.g. `udp://localhost:1234/[:opts]` - - use `gpac` escape, e.g. `udp://localhost:1234[:gpac:opts]` -+ - - The socket output can be configured to drop or revert packet order for test purposes. - A window size in packets is specified as the drop/revert fraction denominator, and the index of the packet to drop/revert is given as the numerator/ -@@ -26,12 +30,14 @@ If the numerator is 0, a packet is randomly chosen in that window. - Example - ``` - :pckd=4/10 --``` -+``` -+ - This drops every 4th packet of each 10 packet window. - Example - ``` - :pckr=0/100 - ``` -+ - This reverts the send order of one random packet in each 100 packet window. - - -diff --git a/docs/Filters/svgplay.md b/docs/Filters/svgplay.md -index 261b50aa..b5c5098b 100644 ---- a/docs/Filters/svgplay.md -+++ b/docs/Filters/svgplay.md -@@ -1,6 +1,6 @@ - - --# SVG loader -+# SVG loader {:data-level="all"} - - Register name used to load filter: __svgplay__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/theoradec.md b/docs/Filters/theoradec.md -index 64fa9f2f..de053436 100644 ---- a/docs/Filters/theoradec.md -+++ b/docs/Filters/theoradec.md -@@ -1,6 +1,6 @@ - - --# Theora decoder -+# Theora decoder {:data-level="all"} - - Register name used to load filter: __theoradec__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/thumbs.md b/docs/Filters/thumbs.md -index 4441f925..46894f30 100644 ---- a/docs/Filters/thumbs.md -+++ b/docs/Filters/thumbs.md -@@ -1,9 +1,9 @@ - - --# Thumbnail collection generator -+# Thumbnail collection generator {:data-level="all"} - - Register name used to load filter: __thumbs__ --This is a JavaScript filter, not checked during graph resolution and needs explicit loading. -+This is a JavaScript filter. It is not checked during graph resolution and needs explicit loading. - Author: GPAC team - - This filter generates screenshots from a video stream. -@@ -21,27 +21,31 @@ If a single image per output frame is used, the default value for [snap](#snap) - Otherwise, the default value for [snap](#snap) is 1 second and for [scale](#scale) is 10. - - A single line of text can be inserted over each frame. Predefined keywords can be used in input text, identified as `$KEYWORD$`: --* ts: replaced by packet timestamp --* timescale: replaced by PID timescale --* time: replaced by packet time as HH:MM:SS.ms --* cpu: replaced by current CPU usage of process --* mem: replaced by current memory usage of process --* version: replaced by GPAC version --* fversion: replaced by GPAC full version --* mae: replaced by Mean Absolute Error with previous frame --* mse: replaced by Mean Square Error with previous frame --* P4CC, PropName: replaced by corresponding PID property -+ -+- ts: replaced by packet timestamp -+- timescale: replaced by PID timescale -+- time: replaced by packet time as HH:MM:SS.ms -+- cpu: replaced by current CPU usage of process -+- mem: replaced by current memory usage of process -+- version: replaced by GPAC version -+- fversion: replaced by GPAC full version -+- mae: replaced by Mean Absolute Error with previous frame -+- mse: replaced by Mean Square Error with previous frame -+- P4CC, PropName: replaced by corresponding PID property -+ - - Example - ``` - gpac -i src reframer:saps=1 thumbs:snap=30:grid=6x30 -o dump/$num$.png --``` -+``` -+ - This will generate images from key-frames only, inserting one image every 30 seconds. Using key-frame filtering is much faster but may give unexpected results if there are not enough key-frames in the source. - - Example - ``` - gpac -i src thumbs:snap=0:grid=5x5 -o dump/$num$.png - ``` -+ - This will generate one image containing 25 frames every second at 25 fps. - - If a single image per output frame is used and the scaling factor is 1, the input packet is reused as input with text and graphics overlaid. -@@ -50,6 +54,7 @@ Example - ``` - gpac -i src thumbs:grid=1x1:txt='Frame $time$' -o dump/$num$.png - ``` -+ - This will inject text over each frame and keep timing and other packet properties. - - A json output can be specified in input [list](#list) to let applications retrieve frame position in output image from its timing. -@@ -63,8 +68,10 @@ If both [mae](#mae) and [mse](#mse) thresholds are not 0, the frame is added if - For both metrics, a value of 0 means all pixels are the same, a value of 100 means all pixels have 100% intensity difference (e.g. black versus white). - - The scene detection is performed after the [snap](#snap) filtering and uses: -+ - - the previous frame in the stream, whether it was added or not, if [scref](#scref) is not set, - - the last added frame otherwise. -+ - - Typical thresholds for scene cut detection are 14 to 20 for [mae](#mae) and 5 to 7 for [mse](#mse). - -diff --git a/docs/Filters/tileagg.md b/docs/Filters/tileagg.md -index 9f2cb7dc..1d8c07ed 100644 ---- a/docs/Filters/tileagg.md -+++ b/docs/Filters/tileagg.md -@@ -1,6 +1,6 @@ - - --# HEVC tile aggregator -+# HEVC tile aggregator {:data-level="all"} - - Register name used to load filter: __tileagg__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/tilesplit.md b/docs/Filters/tilesplit.md -index 5fab36c7..ef4e934c 100644 ---- a/docs/Filters/tilesplit.md -+++ b/docs/Filters/tilesplit.md -@@ -1,6 +1,6 @@ - - --# HEVC tile bitstream splitter -+# HEVC tile bitstream splitter {:data-level="all"} - - Register name used to load filter: __tilesplit__ - This filter is not checked during graph resolution and needs explicit loading. -@@ -10,8 +10,10 @@ The filter will move to passthrough mode if the bitstream is not tiled. - If the `Bitrate` property is set on the input PID, the output tile PIDs will have a bitrate set to `(Bitrate - 10k)/nb_opids`, 10 kbps being reserved for the base. - - Each tile PID will be assigned the following properties: --* `ID`: equal to the base PID ID (same as input) plus the 1-based index of the tile in raster scan order. --* `TileID`: equal to the 1-based index of the tile in raster scan order. -+ -+- `ID`: equal to the base PID ID (same as input) plus the 1-based index of the tile in raster scan order. -+- `TileID`: equal to the 1-based index of the tile in raster scan order. -+ - - __Warning: The filter does not check if tiles are independently-coded (MCTS) !__ - -diff --git a/docs/Filters/tssplit.md b/docs/Filters/tssplit.md -index 21cd73d0..adc3da25 100644 ---- a/docs/Filters/tssplit.md -+++ b/docs/Filters/tssplit.md -@@ -1,6 +1,6 @@ - - --# MPEG Transport Stream splitter -+# MPEG Transport Stream splitter {:data-level="all"} - - Register name used to load filter: __tssplit__ - This filter is not checked during graph resolution and needs explicit loading. -diff --git a/docs/Filters/ttml2srt.md b/docs/Filters/ttml2srt.md -index 41d05316..a20fbf15 100644 ---- a/docs/Filters/ttml2srt.md -+++ b/docs/Filters/ttml2srt.md -@@ -1,6 +1,6 @@ - - --# TTML to SRT -+# TTML to SRT {:data-level="all"} - - Register name used to load filter: __ttml2srt__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/ttml2vtt.md b/docs/Filters/ttml2vtt.md -index e8cff790..49d5db9b 100644 ---- a/docs/Filters/ttml2vtt.md -+++ b/docs/Filters/ttml2vtt.md -@@ -1,6 +1,6 @@ - - --# TTML to WebVTT -+# TTML to WebVTT {:data-level="all"} - - Register name used to load filter: __ttml2vtt__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/ttmldec.md b/docs/Filters/ttmldec.md -index a531f92f..88cb1b85 100644 ---- a/docs/Filters/ttmldec.md -+++ b/docs/Filters/ttmldec.md -@@ -1,6 +1,6 @@ - - --# TTML decoder -+# TTML decoder {:data-level="all"} - - Register name used to load filter: __ttmldec__ - This filter may be automatically loaded during graph resolution. -@@ -10,9 +10,11 @@ The scene graph creation is done through JavaScript. - The filter options are used to override the JS global variables of the TTML renderer. - - In stand-alone rendering (no associated video), the filter will use: -+ - - `Width` and `Height` properties of input pid if any - - otherwise, `osize` option of compositor if set - - otherwise, [txtw](#txtw) and [txth](#txth) -+ - - - # Options -@@ -22,9 +24,10 @@ In stand-alone rendering (no associated video), the filter will use: - __fontSize__ (flt, default: _20_, updatable): font size - __color__ (str, default: _white_, updatable): text color - __valign__ (enum, default: _bottom_, updatable): vertical alignment --* bottom: align text at bottom of text area --* center: align text at center of text area --* top: align text at top of text area -+ -+- bottom: align text at bottom of text area -+- center: align text at center of text area -+- top: align text at top of text area - - __lineSpacing__ (flt, default: _1.0_, updatable): line spacing as scaling factor to font size - __txtw__ (uint, default: _400_): default width in standalone rendering -diff --git a/docs/Filters/ttmlmerge.md b/docs/Filters/ttmlmerge.md -new file mode 100644 -index 00000000..5d0341ac ---- /dev/null -+++ b/docs/Filters/ttmlmerge.md -@@ -0,0 +1,11 @@ -+ -+ -+# TTML sample merger {:data-level="all"} -+ -+Register name used to load filter: __ttmlmerge__ -+This filter may be automatically loaded during graph resolution. -+ -+Merge input samples into a single TTML sample. Merging restarts at the start of DASH segments. -+ -+No options -+ -diff --git a/docs/Filters/ttxtdec.md b/docs/Filters/ttxtdec.md -index 7e905b40..04e090a4 100644 ---- a/docs/Filters/ttxtdec.md -+++ b/docs/Filters/ttxtdec.md -@@ -1,6 +1,6 @@ - - --# TTXT/TX3G decoder -+# TTXT/TX3G decoder {:data-level="all"} - - Register name used to load filter: __ttxtdec__ - This filter may be automatically loaded during graph resolution. -@@ -9,9 +9,11 @@ This filter decodes TTXT/TX3G streams into a BIFS scene graph of the compositor - The TTXT documentation is available at https://wiki.gpac.io/xmlformats/TTXT-Format-Documentation - - In stand-alone rendering (no associated video), the filter will use: -+ - - `Width` and `Height` properties of input pid if any - - otherwise, `osize` option of compositor if set - - otherwise, [txtw](#txtw) and [txth](#txth) -+ - - - # Options -diff --git a/docs/Filters/tx3g2srt.md b/docs/Filters/tx3g2srt.md -index a35e783b..33c5b5e2 100644 ---- a/docs/Filters/tx3g2srt.md -+++ b/docs/Filters/tx3g2srt.md -@@ -1,6 +1,6 @@ - - --# TX3G to SRT -+# TX3G to SRT {:data-level="all"} - - Register name used to load filter: __tx3g2srt__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/tx3g2ttml.md b/docs/Filters/tx3g2ttml.md -index 696245bb..d0cc1676 100644 ---- a/docs/Filters/tx3g2ttml.md -+++ b/docs/Filters/tx3g2ttml.md -@@ -1,6 +1,6 @@ - - --# TX3G to TTML -+# TX3G to TTML {:data-level="all"} - - Register name used to load filter: __tx3g2ttml__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/tx3g2vtt.md b/docs/Filters/tx3g2vtt.md -index 68a491da..0843b721 100644 ---- a/docs/Filters/tx3g2vtt.md -+++ b/docs/Filters/tx3g2vtt.md -@@ -1,6 +1,6 @@ - - --# TX3G to WebVTT -+# TX3G to WebVTT {:data-level="all"} - - Register name used to load filter: __tx3g2vtt__ - This filter may be automatically loaded during graph resolution. -diff --git a/docs/Filters/txtin.md b/docs/Filters/txtin.md -index 12d0497e..950cc1b2 100644 ---- a/docs/Filters/txtin.md -+++ b/docs/Filters/txtin.md -@@ -1,24 +1,29 @@ - - --# Subtitle loader -+# Subtitle loader {:data-level="all"} - - Register name used to load filter: __txtin__ - This filter may be automatically loaded during graph resolution. - - This filter reads subtitle data from input PID to produce subtitle frames on a single PID. - The filter supports the following formats: --* SRT: https://en.wikipedia.org/wiki/SubRip --* WebVTT: https://www.w3.org/TR/webvtt1/ --* TTXT: https://wiki.gpac.io/xmlformats/TTXT-Format-Documentation --* QT 3GPP Text XML (TexML): Apple QT6, likely deprecated --* TTML: https://www.w3.org/TR/ttml2/ --* SUB: one subtitle per line formatted as `{start_frame}{end_frame}text` --* SSA (Substation Alpha): basic parsing support for common files -+ -+- SRT: https://en.wikipedia.org/wiki/SubRip -+- WebVTT: https://www.w3.org/TR/webvtt1/ -+- TTXT: https://wiki.gpac.io/xmlformats/TTXT-Format-Documentation -+- QT 3GPP Text XML (TexML): Apple QT6, likely deprecated -+- TTML: https://www.w3.org/TR/ttml2/ -+- SUB: one subtitle per line formatted as `{start_frame}{end_frame}text` -+- SSA (Substation Alpha): basic parsing support for common files -+ - - Input files must be in UTF-8 or UTF-16 format, with or without BOM. The internal frame format is: --* WebVTT (and srt if desired): ISO/IEC 14496-30 VTT cues --* TTML: ISO/IEC 14496-30 XML subtitles --* Others: 3GPP/QT Timed Text -+ -+- WebVTT (and srt if desired): ISO/IEC 14496-30 VTT cues -+- TTML: ISO/IEC 14496-30 XML subtitles -+- stxt and sbtt: ISO/IEC 14496-30 text stream and text subtitles -+- Others: 3GPP/QT Timed Text -+ - - # TTML Support - -@@ -26,17 +31,21 @@ If [ttml_split](#ttml_split) option is set, the TTML document is split in indepe - Empty periods in TTML will result in empty TTML documents or will be skipped if [no_empty](#no_empty) option is set. - - The first sample has a CTS assigned as indicated by [ttml_cts](#ttml_cts): -+ - - a numerator of -2 indicates the first CTS is 0 - - a numerator of -1 indicates the first CTS is the first active time in document - - a numerator >= 0 indicates the CTS to use for first sample -+ - - When TTML splitting is disabled, the duration of the TTML sample is given by [ttml_dur](#ttml_dur) if not 0, or set to the document duration - - By default, media resources are kept as declared in TTML2 documents. - - [ttml_embed](#ttml_embed) can be used to embed inside the TTML sample the resources in `` or ``: -+ - - for ``, ``, `