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, Literal, Generator, Any, cast, get_args
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
18TestSource = Literal[
19 'testsrc2', 'yuvtestsrc', 'rgbtestsrc', 'smptebars', 'smptehdbars',
20 'zoneplate', 'colorspectrum',
21]
22
23FF_CMD = '{ffmpeg} -f lavfi -i {source}=size={xres}x{yres}:rate={fps} \
24 -pix_fmt {pix_fmt.name} -f rawvideo pipe: '
25
26
27
28class PixFmt(enum.Enum):
29 """Maps ffmpeg's ``pix_fmt`` names to their corresponding
30 :class:`FourCC <cyndilib.wrapper.ndi_structs.FourCC>` types
31 """
32 uyvy422 = FourCC.UYVY #: uyvy422
33 nv12 = FourCC.NV12 #: nv12
34 rgba = FourCC.RGBA #: rgba
35 rgb0 = FourCC.RGBX #: rgb0
36 bgra = FourCC.BGRA #: bgra
37 bgr0 = FourCC.BGRX #: bgr0
38 p216be = FourCC.P216 #: p216be
39 yuv420p = FourCC.I420 #: yuv420p (i420)
40
41 @classmethod
42 def from_str(cls, name: str) -> Self:
43 return cls.__members__[name]
44
45
46class Options(NamedTuple):
47 """Options set through the cli
48 """
49 source: TestSource #: Source to use for the test pattern
50 pix_fmt: PixFmt #: Pixel format to send
51 xres: int #: Horizontal resolution
52 yres: int #: Vertical resolution
53 fps: str #: Frame rate
54 sender_name: str = 'ffmpeg_sender' #: NDI name for the sender
55 ffmpeg: str = 'ffmpeg' #: Name/Path of the "ffmpeg" executable
56
57
58def parse_frame_rate(fr: str) -> Fraction:
59 """Helper for NTSC frame rates (29.97, 59.94)
60 """
61 if '/' in fr:
62 n, d = [int(s) for s in fr.split('/')]
63 elif '.' in fr:
64 n = round(float(fr)) * 1000
65 d = 1001
66 else:
67 n = int(fr)
68 d = 1
69 return Fraction(n, d)
70
71
72@contextmanager
73def ffmpeg_proc(opts: Options) -> Generator[subprocess.Popen[bytes], Any, None]:
74 """Context manager for the ffmpeg subprocess generating frames
75 """
76 ff_cmd = FF_CMD.format(**opts._asdict())
77 ff_proc = subprocess.Popen(shlex.split(ff_cmd), stdout=subprocess.PIPE)
78 try:
79 ff_proc.poll()
80 if ff_proc.returncode is None:
81 yield ff_proc
82 finally:
83 ff_proc.kill()
84
85
86def send(opts: Options) -> None:
87 """Send frames generated by ffmpeg's ``testsrc2`` as an |NDI| stream
88
89 The raw frames generated by ffmpeg are sent to a pipe and read from its
90 :attr:`~subprocess.Popen.stdout`. The data is then fed directly to
91 :meth:`cyndilib.sender.Sender.write_video_async` using an
92 intermediate :class:`memoryview`
93 """
94 sender = Sender(opts.sender_name)
95
96 # Build a VideoSendFrame and set its resolution and frame rate
97 # to match the options argument
98 vf = VideoSendFrame()
99 vf.set_resolution(opts.xres, opts.yres)
100 fr = parse_frame_rate(opts.fps)
101 vf.set_frame_rate(fr)
102 vf.set_fourcc(opts.pix_fmt.value)
103
104 # Add the VideoSendFrame to the sender
105 sender.set_video_frame(vf)
106
107 # Pre-allocate a bytearray to hold frame data and create a view of it
108 # So we can buffer into it from ffmpeg then pass directly to the sender
109 frame_size_bytes = vf.get_data_size()
110 ba = bytearray(frame_size_bytes)
111 mv = memoryview(ba)
112
113 i = 0
114 frame_sent = False
115
116 with sender:
117 with ffmpeg_proc(opts) as ff_proc:
118 stdout = cast(io.BytesIO, ff_proc.stdout)
119 while True:
120 i += 1
121 if ff_proc.returncode is not None:
122 break
123 # Read from the ffmpeg process into a view of the bytearray
124 num_read = stdout.readinto(mv)
125
126 # The first few reads might be empty, ignore
127 if num_read == 0:
128 if frame_sent:
129 # If we've sent a frame and there's no output,
130 # ffmpeg has likely quit without setting returncode
131 break
132 elif i > 1000:
133 # Check for ffmpeg startup errors
134 print('Timeout waiting for ffmpeg to produce output')
135 break
136 continue
137 frame_sent = True
138 # Pass the memoryview directly to the sender
139 # (using the buffer protocol)
140 sender.write_video_async(mv)
141
142 if i % 10 == 0:
143 ff_proc.poll()
144
145
146
147@click.command()
148@click.option(
149 '--source',
150 type=click.Choice(
151 choices=[m for m in get_args(TestSource)],
152 ),
153 default='testsrc2',
154 show_default=True,
155 help='Name of the ffmpeg test source to use',
156)
157@click.option(
158 '--pix-fmt',
159 type=click.Choice(choices=[m.name for m in PixFmt]),
160 default=PixFmt.uyvy422.name,
161 show_default=True,
162 show_choices=True,
163)
164@click.option('-x', '--x-res', type=int, default=1920, show_default=True)
165@click.option('-y', '--y-res', type=int, default=1080, show_default=True)
166@click.option('--fps', type=str, default='30', show_default=True)
167@click.option(
168 '-n', '--sender-name',
169 type=str,
170 default='ffmpeg_sender',
171 show_default=True,
172 help='NDI name for the sender',
173)
174@click.option(
175 '--ffmpeg',
176 type=str,
177 default='ffmpeg',
178 show_default=True,
179 help='Name/Path of the "ffmpeg" executable',
180)
181def main(
182 source: TestSource,
183 pix_fmt: str,
184 x_res: int,
185 y_res: int,
186 fps: str,
187 sender_name: str,
188 ffmpeg: str
189):
190 opts = Options(
191 source=source,
192 pix_fmt=PixFmt.from_str(pix_fmt),
193 xres=x_res,
194 yres=y_res,
195 fps=fps,
196 sender_name=sender_name,
197 ffmpeg=ffmpeg,
198 )
199 send(opts)
200
201
202if __name__ == '__main__':
203 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 import VideoSendFrame, AudioSendFrame, AudioReference
11from cyndilib.sender import Sender
12
13
14FloatArray2D = np.ndarray[tuple[int, int], np.dtype[np.float32]]
15FloatArray3D = np.ndarray[tuple[int, int, int], np.dtype[np.float32]]
16
17
18
19class Options(NamedTuple):
20 """Options set through the cli
21 """
22 xres: int #: Horizontal resolution
23 yres: int #: Vertical resolution
24 fps: int #: Frame rate
25 sine_freq: float = 1000.0 #: Frequency of the sine wave
26 sine_vol_dB: float = -20 #: Volume of the sine wave in dBVU
27 sample_rate: int = 48000 #: Sample rate of the audio
28 audio_channels: int = 2 #: Number of audio channels
29 audio_reference: AudioReference = AudioReference.dBVU
30 """Audio reference level"""
31 num_frames: int|None = None #: Number of frames to send, or None for infinite
32 sender_name: str = 'audio_sender' #: NDI name for the sender
33
34
35
36def build_blank_frame(xres: int, yres: int):
37 """Build an array of black pixels in UYVY422 format."""
38 cw, ch = xres >> 1, yres
39 num_bytes = xres * yres + (cw * ch * 2)
40 data = np.zeros(num_bytes, dtype=np.uint8)
41 data[1::2] = 16 # Y channel
42 data[0::2] = 128 # U/V channels
43 return data
44
45
46def gen_sine_wave(
47 sample_rate: int,
48 num_channels: int,
49 center_freq: float,
50 num_samples: int,
51 amplitude: float = 1.0,
52 t_offset: float = 0.0,
53):
54 """Build a sine wave signal.
55 """
56 t = np.arange(num_samples) / sample_rate
57 t += t_offset
58 sig = amplitude * np.sin(2 * np.pi * center_freq * t)
59 sig = np.reshape(sig, (1, num_samples))
60 if num_channels > 1:
61 sig = np.repeat(sig, num_channels, axis=0)
62 assert sig.shape == (num_channels, num_samples)
63 return sig.astype(np.float32)
64
65
66
67class Signal:
68 """Signal helper
69
70 Allows for iteration over samples of a sine wave signal aligned with the
71 frame rate.
72 """
73 def __init__(self, opts: Options) -> None:
74 self.opts = opts
75 self.amplitude = 10 ** (opts.sine_vol_dB / 20.0)
76 self.samples_per_frame = opts.sample_rate // opts.fps
77 one_sample = Fraction(1, opts.sample_rate)
78 fc = 1 / Fraction(opts.sine_freq)
79 self.samples_per_cycle = fc / one_sample
80 self.cycles_per_frame = self.samples_per_frame / self.samples_per_cycle
81 self.frame_count = 0
82
83 @property
84 def time_offset(self) -> float:
85 """Time offset in seconds for the current frame."""
86 return self.frame_count / self.opts.fps
87
88 def __iter__(self) -> Generator[FloatArray2D, None, None]:
89 while True:
90 sig = gen_sine_wave(
91 sample_rate=self.opts.sample_rate,
92 num_channels=self.opts.audio_channels,
93 center_freq=self.opts.sine_freq,
94 amplitude=self.amplitude,
95 num_samples=self.samples_per_frame,
96 t_offset=self.time_offset,
97 )
98 assert sig.shape == (self.opts.audio_channels, self.samples_per_frame)
99 yield sig
100 self.frame_count += 1
101
102
103
104def send(opts: Options) -> None:
105 """Send a sine wave audio signal as an NDI stream."""
106
107 sig_generator = Signal(opts)
108
109 sender = Sender(opts.sender_name)
110
111 # Build a VideoSendFrame and set its resolution and frame rate
112 # to match the options argument.
113 vf = VideoSendFrame()
114 vf.set_resolution(opts.xres, opts.yres)
115 vf.set_frame_rate(Fraction(opts.fps))
116 vf.set_fourcc(FourCC.UYVY)
117
118 # Build an AudioSendFrame and set its sample rate and number of channels
119 af = AudioSendFrame()
120 af.sample_rate = opts.sample_rate
121 af.num_channels = opts.audio_channels
122 af.reference_level = opts.audio_reference
123
124 # Set `max_num_samples` to the number of samples per frame
125 af.set_max_num_samples(sig_generator.samples_per_frame)
126
127 # Add the video and audio frames to the sender
128 sender.set_video_frame(vf)
129 sender.set_audio_frame(af)
130
131 # Build data for a blank video frame
132 vid_data = build_blank_frame(opts.xres, opts.yres)
133
134 start_time = time.monotonic()
135 num_frames_sent = 0
136
137 with sender:
138 for samples in sig_generator:
139 if opts.num_frames is not None:
140 if num_frames_sent >= opts.num_frames:
141 break
142
143 # Write the video and audio data to the sender
144 # Note that we don't have to wait in between frames,
145 # as the sender will handle the timing for us.
146 sender.write_video_and_audio(
147 video_data=vid_data,
148 audio_data=samples,
149 )
150
151 num_frames_sent += 1
152 now = time.monotonic()
153 elapsed = now - start_time
154 click.echo(f'\rFrames: {num_frames_sent:04d}\tDuration: {elapsed:.3f}s', nl=False)
155
156
157
158@click.command()
159@click.option('--xres', type=int, default=640, show_default=True)
160@click.option('--yres', type=int, default=480, show_default=True)
161@click.option('--fps', type=int, default=30, show_default=True)
162@click.option('-f', '--sine-freq', type=float, default=1000.0, show_default=True)
163@click.option(
164 '-s', '--sine-vol', type=float, default=-20.0, show_default=True,
165 help='Volume of the sine wave in dB (unit depends on audio reference)',
166)
167@click.option(
168 '--audio-reference',
169 type=click.Choice([m.name for m in AudioReference]),
170 default=AudioReference.dBVU.name,
171 show_default=True,
172 help='Audio reference level',
173)
174@click.option('--sample-rate', type=int, default=48000, show_default=True)
175@click.option('--audio-channels', type=int, default=2, show_default=True)
176@click.option(
177 '-n', '--num-frames', type=int, default=None, show_default=True,
178 help='Number of frames to send, or None for infinite',
179)
180@click.option(
181 '--sender-name', type=str, default='audio_sender', show_default=True,
182 help='NDI name for the sender',
183)
184def main(
185 xres: int,
186 yres: int,
187 fps: int,
188 sine_freq: float,
189 sine_vol: float,
190 audio_reference: str,
191 sample_rate: int,
192 audio_channels: int,
193 num_frames: int | None,
194 sender_name: str,
195) -> None:
196 """Send a sine wave audio signal as an NDI stream."""
197 audio_reference_enum = AudioReference[audio_reference]
198 opts = Options(
199 xres=xres,
200 yres=yres,
201 fps=fps,
202 sine_freq=sine_freq,
203 sine_vol_dB=sine_vol,
204 audio_reference=audio_reference_enum,
205 sample_rate=sample_rate,
206 audio_channels=audio_channels,
207 num_frames=num_frames,
208 sender_name=sender_name,
209 )
210 try:
211 send(opts)
212 finally:
213 click.echo('')
214
215
216if __name__ == '__main__':
217 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()