Examples#
Note
The Sender and Receiver examples both make use of UNIX pipes and will only work on supported operating systems.
Sender#
Frames are generated by ffmpeg running in a subprocess which outputs the raw
data to its stdout.
A Sender is created and an instance of
VideoSendFrame is added to it.
The raw data from ffmpeg is then fed using the Sender’s
write_video_async() method.
1from __future__ import annotations
2
3from typing import NamedTuple, Generator, Any, cast
4from typing_extensions import Self
5import enum
6import io
7import subprocess
8import shlex
9from fractions import Fraction
10from contextlib import contextmanager
11
12import click
13
14from cyndilib.wrapper.ndi_structs import FourCC
15from cyndilib.video_frame import VideoSendFrame
16from cyndilib.sender import Sender
17
18
19FF_CMD = '{ffmpeg} -f lavfi -i testsrc2=size={xres}x{yres}:rate={fps} \
20 -pix_fmt {pix_fmt.name} -f rawvideo pipe: '
21
22
23
24class PixFmt(enum.Enum):
25 """Maps ffmpeg's ``pix_fmt`` names to their corresponding
26 :class:`FourCC <cyndilib.wrapper.ndi_structs.FourCC>` types
27 """
28 uyvy422 = FourCC.UYVY #: uyvy422
29 nv12 = FourCC.NV12 #: nv12
30 rgba = FourCC.RGBA #: rgba
31 bgra = FourCC.BGRA #: bgra
32
33 @classmethod
34 def from_str(cls, name: str) -> Self:
35 return cls.__members__[name]
36
37
38class Options(NamedTuple):
39 """Options set through the cli
40 """
41 pix_fmt: PixFmt #: Pixel format to send
42 xres: int #: Horizontal resolution
43 yres: int #: Vertical resolution
44 fps: str #: Frame rate
45 sender_name: str = 'ffmpeg_sender' #: NDI name for the sender
46 ffmpeg: str = 'ffmpeg' #: Name/Path of the "ffmpeg" executable
47
48
49def parse_frame_rate(fr: str) -> Fraction:
50 """Helper for NTSC frame rates (29.97, 59.94)
51 """
52 if '/' in fr:
53 n, d = [int(s) for s in fr.split('/')]
54 elif '.' in fr:
55 n = round(float(fr)) * 1000
56 d = 1001
57 else:
58 n = int(fr)
59 d = 1
60 return Fraction(n, d)
61
62
63@contextmanager
64def ffmpeg_proc(opts: Options) -> Generator[subprocess.Popen[bytes], Any, None]:
65 """Context manager for the ffmpeg subprocess generating frames
66 """
67 ff_cmd = FF_CMD.format(**opts._asdict())
68 ff_proc = subprocess.Popen(shlex.split(ff_cmd), stdout=subprocess.PIPE)
69 try:
70 ff_proc.poll()
71 if ff_proc.returncode is None:
72 yield ff_proc
73 finally:
74 ff_proc.kill()
75
76
77def send(opts: Options) -> None:
78 """Send frames generated by ffmpeg's ``testsrc2`` as an |NDI| stream
79
80 The raw frames generated by ffmpeg are sent to a pipe and read from its
81 :attr:`~subprocess.Popen.stdout`. The data is then fed directly to
82 :meth:`cyndilib.sender.Sender.write_video_async` using an
83 intermediate :class:`memoryview`
84 """
85 sender = Sender(opts.sender_name)
86
87 # Build a VideoSendFrame and set its resolution and frame rate
88 # to match the options argument
89 vf = VideoSendFrame()
90 vf.set_resolution(opts.xres, opts.yres)
91 fr = parse_frame_rate(opts.fps)
92 vf.set_frame_rate(fr)
93 vf.set_fourcc(opts.pix_fmt.value)
94
95 # Add the VideoSendFrame to the sender
96 sender.set_video_frame(vf)
97
98 # Pre-allocate a bytearray to hold frame data and create a view of it
99 # So we can buffer into it from ffmpeg then pass directly to the sender
100 frame_size_bytes = vf.get_data_size()
101 ba = bytearray(frame_size_bytes)
102 mv = memoryview(ba)
103
104 i = 0
105
106 with sender:
107 with ffmpeg_proc(opts) as ff_proc:
108 stdout = cast(io.BytesIO, ff_proc.stdout)
109 while True:
110 if ff_proc.returncode is not None:
111 break
112 # Read from the ffmpeg process into a view of the bytearray
113 num_read = stdout.readinto(mv)
114
115 # The first few reads might be empty, ignore
116 if num_read == 0:
117 continue
118
119 # Pass the memoryview directly to the sender
120 # (using the buffer protocol)
121 sender.write_video_async(mv)
122
123 i += 1
124 if i % 10 == 0:
125 ff_proc.poll()
126
127
128
129@click.command()
130@click.option(
131 '--pix-fmt',
132 type=click.Choice(choices=[m.name for m in PixFmt]),
133 default=PixFmt.uyvy422.name,
134 show_default=True,
135 show_choices=True,
136)
137@click.option('-x', '--x-res', type=int, default=1920, show_default=True)
138@click.option('-y', '--y-res', type=int, default=1080, show_default=True)
139@click.option('--fps', type=str, default='30', show_default=True)
140@click.option(
141 '-n', '--sender-name',
142 type=str,
143 default='ffmpeg_sender',
144 show_default=True,
145 help='NDI name for the sender',
146)
147@click.option(
148 '--ffmpeg',
149 type=str,
150 default='ffmpeg',
151 show_default=True,
152 help='Name/Path of the "ffmpeg" executable',
153)
154def main(pix_fmt: str, x_res: int, y_res: int, fps: str, sender_name: str, ffmpeg: str):
155 opts = Options(
156 pix_fmt=PixFmt.from_str(pix_fmt),
157 xres=x_res,
158 yres=y_res,
159 fps=fps,
160 sender_name=sender_name,
161 ffmpeg=ffmpeg,
162 )
163 send(opts)
164
165
166if __name__ == '__main__':
167 main()
Audio Sender#
This example sends audio frames using a sine wave generated by numpy. The video frames are blank frames generated manually.
A Sender is created and AudioSendFrame and
VideoSendFrame instances are added to it.
The audio and video data are then written using the Sender’s
write_video_and_audio() method.
1from __future__ import annotations
2from typing import NamedTuple, Generator
3from fractions import Fraction
4import time
5
6import numpy as np
7import click
8
9from cyndilib.wrapper.ndi_structs import FourCC
10from cyndilib.video_frame import VideoSendFrame
11from cyndilib.audio_frame import AudioSendFrame
12from cyndilib.sender import Sender
13
14
15FloatArray2D = np.ndarray[tuple[int, int], np.dtype[np.float32]]
16FloatArray3D = np.ndarray[tuple[int, int, int], np.dtype[np.float32]]
17
18
19
20class Options(NamedTuple):
21 """Options set through the cli
22 """
23 xres: int #: Horizontal resolution
24 yres: int #: Vertical resolution
25 fps: int #: Frame rate
26 sine_freq: float = 1000.0 #: Frequency of the sine wave
27 sine_vol_dBVU: float = -20 #: Volume of the sine wave in dBVU
28 sample_rate: int = 48000 #: Sample rate of the audio
29 audio_channels: int = 2 #: Number of audio channels
30 num_frames: int|None = None #: Number of frames to send, or None for infinite
31 sender_name: str = 'audio_sender' #: NDI name for the sender
32
33
34
35def build_blank_frame(xres: int, yres: int):
36 """Build an array of black pixels in UYVY422 format."""
37 cw, ch = xres >> 1, yres
38 num_bytes = xres * yres + (cw * ch * 2)
39 data = np.zeros(num_bytes, dtype=np.uint8)
40 data[1::2] = 16 # Y channel
41 data[0::2] = 128 # U/V channels
42 return data
43
44
45def gen_sine_wave(
46 sample_rate: int,
47 num_channels: int,
48 center_freq: float,
49 num_samples: int,
50 amplitude: float = 1.0,
51 t_offset: float = 0.0,
52):
53 """Build a sine wave signal.
54 """
55 t = np.arange(num_samples) / sample_rate
56 t += t_offset
57 sig = amplitude * np.sin(2 * np.pi * center_freq * t)
58 sig = np.reshape(sig, (1, num_samples))
59 if num_channels > 1:
60 sig = np.repeat(sig, num_channels, axis=0)
61 assert sig.shape == (num_channels, num_samples)
62 return sig.astype(np.float32)
63
64
65
66class Signal:
67 """Signal helper
68
69 Allows for iteration over samples of a sine wave signal aligned with the
70 frame rate.
71 """
72 def __init__(self, opts: Options) -> None:
73 self.opts = opts
74 self.amplitude = 10 ** (opts.sine_vol_dBVU / 20.0)
75 self.samples_per_frame = opts.sample_rate // opts.fps
76 one_sample = Fraction(1, opts.sample_rate)
77 fc = 1 / Fraction(opts.sine_freq)
78 self.samples_per_cycle = fc / one_sample
79 self.cycles_per_frame = self.samples_per_frame / self.samples_per_cycle
80 self.frame_count = 0
81
82 @property
83 def time_offset(self) -> float:
84 """Time offset in seconds for the current frame."""
85 return self.frame_count / self.opts.fps
86
87 def __iter__(self) -> Generator[FloatArray2D, None, None]:
88 while True:
89 sig = gen_sine_wave(
90 sample_rate=self.opts.sample_rate,
91 num_channels=self.opts.audio_channels,
92 center_freq=self.opts.sine_freq,
93 amplitude=self.amplitude,
94 num_samples=self.samples_per_frame,
95 t_offset=self.time_offset,
96 )
97 assert sig.shape == (self.opts.audio_channels, self.samples_per_frame)
98 yield sig
99 self.frame_count += 1
100
101
102
103def send(opts: Options) -> None:
104 """Send a sine wave audio signal as an NDI stream."""
105
106 sig_generator = Signal(opts)
107
108 sender = Sender(opts.sender_name)
109
110 # Build a VideoSendFrame and set its resolution and frame rate
111 # to match the options argument.
112 vf = VideoSendFrame()
113 vf.set_resolution(opts.xres, opts.yres)
114 vf.set_frame_rate(Fraction(opts.fps))
115 vf.set_fourcc(FourCC.UYVY)
116
117 # Build an AudioSendFrame and set its sample rate and number of channels
118 af = AudioSendFrame()
119 af.sample_rate = opts.sample_rate
120 af.num_channels = opts.audio_channels
121
122 # Set `max_num_samples` to the number of samples per frame
123 af.set_max_num_samples(sig_generator.samples_per_frame)
124
125 # Add the video and audio frames to the sender
126 sender.set_video_frame(vf)
127 sender.set_audio_frame(af)
128
129 # Build data for a blank video frame
130 vid_data = build_blank_frame(opts.xres, opts.yres)
131
132 start_time = time.monotonic()
133 num_frames_sent = 0
134
135 with sender:
136 for samples in sig_generator:
137 if opts.num_frames is not None:
138 if num_frames_sent >= opts.num_frames:
139 break
140
141 # Write the video and audio data to the sender
142 # Note that we don't have to wait in between frames,
143 # as the sender will handle the timing for us.
144 sender.write_video_and_audio(
145 video_data=vid_data,
146 audio_data=samples,
147 )
148
149 num_frames_sent += 1
150 now = time.monotonic()
151 elapsed = now - start_time
152 click.echo(f'\rFrames: {num_frames_sent:04d}\tDuration: {elapsed:.3f}s', nl=False)
153
154
155
156@click.command()
157@click.option('--xres', type=int, default=640, show_default=True)
158@click.option('--yres', type=int, default=480, show_default=True)
159@click.option('--fps', type=int, default=30, show_default=True)
160@click.option('-f', '--sine-freq', type=float, default=1000.0, show_default=True)
161@click.option('-s', '--sine-vol', type=float, default=-20.0, show_default=True)
162@click.option('--sample-rate', type=int, default=48000, show_default=True)
163@click.option('--audio-channels', type=int, default=2, show_default=True)
164@click.option(
165 '-n', '--num-frames', type=int, default=None, show_default=True,
166 help='Number of frames to send, or None for infinite',
167)
168@click.option(
169 '--sender-name', type=str, default='audio_sender', show_default=True,
170 help='NDI name for the sender',
171)
172def main(
173 xres: int,
174 yres: int,
175 fps: int,
176 sine_freq: float,
177 sine_vol: float,
178 sample_rate: int,
179 audio_channels: int,
180 num_frames: int | None,
181 sender_name: str,
182) -> None:
183 """Send a sine wave audio signal as an NDI stream."""
184 opts = Options(
185 xres=xres,
186 yres=yres,
187 fps=fps,
188 sine_freq=sine_freq,
189 sine_vol_dBVU=sine_vol,
190 sample_rate=sample_rate,
191 audio_channels=audio_channels,
192 num_frames=num_frames,
193 sender_name=sender_name,
194 )
195 try:
196 send(opts)
197 finally:
198 click.echo('')
199
200
201if __name__ == '__main__':
202 main()
Receiver#
This example receives video frames from an NDI® Source
and shows them using ffplay.
Finder is used to locate the Source
with the given name.
A Receiver is then created and an instance of
VideoFrameSync is added to it.
Video frames are then read using the
FrameSync.capture_video
method which is available from the frame_sync attribute
on the receiver.
The data is then fed to the stdin of the fplay
subprocess directly from the video frame using the
buffer protocol.
1from __future__ import annotations
2
3from typing import NamedTuple, TYPE_CHECKING
4from typing_extensions import Self
5import enum
6import time
7import subprocess
8import shlex
9
10import click
11
12from cyndilib.wrapper.ndi_structs import FourCC
13from cyndilib.wrapper.ndi_recv import RecvColorFormat, RecvBandwidth
14from cyndilib.video_frame import VideoFrameSync
15from cyndilib.receiver import Receiver
16from cyndilib.finder import Finder
17if TYPE_CHECKING:
18 from cyndilib.finder import Source
19
20
21FF_PLAY = '{ffplay} -video_size {xres}x{yres} -pixel_format {pix_fmt} -f rawvideo -i pipe:'
22"""ffplay command line format"""
23
24
25pix_fmts = {
26 FourCC.UYVY: 'uyvy422',
27 FourCC.NV12: 'nv12',
28 FourCC.RGBA: 'rgba',
29 FourCC.BGRA: 'bgra',
30 FourCC.RGBX: 'rgba',
31 FourCC.BGRX: 'bgra',
32}
33"""Mapping of :class:`FourCC <cyndilib.wrapper.ndi_structs.FourCC>` types to
34ffmpeg's ``pix_fmt`` definitions
35"""
36
37
38class RecvFmt(enum.Enum):
39 """Pixel format to receive (mapped to values of
40 :class:`cyndilib.wrapper.ndi_recv.RecvColorFormat`)
41 """
42 uyvy = RecvColorFormat.UYVY_RGBA #: UYVY (RGBA if alpha is present)
43 rgb = RecvColorFormat.RGBX_RGBA #: RGB / RGBA
44 bgr = RecvColorFormat.BGRX_BGRA #: BGR / BGRA
45
46 @classmethod
47 def from_str(cls, name: str) -> Self:
48 return cls.__members__[name]
49
50
51class Bandwidth(enum.Enum):
52 """Receive bandwidth
53 """
54 lowest = RecvBandwidth.lowest #: Lowest
55 highest = RecvBandwidth.highest #: Highest
56
57 @classmethod
58 def from_str(cls, name: str) -> Self:
59 return cls.__members__[name]
60
61
62class Options(NamedTuple):
63 """Options set through the cli
64 """
65 sender_name: str = 'ffmpeg_sender'
66 """The name of the |NDI| source to connect to"""
67
68 recv_fmt: RecvFmt = RecvFmt.uyvy
69 """Receive pixel format"""
70
71 recv_bandwidth: Bandwidth = Bandwidth.highest
72 """Receive bandwidth"""
73
74 ffplay: str = 'ffplay'
75 """Name/Path of the ``ffplay`` executable"""
76
77
78def get_source(finder: Finder, name: str) -> Source:
79 """Use the Finder to search for an NDI source by name using either its
80 full name or its :attr:`~cyndilib.finder.Source.stream_name`
81 """
82 click.echo('waiting for ndi sources...')
83 finder.wait_for_sources(10)
84 for source in finder:
85 if source.name == name or source.stream_name == name:
86 return source
87 raise Exception(f'source not found. {finder.get_source_names()=}')
88
89
90def wait_for_first_frame(receiver: Receiver) -> None:
91 """The first few frames contain no data. Capture frames until the first
92 non-empty one
93 """
94 vf = receiver.frame_sync.video_frame
95 assert vf is not None
96 frame_rate = vf.get_frame_rate()
97 wait_time = float(1 / frame_rate)
98 click.echo('waiting for frame...')
99 while receiver.is_connected():
100 receiver.frame_sync.capture_video()
101 resolution = vf.get_resolution()
102 if min(resolution) > 0 and vf.get_data_size() > 0:
103 click.echo('have frame')
104 return
105 time.sleep(wait_time)
106
107
108def play(options: Options) -> None:
109 """Create the :class:`~cyndilib.receiver.Receiver` and send the frames to
110 ``ffplay``
111 """
112 # Get the NDI source and keep the Finder open until exit
113 with Finder() as finder:
114 source = get_source(finder, options.sender_name)
115
116 # Build the receiver and video frame
117 receiver = Receiver(
118 color_format=options.recv_fmt.value,
119 bandwidth=options.recv_bandwidth.value,
120 )
121 vf = VideoFrameSync()
122 frame_sync = receiver.frame_sync
123 frame_sync.set_video_frame(vf)
124
125 # Set the receiver source and wait for it to connect
126 receiver.set_source(source)
127 click.echo(f'connecting to "{source.name}"...')
128 i = 0
129 while not receiver.is_connected():
130 if i > 30:
131 raise Exception('timeout waiting for connection')
132 time.sleep(.5)
133 i += 1
134 click.echo('connected')
135
136 proc: subprocess.Popen|None = None
137
138 try:
139 wait_for_first_frame(receiver)
140 # At this point we should have received a frame, so the pixel format,
141 # resolution and frame rate should be populated.
142 fourcc = vf.get_fourcc()
143 frame_rate = vf.get_frame_rate()
144 wait_time = float(1 / frame_rate)
145 xres, yres = vf.get_resolution()
146
147 cmd_str = FF_PLAY.format(
148 xres=xres,
149 yres=yres,
150 pix_fmt=pix_fmts[fourcc],
151 ffplay=options.ffplay,
152 )
153 click.echo(f'{cmd_str=}')
154 proc = subprocess.Popen(shlex.split(cmd_str), stdin=subprocess.PIPE)
155 assert proc.stdin is not None
156
157 # Since we already have a frame with data, write it to ffplay
158 # Note that the frame object itself is directly used as the data source
159 # (since `VideoFrameSync` supports the buffer protocol)
160 proc.stdin.write(vf)
161
162 while receiver.is_connected():
163 # Not the best timing method, but we're using `FrameSync` to
164 # capture frames, so it'll correct things for us (within reason).
165 time.sleep(wait_time)
166 receiver.frame_sync.capture_video()
167 proc.poll()
168 if proc.returncode is not None:
169 break
170 proc.stdin.write(vf)
171
172 finally:
173 if proc is not None:
174 proc.kill()
175
176
177@click.command()
178@click.option(
179 '-s', '--sender-name',
180 type=str,
181 default='ffmpeg_sender',
182 show_default=True,
183 help='The NDI source name to connect to',
184)
185@click.option(
186 '-f', '--recv-fmt',
187 type=click.Choice(choices=[m.name for m in RecvFmt]),
188 default='uyvy',
189 show_default=True,
190 show_choices=True,
191 help='Pixel format'
192)
193@click.option(
194 '-b', '--recv-bandwidth',
195 type=click.Choice(choices=[m.name for m in Bandwidth]),
196 default='highest',
197 show_default=True,
198 show_choices=True,
199)
200@click.option(
201 '--ffplay',
202 type=str,
203 default='ffplay',
204 show_default=True,
205 help='Name/Path of the "ffplay" executable',
206)
207def main(sender_name: str, recv_fmt: str, recv_bandwidth: str, ffplay: str):
208 options = Options(
209 sender_name=sender_name,
210 recv_fmt=RecvFmt.from_str(recv_fmt),
211 recv_bandwidth=Bandwidth.from_str(recv_bandwidth),
212 ffplay=ffplay,
213 )
214 play(options)
215
216
217if __name__ == '__main__':
218 main()
PTZ#
This example showcases the PTZ functions on an NDI® Receiver.
Finder is used to locate a Source.
A Receiver is then created.
Various PTZ methods are then invoked.
1import time
2from cyndilib.finder import Finder
3from cyndilib.receiver import Receiver
4from cyndilib.wrapper.ndi_recv import RecvColorFormat, RecvBandwidth
5
6
7def main():
8 finder = Finder()
9 finder.open()
10 for i in range(5):
11 has_source = finder.wait_for_sources(timeout=5)
12 if has_source:
13 break
14 print(f"No sources detected ({i})")
15
16 source_names = finder.get_source_names()
17 print(source_names)
18
19 source = source_names[0]
20 source_obj = finder.get_source(source)
21 print(source_obj)
22
23 receiver = Receiver(
24 color_format=RecvColorFormat.fastest,
25 bandwidth=RecvBandwidth.metadata_only,
26 recv_name="obs_ndi_ptz"
27 )
28
29 receiver.set_source(source_obj)
30 time.sleep(1.5)
31
32 if not receiver.is_ptz_supported():
33 raise f"The NDI '{source}' does not indicate PTZ support."
34
35 ptz = receiver.ptz
36
37 print("pan to center, tilt to middle")
38 ptz.set_pan_and_tilt_values(0.0, 0.0)
39 time.sleep(5)
40
41 print("zoom to min")
42 ptz.set_zoom_level(0)
43 time.sleep(2)
44
45 print("zoom to max")
46 ptz.set_zoom_level(1)
47 time.sleep(2)
48
49 print("zoom to min")
50 ptz.set_zoom_level(0)
51 time.sleep(2)
52
53 print("zoom in")
54 for _ in range(0, 100):
55 time.sleep(0.01)
56 ptz.zoom(1)
57
58 print("zoom out")
59 for _ in range(0, 100):
60 time.sleep(0.01)
61 ptz.zoom(-0.5)
62
63 print("zoom to min")
64 ptz.set_zoom_level(0)
65 time.sleep(2)
66
67 print("pan to left, tilt to middle")
68 ptz.set_pan_and_tilt_values(-0.5, 0.0)
69 time.sleep(5)
70
71 print("pan to center, tilt to middle")
72 ptz.set_pan_and_tilt_values(0.0, 0.0)
73 time.sleep(5)
74
75 print("pan to right, tilt to middle")
76 ptz.set_pan_and_tilt_values(0.5, 0.0)
77 time.sleep(5)
78
79 print("pan to center, tilt to middle")
80 ptz.set_pan_and_tilt_values(0.0, 0.0)
81 time.sleep(5)
82
83 print("pan to center, title to down")
84 ptz.set_pan_and_tilt_values(0.0, -1.0)
85 time.sleep(5)
86
87 print("pan to center, tilt to middle")
88 ptz.set_pan_and_tilt_values(0.0, 0.0)
89 time.sleep(5)
90
91 print("pan to center, tilt to up")
92 ptz.set_pan_and_tilt_values(0.0, 1.0)
93 time.sleep(5)
94
95 print("continuously pan left")
96 for _ in range(0, 100):
97 time.sleep(0.05)
98 ptz.pan(.5)
99
100 print("continuously pan right")
101 for _ in range(0, 100):
102 time.sleep(0.05)
103 ptz.pan(-.5)
104
105 print("pan to center, title to middle")
106 ptz.set_pan_and_tilt_values(0.0, 0.0)
107 time.sleep(2)
108
109 print("continuously tilt down")
110 for _ in range(0, 100):
111 time.sleep(0.05)
112 ptz.tilt(-.5)
113
114 print("continuously tilt up")
115 for _ in range(0, 100):
116 time.sleep(0.05)
117 ptz.tilt(.5)
118
119 print("pan to center, title to middle")
120 ptz.set_pan_and_tilt_values(0.0, 0.0)
121 time.sleep(2)
122
123 print("store as preset to slot 10")
124 ptz.store_preset(10)
125
126 print("move")
127 ptz.set_pan_and_tilt_values(.5, .5)
128 time.sleep(2)
129
130 print("store as preset to slot 11")
131 ptz.store_preset(11)
132
133 print("recall preset in slot 10")
134 ptz.recall_preset(10, 1.0)
135 time.sleep(2)
136
137 print("recall preset in slot 11")
138 ptz.recall_preset(11, 1.0)
139 time.sleep(2)
140
141 print("recall preset in slot 10, slower")
142 ptz.recall_preset(10, 0.5)
143 time.sleep(4)
144
145 print("recall preset in slot 11, slowest")
146 ptz.recall_preset(11, 0.0)
147 time.sleep(6)
148
149 print("pan to center, title to middle")
150 ptz.set_pan_and_tilt_values(0.0, 0.0)
151 time.sleep(2)
152
153 print("trigger autofocus")
154 ptz.autofocus()
155 time.sleep(1)
156
157 print("focus min (infinity)")
158 ptz.set_focus(0.0)
159 time.sleep(2)
160
161 print("focus max")
162 ptz.set_focus(1.0)
163 time.sleep(2)
164
165 print("decrease focus")
166 for _ in range(0, 100):
167 time.sleep(0.05)
168 ptz.focus(-.5)
169
170 print("increase focus")
171 for _ in range(0, 100):
172 time.sleep(0.05)
173 ptz.focus(.5)
174
175 print("trigger autofocus")
176 ptz.autofocus()
177 time.sleep(1)
178
179 print("trigger auto white-balance")
180 ptz.white_balance_auto()
181 time.sleep(2)
182
183 print("set indoor white-balance")
184 ptz.white_balance_indoor()
185 time.sleep(2)
186
187 print("set outdoor white-balance")
188 ptz.white_balance_outdoor()
189 time.sleep(2)
190
191 print("trigger oneshot white-balance")
192 ptz.white_balance_oneshot()
193 time.sleep(2)
194
195 print("set white-balance to min")
196 ptz.set_white_balance(0.0, 0.0)
197 time.sleep(2)
198
199 print("set white-balance to max")
200 ptz.set_white_balance(1.0, 1.0)
201 time.sleep(2)
202
203 print("trigger auto white-balance")
204 ptz.white_balance_auto()
205 time.sleep(2)
206
207 print("(re-)enable auto exposure")
208 ptz.exposure_auto()
209 time.sleep(2)
210
211 print("set exposure to dark")
212 ptz.set_exposure_coarse(0.0)
213 time.sleep(2)
214
215 print("set exposure to bright")
216 ptz.set_exposure_coarse(1.0)
217 time.sleep(2)
218
219 print("set exposure to dark (fine adjustment)")
220 ptz.set_exposure_fine(.0, .0, .0)
221 time.sleep(2)
222
223 print("set exposure to bright (fine adjustment)")
224 ptz.set_exposure_fine(1.0, 1.0, 1.0)
225 time.sleep(2)
226
227 print("re-enable auto exposure")
228 ptz.exposure_auto()
229 time.sleep(2)
230
231
232if __name__ == "__main__":
233 main()