gaspard7/wiki_gpac huggingface dataset:
Encoding {: data-level="beginner" }

Encode an MP3 to an AAC file at 100 kbps - _see [filter](ffenc)_  
```
gpac -i source.mp3 -o dst.aac:b=100k
```

Encode an MP3 to AAC in MP4 file at 100 kbps - _see [filter](ffenc)_  
```
gpac -i source.mp3 enc:c=aac:b=100k -o dst.mp4
```

Transcode to AAC at 100 kbps and AVC at 1m, intra every 2s - _see [filter](ffenc)_  
```
gpac -i source enc:c=aac:b=100k:FID=1 enc:c=avc:b=1m:fintra=2:FID=2 -o dst.mp4:SID=1,2
```

Rescale video and encode to AVC at 1m, leaving other streams if any unmodified - _see [filter](ffenc)_  
```
gpac -i source ffsws:osize=320x240 enc:c=avc:b=1m -o dst.mp4
```

Rescale YUV video and encode to AVC at 1m - _see [filter](ffenc)_  
```
gpac -i source.yuv:size=1280x720 ffsws:osize=320x240 enc:c=avc:b=1m -o dst.mp4
```

Resample audio to 48khz - _see [filter](ffenc)_  
```
gpac -i source resample:sr=48k -o dump.pcm
gpac -i source.pcm:sr=44100:ch=1 resample:sr=48k -o dump.pcm
```

Setting FFmpeg and x264 options - _see [filter](ffenc)_  
```
gpac -i vid.mp4 enc:c=avc:b=2m:fintra=2:vprofile=baseline:preset=ultrafast:tune=zerolatency::x264-params=no-mbtree:sliced-threads:sync-lookahead=0 -o dst.mp4
```

Transcode to AVC 1mbps and aac 128k while monitoring video output and filter stats- _see [filter](ffenc)_  
```
gpac -i source enc:c=avc:b=1m enc:c=aac:b=128k -o dst.mp4 vout:vsync=0 -r
```

Downscale and transcode to AVC 1mbps and aac 128k while monitoring video output - _see [filter](ffenc)_  
```
gpac -i source ffsws:osize=256x144 enc:c=avc:b=1m enc:c=aac:b=128k -o dst.mp4 vout:vsync=0
```

Use two-pass encoding with FFmpeg (libx264 only for GPAC 2.0, all codecs for higher versions) - _see [filter](ffenc)_  
```
#first pass
gpac -i source enc:c=avc:b=3m:pass1 -o null
#second pass
gpac -i source enc:c=avc:b=3m:pass2 -o dst.mp4
```
Rescaling

rescale without respecting aspect ratio
```
gpac -i source1.mp4 ffsws:osize=512x512 vout
```

rescale respecting aspect ratio and pad color to red
```
gpac -i source1.mp4 ffsws:osize=512x512:keepar=full:padclr=red vout
```

rescale without respecting aspect ratio nut set output aspect ratio, output video will be 340x512 (`340=510*2/3` )
```
gpac -i source1.mp4 ffsws:osize=510x512:osr=3/2 vout
```
Encryption

Encrypt and dash several sources using a single drm configuration - _see [filter](cecrypt)_  
```
gpac -i source1.mp4 -i source2.aac cecrypt:cfile=drm.xml -o live.mpd
```

Encrypt and dash several sources using one drm configuration per source - _see [filter](cecrypt)_  
```
gpac -i source1.mp4:#CryptInfo=drm1.xml -i source2.aac:#CryptInfo=drm1.xml cecrypt -o live.mpd
```

perform pre-dashing before encryption for key roll at segment boundaries - _see [filter](cecrypt)_  
```
gpac -i source.mp4 dasher:gencues cecrypt:cfile=roll_seg.xml -o live.mpd
```
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)_  
```
gpac -i - @#video reframer:saps=1 -o stdout:ext=264
```

Grab a raw stream (e.g. YUV420) from stdin  and output as raw 264 over stdout - _see  [filter](fout)   [howto](pipes)_  
```
gpac -i stdin:ext=yuv:size=1280x720 -o stdout:ext=264
```


Grab a TS UDP stream and remux as fragmented mp4 over a named pipe - _see  [filter](pout)   [howto](pipes)_   
```
gpac -i udp://234.0.0.1:1234 -o pipe://mypipe:ext=mp4:mkp:frag
```

Mux source to real-time M2TS UDP multicast stream - _see  [filter](m2tsmx)   [howto](sockets)_   
```
gpac -i source.mp4 -o udp://234.0.0.1:1234/:ext=ts:realtime
```
DASH and HLS generation

See Howtos [DASH](DASH-intro),  [DASH Low Latency](LL-DASH) and [dasher](dasher) filter.

Generating a live profile, static MPD  
```
gpac -i source.mp4 -i source2.mp4  -o dash/live.mpd
```

Generating a onDemand profile, static MPD from a source  
```
gpac -i source.mp4 -i source2.mp4 -o dash/live.mpd:profile=onDemand
```

Generating a HLS with one file per segment  
```
gpac -i source.mp4 -i source2.mp4 -o dash/live.m3u8
```
 
Generating a HLS with single file from a source  
```
gpac -i source.mp4 -i source2.mp4 -o dash/live.m3u8:sfile
```

Generating a HLS and MPD with one file per segment  
```
gpac -i source.mp4 -i source2.mp4 -o dash/live.mpd:dual
```
 
Generating a HLS with single file and MPD from a source  
```
gpac -i source.mp4 -i source2.mp4 -o dash/live.mpd:dual:profile=onDemand
```

Generating a live profile, dynamic offering  
```
gpac -i source.mp4 -i source2.mp4 -o dash/live.mpd:dmode=dynamic
```

Generating a live profile, dynamic offering with time regulation at each segment  
```
gpac -i source.mp4 -i source2.mp4 -o dash/live.mpd:dmode=dynamic:sreg
```

Generating a live profile, low-latency, dynamic offering with time regulation at each frame  
```
gpac -i source.mp4 -i source2.mp4 reframer:rt=on -o dash/live.mpd:dmode=dynamic:segdur=1:cdur=0.1
```

Generating a live profile, low-latency, dynamic offering with time regulation at each frame and offering it as HTTP, with 1h timeshift  
```
gpac -i source.mp4 -i source2.mp4 reframer:rt=on -o http://127.0.0.1:8080/live.mpd:gpac:dmode=dynamic:segdur=1:cdur=0.1:rdirs=/tmp:tsb=3600
```

Looping two sources to simulate a live over http  
```
gpac --floop=-1 flist:srcs=source.mp4 flist:srcs=source2.mp4 reframer:rt=on -o http://127.0.0.1:8080/live.mpd:gpac:dmode=dynamic:segdur=1:cdur=0.1:rdirs=/tmp:tsb=3600
```

Live UTC counter single resolution over  HTTP in low latency  
```
gpac avgen enc:c=aac:b=100k:FID=1 enc:c=avc:b=2m:fintra=1:FID=2 -o http://127.0.0.1:8080/live.mpd:gpac:SID=1,2:dmode=dynamic:segdur=1:cdur=0.1:rdirs=/tmp
```

Live UTC counter 2 resolutions over  HTTP in low latency  
```
gpac avgen:sizes=1280x720,1920x1080:rates=2M,5M enc:c=aac:b=100k:FID=1 enc:c=avc:fintra=1:FID=2 -o http://127.0.0.1:8080/live.mpd:gpac:SID=1,2:dmode=dynamic:segdur=1:cdur=0.1:rdirs=/tmp
```

Generate trick mode from source1 and use both source1 A+V, source2 V and trick mode in dash:
```
gpac -i source1.mp4 @#video reframer:saps=1 -i source2.mp4 @#video @1 @@ -o dash/live.mpd
```

Insert custom DASH tags in manifest:
```
gpac -i source:#RDesc=<rep_desc>bar</rep_desc>:#ASCDesc=<as_desc>bar</as_desc>:#PDesc=<p_desc>bar</p_desc> -o manifest.mpd
```

Insert custom HLS tags in manifest (global tags use `hlsx` option and per-PID tags use `HLSMExt` property) and variant playlists (`HLSVExt` PID property):
```
gpac -i source::#HLSMExt=vfoo,vbar=video::#HLSVExt=#fooVideo,#bar1=optVideo -i source2::#HLSMExt=afoo,abar=audio-en::#HLSVExt=#fooAudio,#bar1=optAudio -o manifest.m3u8::hlsx=#SomeExt,#SomeOtherExt=true
```
Time modification

_Note: If the source is an MP4 file, it is much simpler/faster to perform these operations using MP4Box_

Add 200 ms delay to audio (audio later than other streams)  - _see [filter](restamp)_  
```
gpac -i source restamp:delay_a=2/10 -o dst
```

Add -1s delay to audio (audio earlier than other streams)  - _see [filter](restamp)_  
```
gpac -i source restamp:delay_a=-1 -o dst
```

Speedup all tracks except audio by 10% (speed 1.1) - _see [filter](restamp)_  
```
gpac -i source restamp:fps=-11/10 -o dst
```

Change video FPS to 29.97 with frame cloning - _see [filter](restamp)_  
```
gpac -i source restamp:fps=30000/1001:rawv=force -o dst
```
Source splitting {: data-level="beginner" }

Extract from 1min to 2min30s - _see [filter](restamp)_  
```
gpac -i source reframer:xs=T00:01:00:xe=T00:02:30 -o dst
```


Extract from 1min to 2min30s after decoding (sample-accurate audio) - _see [filter](restamp)_  
```
gpac -i source reframer:raw=av::xs=T00:01:00:xe=T00:02:30 -o dst
```


Extract from 1min to 2min30s and 5min20s to 6min - _see [filter](restamp)_  
```
gpac -i source reframer:xs=T00:01:00,T00:05:20:xe=T00:02:30,T00:06:00 -o dst
```

Extract from 5min20s to end - _see [filter](restamp)_  
```
gpac -i source reframer:xs=T00:05:20 -o dst
```

Extract from 1min to 2min30s and 5min20s to end - _see [filter](restamp)_  
```
gpac -i source reframer:xs=T00:01:00,T00:05:20:xe=T00:02:30 -o dst
```
Playlists and source concatenation {: data-level="beginner" }

Loop file forever playback  - _see [filter](flist)_  
```
gpac flist:srcs=source.mp4:floop=-1 aout vout
```

Read all PNGs from a directory, sorting by creation date  - _see [filter](flist)_  
```
gpac flist:srcs=src/*.png:fsort=date vout
```

Resize a set of videos with aspect ratio respected  - _see [filter](ffsws)_  
```
gpac flist:srcs=1.mp4,2.mp4,3.mp4 ffsws:osize=1280x720:keepar=full c=avc -o dst.mp4
```

Concatenate all MP4 from directory `dir`:
```
gpac flist:srcs=*.mp4 vout

#same as above but through a playlist
ls *.mp4 > pl.m3u
gpac -i pl.m3u vout
```
Playback { : data-level="beginner" }

Basic playback  - _see [howto](filters-playback)_  
```
gpac -play source
```

Video only playback  - _see [howto](filters-playback)_  
```
gpac -i source vout
```

Video only playback, no sync  - _see [howto](filters-playback)_  
```
gpac -i source vout:sync=0
```

Audio only playback  - _see [howto](filters-playback)_  
```
gpac -i source aout
```

Single-threaded GUI playback  - _see [howto](filters-playback)_  
```
gpac compositor:player=gui:src=source
gpac -threads=0 -gui source
```

Single-threaded compositor playback (available as alias `-mp4c`)  - _see [howto](filters-playback)_  
```
gpac compositor:player=base:src=source
gpac -threads=0 -mp4c source
```

__NOTE__ The `-mp4c` and `-gui` aliases will by default set `-threads=2`.
Thumbnails

Create thumbnails based on key frames in source, with at least 30 seconds between frames and dynamic thumbnail height
```
gpac -i src reframer:saps=1 thumbs:snap=30 -o dump_$num$.png
```

Create thumbnails based on key frames in source, with at least 30 seconds between frames in a 6x6 layout with 5x downscale and json generation
```
gpac -i src reframer:saps=1 thumbs:snap=30:grid=6x6:list=thumbs.json:scale=5 -o dump_$num$.png
```

Create thumbnails of scene cuts based on key frames in source
```
gpac -i src reframer:saps=1 thumbs:snap=0:mae=15 -o dump_$num$.png
```

Use thumbnailer to overlay frame time over video
```
gpac -i src thumbs:grid=1x1:txt='$time$' vout
```
FFmpeg support

Set gpac as RTMP output server  
```
gpac -i src.mp4 -o rtmp://127.0.0.1:8888/live:gpac:ffmt=flv:rtmp_listen=1
```

Read from RTMP server  
```
gpac -i rtmp://127.0.0.1:8888/live vout aout
```

Set gpac as RTMP input server, and use another instance to push 
```
gpac -i rtmp://127.0.0.1:8888/live:gpac:rtmp_listen=1 vout aout
gpac -i src.mp4 -o rtmp://127.0.0.1:8888/live:gpac:ffmt=flv
```


Read audio and video from device  
```
gpac -i av:// vout aout
```

Read video from device  
```
gpac -i video:// vout
```

Read audio from device  
```
gpac -i audio:// aout
```

read from screen on OSX  
```
gpac -i video://:fmt=avfoundation:dev=screen0 vout
```

read from screen on Linux
```
gpac -i video://:fmt=x11grab:dev=:0.0 vout
```

Read audio after filter from libavfilter (compressor here)  
```
gpac -i audio.mp4 ffavf:afmt=s16:sr=44100::f=acompressor aout
```

Read video after filter from libavfilter (negate here)  
```
gpac -i video.mp4 ffavf::f=negate vout
```

Use audio to video filter from libavfilter (showspectrum here)  
```
gpac -i audio.mp4 ffavf:pfmt=yuv::f=showspectrum=size=320x320 aout vout
```

Use 2 video to video filter from libavfilter (overlay here)
```
gpac -i video1.mp4:#ffid=a -i video2.mp4:#ffid=b ffavf:pfmt=yuv::f=[a][b]overlay=main_w-overlay_w-10:main_h-overlay_h-10 vout
```

Compute PSNR
```
gpac -i video.mp4:#ffid=dist -i ref.mp4:#ffid=ref ffavf::'f=[dist][ref]psnr=f=psnr.log' -o null
```

Compute SSIM
```
gpac -i video.mp4:#ffid=dist -i ref.mp4:#ffid=ref ffavf::'f=[dist][ref]ssim=f=ssim.log' -o null
```


Compute PSNR and SSIM
```
gpac -i video.mp4:#ffid=dist -i ref.mp4:#ffid=ref \
	@@ @@1 ffavf::'f=[dist][ref]psnr=f=psnr2.log' @ -o null \
	@@ @@1 ffavf::f='[dist][ref]ssim=f=ssim2.log' @ -o null
```

SRT session from local file (requires FFmpeg with SRT support)
```
#player listening for SRT handshake
gpac -play srt://127.0.0.1:1234:gpac:mode=listener
#streamer with real-time regulation
gpac -i video.mp4 reframer:rt=on -o srt://127.0.0.1:1234:gpac:ffmt=mpegts
```

SRT session from avgen filter (requires FFmpeg with SRT support)
```
#player listening for SRT handshake
gpac -play srt://127.0.0.1:1234:gpac:mode=listener
#streamer with real-time regulation
gpac avgen reframer:rt=on c=avc c=aac -o srt://127.0.0.1:1234:gpac:ffmt=mpegts
```
Overview {:data-level="all"}

We discuss here how to use [GPAC Filters](Filters) in Python. 

The GPAC Python API provides Python bindings to GPAC filter session. The design is closely inspired from the [JS FilterSession](jssession) API used in GPAC.
The GPAC Python API is [documented here](https://doxygen.gpac.io/group__pyapi__grp.html).

You can also have a look at the  [python scripts](https://github.com/gpac/testsuite/tree/filters/media/python) in the GPAC test suite.


__Warning__

GPAC Python bindings are only available starting from GPAC 2.0.
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.

You __must__:

- use the bindings which come along with your GPAC installation, otherwise ABI/API might mismatch, resulting in crashes.
- use a regular GPAC build, not a static library version (so python bindings are not compatible with mp4box-only build).
- make sure the libgpac shared library is known to your dynamic library loader.


The binding is called libgpac.py, and is by default available in GPAC share folder, for example `/usr/share/gpac/python`. It is hosted in GPAC [source tree](https://github.com/gpac/gpac/blob/master/share/python/libgpac.py).

The Python module handler is furthermore very likely not configured to look at this directory, and you will have to indicate where to look:

```
import sys
sys.path.append('/usr/share/gpac/python')
import libgpac
```

The GPAC python bindings will try to import [numpy](https://numpy.org/) by default to allow easy packet data access, but can run without it. Check everything is in place by printing GPAC version :


```
import sys
sys.path.append('/usr/share/gpac/python')
import libgpac as gpac

print("Welcome to GPAC Python !\nVersion: " + gpac.version)
```

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

The first thing to do is to initialize libgpac. This is done by default while importing the bindings with the following settings:

- no memory tracking
- default GPAC profile used

If you want to change these, you need to re-init libgpac right after import:

```
gpac.init(1, "customprofile")
```


Before starting any filter session, you may also need to pass some global configuration options to GPAC:
```
#first argument is ignored by libgpac
opts = ["myapp"];
#for example, blacklist some filters
opts.append("-blacklist=filters_you_do_not_want")
opts.append("-no-block"")
gpac.set_args(opts)
```

You can also pass the command line arguments so that you can specify GPAC core and global filter options at prompt:
```
gpac.set_args(sys.argv)
```

You may want to adjust the log tools and levels of GPAC:
```
gpac.set_logs("dash@info")
```
Simple sessions

To create a filter session, the simplest way is to use all defaults value, creating a single-threaded blocking session:

```
fs = gpac.FilterSession()
```

You can then add your filters as usual. 

Playback example:

```
f1 = fs.load_src("file.mp4")
f2 = fs.load("vout")
fs.run()
```

Remux example:

```
f1 = fs.load_src("file.mp4")
f2 = fs.load_dst("test.ts")
fs.run()
```

Once you are done, you must explicitly destroy the filter session and uninit libgpac to cleanup all resources. 

```
fs.delete()
gpac.close()
```
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.

```
fs = gpac.FilterSession(gpac.GF_FS_FLAG_NON_BLOCKING)
f1 = fs.load_src("file.mp4")
f2 = fs.load("vout")
while True:
	#do things

	#call session
	fs.run()

	#if last task, GPAC session is done
	if fs.last_task:
		break
```
Callbacks in blocking sessions

Alternatively, you may run the session in blocking mode, and ask for being called back once or on regular basis. This is achieved by posting tasks to the GPAC session scheduler. A task object shall provide an `execute` method to be called. The default `execute` method does nothing. You can either derive an object from the `FilterTask` class, or override the `execute` function of the created task:

```

#custom task callback 
#- returns -1: indicates the task is done, it will no longer get called and will be destroyed
#- returns >=0 (None handled as 0): indicates the task is still active and should be
#called again in the returned number of milliseconds
#
def my_exec(self):
	if (self.session.last_task):
		print("No more tasks, we are done")
		return -1
	print("In callback, nb filters: " + str(self.session.nb_filters))
	return 500

#create the custom task
task = gpac.FilterTask('testtask')
task.execute = types.MethodType(my_exec, task)
task.count=0
fs.post(task)

#run as usual
```
  
Tasks can be created at any time, either at the beginning or in a callback function (e.g., another task).

Tasks can be created in non-blocking sessions; in this case their execution timing will depend on the frequency at which your application calls `fs.run()`.
Linking filters

In order to link filters when desired, you must explicitly do this using `set_source` of the destination filter. For example, when inserting a [reframer](reframer) in a chain:

```
f_src = fs.load_src('source.mp4')
f_dst = fs.load_dst('remux.mp4')
f_reframe = fs.load('reframer')
f_dst.set_source(reframer)
```

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.
Inspecting filters

You can query the number of input and output PIDs of a filter, the source filter of an input PID, the destination filters of an output PID, their possible options, update options, send events, ...

Please check the  [API documentation](https://doxygen.gpac.io/classlibgpac_1_1_filter.html) and refer to the [python scripts](https://github.com/gpac/testsuite/tree/filters/media/python) in the GPAC test suite.

Note that some properties (as in GPAC JS) are not exposed as their native types but as string (see [here](https://doxygen.gpac.io/group__pyapi__grp.html#details)). This is the case for these important types:

- StreamType: string containing the streamtype name
- CodecID: string containing the codec name
- PixelFormat: string containing the pixel format name
- AudioFormat: string containing the audio format name
Custom Sink example

The following defines a custom filter doing simple inspection of the pipeline (sink filter) 
```
#define a custom filter
class MyFilter(gpac.FilterCustom):
	def __init__(self, session):
		gpac.FilterCustom.__init__(self, session, "PYnspect")
		#indicate what we accept and produce - this can be done either in the constructor or after, but before running the session
		#here we only accept video streams as input, and do not produce any output
		self.push_cap("StreamType", "Visual", gpac.GF_CAPS_INPUT)

	#callbacks must be defined before instantiating an object from this class

	#we accept one or more input video PID, we must configure them
	def configure_pid(self, pid, is_remove):
		if is_remove:
			return 0
		#PID is already registered with our filter, this is a reconfiguration
		if pid in self.ipids:
			print('PID reconfigured')
		#otherwise this is our first configure
		else:
			print('PID configured - props:')
			
			#enumerate all props using ourselves as the callback, getting called back in `on_prop_enum` below
			pid.enum_props(self)
			#we are a sink, we MUST fire a play event
			evt = gpac.FilterEvent(gpac.GF_FEVT_PLAY)
			pid.send_event(evt)
		return 0

	#process
	def process(self):
		for pid in self.ipids:
			pck = pid.get_packet()
			if pck==None:
				break


			print('Got Packet DTS ' + str(pck.dts) + ' CTS ' + str(pck.cts) + ' SAP ' + str(pck.sap) + ' dur ' + str(pck.dur) + ' size ' + str(pck.size))

			pid.drop_packet()
		return 0

	def on_prop_enum(self, pname, pval):
		print('Property ' + pname + ' value: ' + str(pval))

#load a source
my_src = fs.load_src("source.mp4")

#load a custom filter
my_filter = MyFilter(fs)

#if needed, setup links between filters (in this example, only 2 filters explicitly loaded, no need for links)

#run the session
fs.run()


```
GPU Decoders and OpenGL

You can get access to the GPU textures (when present) of a packet for later reuse in OpenGL+Python.
When fetching a packet, usage of GPU textures is signaled by the `frame_ifce_gl` property of the packet.

First you must delegate all GL context management to your python app (must be done before loading any filter), otherwise some filter may create an alternate context:
```
    fs = gpac.FilterSession(gpac.GF_FS_FLAG_NON_BLOCKING, "")
    fs.external_opengl_provider()
```

If you run the session in multithreaded mode, you may need to override the filter session `on_gl_activate` to properly activate the GL context for the calling thread.

A typical packet processing will then be:

- if GPU texture
	- use `get_gl_texture` for each video plane, typically 3 for YUV, 2 for Y+packed YV (nv12), 1 for RGB/RGBA
	- set active texture units and uniforms using the textureID returned
- otherwise
	- if packet is a frame interface, clone packet to fetch data otherwise use source packet data
	- push data to GPU according to the format

The following illustrates how to get texture IDs
```
pck = pid.get_packet()
if pck.frame_ifce_gl:
	tx = pck.get_gl_texture(0)
	texture1 = tx.id
	#only needed at first frame fetch if same texture ID - there is no guarantee that the source (decoder) will always use the same texture IDs
	glBindTexture(GL_TEXTURE_2D, texture1)
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)

	#YUV or NV12
	if nb_textures>1:
		tx = pck.get_gl_texture(1)
		texture2 = tx.id
		glBindTexture(GL_TEXTURE_2D, texture2)
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)

		#YUV 
		if nb_textures>2:
			tx = pck.get_gl_texture(2)
			texture3 = tx.id
			if reset:
				glBindTexture(GL_TEXTURE_2D, texture3)
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
else:
	data=pck.data
	if pck.frame_ifce:
		#cf discussion in previous example
		clone=pck.clone()
		data = clone.data
	#push data or parts of data to appropriate textures using glTexImage2D & co
	
```

And let your OpenGL skills do the rest !

A simple example illustrating this is available [here](examples/python/pygl_gpac.py), using input file "video.mp4" and PyGame + OpenGL.
Remotery interaction

GPAC is by default compiled with [Remotery](https://github.com/Celtoys/Remotery) support for remote profiling. 
You can interact with Remotery websocket server by sending messages to the remote browser, or receiving messages from it:

```
class MyRemotery:
	def on_rmt_event(self, text):
		print('Remotery got message ' + text)
		gpac.rmt_send('Some response text')

my_rmt = MyRemotery()
gpac.set_rmt_fun(my_rmt)

```

You will need to enable Remotery in GPAC by setting the option `-rmt`, as this cannot be enabled or disabled at run time.

You can however enable or disable Remotery profiler using `gpac.rmt_enable(True/False)`.