From f916075a2dc72e32fdd2c5131953a181bd87045b Mon Sep 17 00:00:00 2001
From: Jose Tiago Macara Coutinho Human friendly interface to linux subsystems using python. Provides python access to several linux subsystems like V4L2, input and MIDI. There is experimental, undocumented, incomplete and unstable access to USB. Need fine control over Webcams, MIDI devices, input devices (joysticks, gamepads, keyboards, mice or even the keyboard light on your laptop)? Linuxpy has your back. Requirements: And yes, it is true: there are no python dependencies! Also there are no C libraries dependencies! Everything is done here through direct ioctl, read and write calls. Ain't linux wonderful? From within your favorite python environment: To run the examples you'll need: To develop, run tests, build package, lint, etc you'll need: To run docs you'll need: Most python libraries try as hard as possible to be platform independent. Why create a library that is explicitly designed to work only on linux? Well, first of all, one of the goals is to be able to access low level linux device capabilities like video controls. Second, I don't have access to proprietary OS like Windows or MacOS. If this answer is not enough than think of this library as a low level dependency on linux systems of other libraries that will be concerned with providing a common API on different platforms. A linux OS and python >= 3.9. From within your favorite python environment: Additionally, to run the code generation tool you'll need On a debian based run: This project uses an internal simple code generator that reads linux kernel header files and produces several To re-generate these files for a newer linux kernel (or fix a bug) you'll need linux header files installed on your system + black. To launch the tool call: Some video tests will only run with a properly configured Additionally the user which runs the tests will need read/write access to Some input tests require the user which runs the tests to have read/write access to Here's the reference or code API, the classes, functions, parameters, attributes, and all the LinuxPy parts you can use in your applications. If you want to learn LinuxPy you are much better off reading the LinuxPy User Guide. Human friendly interface to linux Input subsystem. The heart of linuxpy MIDI library is the Bases: Input error Bases: Central linux input subsystem class. You can create an instance directly if you know the device name: ... but it is generally easier to use the The device name The version The physical location The device input ID The device capabilities All active keys at the moment of calling this Current absolute X value Current absolute Y value Current absolute Z value Current relative X value Current relative Y value Current relative Z value Build an infinite iterator that streams input events You'll need an open Device before using it: Build an infinite async iterator that streams input events You'll need an open Device before using it: Absolute information for the given abs code Read event. Event must be available to read or otherwise will raise an error Wait for next event or return last event Bases: A uinput device with no capabilities registered List readable character devices in the given path. Yields packets of events occurring at the same moment in time. Yields packets of events occurring at the same moment in time. If find_all is False: Find a device follwing the criteria matched by custom_match and kwargs. If no device is found matching the criteria it returns None. Default is to return a random first device. If find_all is True: The result is an iterator. Find all devices that match the criteria custom_match and kwargs. If no device is found matching the criteria it returns an empty iterator. Default is to return an iterator over all input devices found on the system. If find_all is False: Find a gamepad device follwing the criteria matched by custom_match and kwargs. If no device is found matching the criteria it returns None. Default is to return a random first gamepad. If find_all is True: The result is an iterator. Find all gamepad devices that match the criteria custom_match and kwargs. If no gamepad is found matching the criteria it returns an empty iterator. Default is to return an iterator over all gamepad devices found on the system. If find_all is False: Find a keyboard device follwing the criteria matched by custom_match and kwargs. If no device is found matching the criteria it returns None. Default is to return a random first keyboard. If find_all is True: The result is an iterator. Find all keyboard devices that match the criteria custom_match and kwargs. If no keyboard is found matching the criteria it returns an empty iterator. Default is to return an iterator over all keyboard devices found on the system. If find_all is False: Find a mouse device follwing the criteria matched by custom_match and kwargs. If no device is found matching the criteria it returns None. Default is to return a random first mouse. If find_all is True: The result is an iterator. Find all mouse devices that match the criteria custom_match and kwargs. If no mouse is found matching the criteria it returns an empty iterator. Default is to return an iterator over all mouse devices found on the system. Human friendly interface to linux MIDI subsystem. The heart of linuxpy MIDI library is the which is roughly equivalent to: Here's a real world example: Bases: MIDI error Bases: Central MIDI class. Current Client information Current Client information Current running mode Current system information An iterator over all open clients on the system. It returns new Client each time Returns a new list of all clients on the system An iterator over all open ports on the system. It returns new Port objects each time Returns a new list of all open ports on the system. It returns new Port objects each time Build an infinite iterator that streams MIDI events from the subscribed ports. You'll need an open sequencer before using it: Build an infinite async iterator that streams MIDI events from the subscribed ports. You'll need an open sequencer before using it: Returns a Client for the given ID or raises an error if the client doesn't exist. It returns new Client object each time Returns a Port for the given address or raises an error if the port doesn't exist. It returns new Port object each time Create a new local port. By default it will create a MIDI generic application Input/Output port. Delete a previously created local port. If the port has any subscriptions they will be closed before the port is deleted Subscribe a source port to a destination port Unsubscribe a previously subscribed source port to a destination port Read list of pending events. If the sequencer is opened in blocking mode and there are no events it blocks until at least one event occurs otherwise as OSError is raised. Use the Read list of pending events. If there are no events it blocks until at least one event occurs and returns it. Use the Read list of pending events. If there are no events it blocks until at least one event occurs and returns it. This method assumes the internal file descriptior was opened in non-blocking mode. Use the Read list of pending events. If there are no events it blocks until at least one event occurs and returns it Send an event message Send a message of the given type from a specific port to the destination address(es). Use kwargs to pass specific event arguments like velocity in a \"note on\" event. event_type can be an instance of EventType or the equivalent number or a case insensitive string matching the event type (ex: \"noteon\", \"NOTEON\", \"note-on\" or \"note on\"). The following example sends \"note on\" with velocity 45 on port 0 of client 14: MIDI sequencer client. Don't instantiate this object directly Use instead Client name True if the client was created by the MIDI sequencer that it references or False otherwise\" An iterator over all open ports for this client. It returns new Port each time Returns a new list of all open ports for this client The client ID MIDI sequencer port. Don't instantiate this object directly Use instead Port name True if the port was created by the MIDI sequencer that it references or False otherwise\" The client ID The port ID The port type The port capabilities The port address The port ID Connect this port to a remote port. After connecting, this port will receive events originating from the source port. Example: Disconnect this port from a previously connected source port. Connect this port to a remote port. After connecting, events originating from this port will be sent to the destination port. Example: Disconnect this port from a previously connected destination port. Delete this port. Raises MidiError if port is not local. Any subscriptions are canceled before the port is deleted. Send a message of the given type from to the destination address(es). Use kwargs to pass specific event arguments like velocity in a \"note on\" event. event_type can be an instance of EventType or the equivalent number or a case insensitive string matching the event type (ex: \"noteon\", \"NOTEON\", \"note-on\" or \"note on\"). The following example sends \"note on\" on note 42, with velocity 45: Event message object result of listening on a sequencer Serialize the Event in a bytes ready to be sent Create new Event of the given type Convert to low level snd_seq_addr Infinite stream of events coming from the given sequencer Infinite async stream of events coming from the given sequencer Human friendly interface to V4L2 (Video 4 Linux 2) subsystem. Bases: Video for linux 2 error The resulting object from an acquisition. Wait for next event or return last event in queue Wait for next frame or return last frame request + query buffers request + query buffers create buffers + mmap_from_buffer Some drivers (ex: v4l2loopback) don't report being output capable so that apps like zoom recognize them as valid capture devices so some results might be missing This tutorial shows you how to use LinuxPy with most of its features. Human friendly interface to the Linux Input subsystem. API not documented yet. Just this example: Without further ado: asyncio is a first class citizen to linuxpy.midi: A basic CLI is provided that allows listing MIDI clients & ports and dumping MIDI sequencer events: List all ports: Listen to events on selected port(s): Human friendly interface to the Video for Linux 2 (V4L2) subsystem. Without further ado: Create a device object from an ID: from a filename:
"},{"location":"#installation","title":"Installation","text":"
asyncio.to_thread()
$ pip install linuxpy[examples]\n
$ pip install linuxpy[dev]\n
"},{"location":"#faq","title":"FAQ","text":"$ pip install linuxpy[docs]\n
$ pip install linuxpy[dev]\n
castxml
installed on your system.
"},{"location":"develop/#code-generation","title":"Code generation","text":"$ apt install castxml\n
raw.py
ctypes based python files for each sub-system.
"},{"location":"develop/#running-tests","title":"Running tests","text":"$ python -m linuxpy.codegen.cli\n
v4l2loopback
.$ sudo modprobe v4l2loopback video_nr=199 card_label=\"Loopback 199\"\n
/dev/video199
. On most systems this can be achieved by adding the user to the video
group:$ sudo addgroup $USER video\n
/dev/uinput
. On most systems this can be achieved by adding the user to the input
group:
"},{"location":"api/","title":"LinuxPy reference API","text":"$ sudo addgroup $USER input\n
linuxpy.input.device
","text":"Device
class. The recommended way is to use one of the find methods to create a Device object and use it within a context manager like:
"},{"location":"api/input/#linuxpy.input.device.InputError","title":"from linuxpy.input.device import find_gamepad\n\nwith find_gamepad() as gamepad:\n print(f\"Gamepad name: {gamepad.name}\")\n
InputError
","text":"Exception
Device(*args, **kwargs)
","text":"BaseDevice
from linuxpy.input.device import Device\n\nwith Device(\"/dev/input11\") as i11:\n print(i11.name)\n
find
helper to get a device with a certain condition. Example:
"},{"location":"api/input/#linuxpy.input.device.Device.name","title":"from linuxpy.input.device import find\n\ntrack_point = find(name=\"TPPS/2 Elan TrackPoint\")\n
name: str
cached
property
","text":"version: Version
cached
property
","text":"physical_location: str
cached
property
","text":"device_id: input_id
cached
property
","text":"capabilities
cached
property
","text":"active_keys
property
","text":"x
property
","text":"y
property
","text":"z
property
","text":"rx
property
","text":"ry
property
","text":"rz
property
","text":"__iter__() -> Iterable[InputEvent]
","text":"
"},{"location":"api/input/#linuxpy.input.device.Device.__aiter__","title":"from linuxpy.input.device import find_mouse\n\nwith find_mouse() as mouse:\n for event in mouse:\n print(event)\n
__aiter__() -> AsyncIterable[InputEvent]
async
","text":"
"},{"location":"api/input/#linuxpy.input.device.Device.get_abs_info","title":"import asyncio\nfrom linuxpy.input.device import find_mouse\n\nasync def main():\n with find_mouse() as mouse:\n async for event in mouse:\n print(event)\n\nasyncio.run(main())\n
get_abs_info(abs_code)
","text":"read_event()
","text":"EventReader(device: Device, max_queue_size=1)
","text":""},{"location":"api/input/#linuxpy.input.device.EventReader.aread","title":"aread()
async
","text":"BaseUDevice(filename=PATH, bus=Bus.USB, vendor_id=1, product_id=1, name='linuxpy emulated device')
","text":"BaseDevice
iter_input_files(path: PathLike = '/dev/input', pattern: str = 'event*')
","text":"event_batch_stream(fd) -> Iterable[Sequence[InputEvent]]
","text":"async_event_batch_stream(fd, maxsize: int = 1000) -> AsyncIterable[Sequence[InputEvent]]
async
","text":"find(find_all: bool = False, custom_match: Optional[Callable] = None, **kwargs) -> Union[Device, Iterable[Device], None]
","text":"find_gamepad(find_all: bool = False, custom_match: Optional[Callable] = None, **kwargs) -> Union[Device, Iterable[Device], None]
","text":"find_keyboard(find_all: bool = False, custom_match: Optional[Callable] = None, **kwargs) -> Union[Device, Iterable[Device], None]
","text":"find_mouse(find_all: bool = False, custom_match: Optional[Callable] = None, **kwargs) -> Union[Device, Iterable[Device], None]
","text":"linuxpy.midi.device
","text":"Sequencer
class. Usually you need only one instance of Sequencer for your application. The recommended way is to use it within a context manager like:with Sequencer(\"My MIDI App\") as midi:\n print(f\"MIDI version: {midi.version}\")\n
midi = Sequencer(\"My MIDI App\")\nmidi.open()\ntry:\n print(f\"MIDI version: {midi.version}\")\nfinally:\n midi.close()\n
"},{"location":"api/midi/#linuxpy.midi.device.MidiError","title":"from linuxpy.midi.device import Sequencer\n\nwith Sequencer(\"My MIDI App\") as midi:\n print(f\"I'm client {midi.client_id}\")\n print(f\"MIDI version: {midi.version}\")\n port = midi.create_port()\n port.connect_from((0, 1))\n for event in midi:\n print(event)\n
MidiError
","text":"Exception
Sequencer(name: str = 'linuxpy client', **kwargs)
","text":"BaseDevice
"},{"location":"api/midi/#linuxpy.midi.device.Sequencer.client_info","title":"from linuxpy.midi.device import Sequencer\n\nwith Sequencer(\"My MIDI App\") as midi:\n print(f\"I'm client {midi.client_id}\")\n print(f\"MIDI version: {midi.version}\")\n port = midi.create_port()\n port.connect_from((0, 1))\n for event in midi:\n print(event)\n
client_info: snd_seq_client_info
property
","text":"client: Client
property
","text":"running_mode: snd_seq_running_info
property
","text":"system_info: snd_seq_system_info
property
","text":"iter_clients: Iterable[Client]
property
","text":"clients: Sequence[Client]
property
","text":"iter_ports: Iterable[Port]
property
","text":"ports: Sequence[Port]
property
","text":"__iter__() -> Iterable[Event]
","text":"
"},{"location":"api/midi/#linuxpy.midi.device.Sequencer.__aiter__","title":"from linuxpy.midi.device import Sequencer\n\nwith Sequencer() as midi:\n port = midi.create_port()\n port.connect_from((0, 1))\n for event in midi:\n print(event)\n
__aiter__() -> AsyncIterable[Event]
async
","text":"
"},{"location":"api/midi/#linuxpy.midi.device.Sequencer.get_client","title":"import asyncio\nfrom linuxpy.midi.device import Sequencer\n\nasync def main():\n with Sequencer() as midi:\n port = midi.create_port()\n port.connect_from((0, 1))\n async for event in midi:\n print(event)\n\nasyncio.run(main())\n
get_client(client_id: int) -> Client
","text":"get_port(address: FullPortAddress) -> Port
","text":"create_port(name: str = 'linuxpy port', capabilities: PortCapability = INPUT_OUTPUT, port_type: PortType = PortType.MIDI_GENERIC | PortType.APPLICATION) -> Port
","text":"delete_port(port: Union[int, Port])
","text":"subscribe(src: FullPortAddress, dest: FullPortAddress)
","text":"unsubscribe(src: FullPortAddress, dest: FullPortAddress)
","text":"iter_raw_read(max_nb_packets: int = 64) -> Iterable[Event]
","text":"read()
call instead because it handles blocking vs non-blocking variants transperently.raw_read(max_nb_packets=64) -> Sequence[Event]
","text":"read()
call instead because it handles blocking vs non-blocking variants transperently.wait_read() -> Sequence[Event]
","text":"read()
call instead because it handles blocking vs non-blocking variants transperentlyread() -> Sequence[Event]
","text":"write(event: Event)
","text":"send(port: PortAddress, event_type: Union[str, int, EventType], queue: int = QUEUE_DIRECT, to: Union[FullPortAddress, FullPortAddresses] = SUBSCRIBERS, **kwargs)
","text":"
"},{"location":"api/midi/#linuxpy.midi.device.Client","title":"midi.send((14, 0), \"note on\", velocity=45)\n
Client(sequencer: Sequencer, client: snd_seq_client_info)
","text":"Sequencer.get_client()
name: str
property
","text":"is_local: bool
property
","text":"iter_ports: Iterable[Port]
property
","text":"ports: Sequence[Port]
property
","text":"__int__()
","text":"Port(sequencer: Sequencer, port: snd_seq_port_info)
","text":"Sequencer.get_port()
name: str
property
","text":"is_local: bool
property
","text":"client_id: int
property
","text":"port_id: int
property
","text":"type: PortType
property
","text":"capability: PortCapability
property
","text":"address: snd_seq_addr
property
","text":"__int__()
","text":"connect_from(src: FullPortAddress)
","text":"
"},{"location":"api/midi/#linuxpy.midi.device.Port.disconnect_from","title":"from linuxpy.midi.device import Sequencer\n\nwith Sequencer() as midi:\n port = midi.create_port()\n port.connect_from((0, 1))\n for event in midi:\n print(event)\n
disconnect_from(src: FullPortAddress)
","text":"connect_to(dest: FullPortAddress)
","text":"
"},{"location":"api/midi/#linuxpy.midi.device.Port.disconnect_to","title":"from linuxpy.midi.device import Sequencer\n\nwith Sequencer() as midi:\n port = midi.create_port()\n # Assume 14:0 is Midi Through\n port.connect_to((14, 0))\n port.send(\"note on\", note=11, velocity=10)\n
disconnect_to(dest: FullPortAddress)
","text":"delete()
","text":"send(event_type: Union[str, int, EventType], **kwargs)
","text":"
"},{"location":"api/midi/#linuxpy.midi.device.Event","title":"port.send(\"note on\", note=42, velocity=45)\n
Event(event: snd_seq_event)
","text":"__bytes__()
","text":"new(etype: EventT, **kwargs)
classmethod
","text":"to_address(addr: FullPortAddress) -> snd_seq_addr
","text":"event_stream(sequencer: Sequencer) -> Iterable[Event]
","text":"async_event_stream(sequencer: Sequencer, maxsize: int = 10) -> AsyncIterable[Event]
async
","text":"linuxpy.video.device
","text":"V4L2Error
","text":"Exception
Frame(data: bytes, buff: raw.v4l2_buffer, format: Format)
","text":"EventReader(device: Device, max_queue_size=100)
","text":""},{"location":"api/video/#linuxpy.video.device.EventReader.aread","title":"aread()
async
","text":"FrameReader(device: Device, raw_read: Callable[[], Buffer], max_queue_size: int = 1)
","text":""},{"location":"api/video/#linuxpy.video.device.FrameReader.aread","title":"aread() -> Frame
async
","text":"create_buffer(fd, buffer_type: BufferType, memory: Memory) -> raw.v4l2_buffer
","text":"create_buffers(fd, buffer_type: BufferType, memory: Memory, count: int) -> list[raw.v4l2_buffer]
","text":"create_mmap_buffers(fd, buffer_type: BufferType, memory: Memory, count: int) -> list[mmap.mmap]
","text":"iter_video_output_files(path: PathLike = '/dev') -> Iterable[Path]
","text":"
"},{"location":"user_guide/input/#asyncio","title":"asyncio","text":"python -m asyncio from linuxpy.input.device import find_gamepad with find_gamepad() as pad: async for event in pad: print(event) InputEvent(time=1697520475.348099, type=<EventType.SYN: 0>, code=<Synchronization.REPORT: 0>, value=0) InputEvent(time=1697520475.361564, type=<EventType.REL: 2>, code=<Relative.X: 0>, value=-1) InputEvent(time=1697520475.361564, type=<EventType.REL: 2>, code=<Relative.Y: 1>, value=1) InputEvent(time=1697520475.361564, type=<EventType.SYN: 0>, code=<Synchronization.REPORT: 0>, value=0) InputEvent(time=1697520475.371128, type=<EventType.REL: 2>, code=<Relative.X: 0>, value=-1) InputEvent(time=1697520475.371128, type=<EventType.SYN: 0>, code=<Synchronization.REPORT: 0>, value=0) ..."},{"location":"user_guide/input/#references","title":"References","text":"import time\nfrom linuxpy.input.device import find_gamepads\n\npad = next(find_gamepads())\nabs = pad.absolute\n\nwith pad:\n while True:\n print(f\"X:{abs.x:>3} | Y:{abs.y:>3} | RX:{abs.rx:>3} | RY:{abs.ry:>3}\", end=\"\\r\", flush=True)\n time.sleep(0.1)\n
"},{"location":"user_guide/midi/","title":"MIDI Sequencer","text":"
"},{"location":"user_guide/midi/#asyncio","title":"asyncio","text":"$ python\n>>> from linuxpy.midi.device import Sequencer\n>>> seq = Sequencer(\"a midi client\")\n>>> seq.open()\n\n>>> seq.version\n1.0.2\n\n>>> seq.client_info\nsnd_seq_client_info(client=128, type=1, name=b'a midi client', filter=0, multicast_filter=b'', event_filter=b'', num_ports=0, event_lost=0, card=-1, pid=1288570)\n\n>>> seq.running_mode\nsnd_seq_running_info(client=0, big_endian=0, cpu_mode=0, pad=0)\n\n>>> seq.system_info\nsnd_seq_system_info(queues=32, clients=192, ports=254, channels=256, cur_clients=3, cur_queues=0)\n
"},{"location":"user_guide/midi/#cli","title":"CLI","text":"$ python -m asyncio\n\n>>> from linuxpy.midi.device import Sequencer\n>>> with Sequencer() as seq:\n... port = seq.create_port()\n... port.connect_from(14, 0)\n... async for event in seq:\n... print(event)\n 14:0 Note on channel=0, note=100, velocity=3, off_velocity=0, duration=0\n 14:0 Clock queue=0, pad=b''\n 14:0 System exclusive F0 61 62 63 F7\n 14:0 Note off channel=0, note=55, velocity=3, off_velocity=0, duration=0\n
from linuxpy.video.device import Device\ncamera = Device.from_id(10)\n
from linuxpy.video.device import Device\ncamera = Device(\"/dev/video10\")\n
or from an existing file object:
from linuxpy.video.device import Device\nwith open(\"/dev/video10\", \"rb+\", buffering=0) as fd:\n camera = Device(fd)\n
Before using video Device
object you need to open it. You can either use the device object as a context manager (prefered):
with Device.from_id(10) as camera:\n ...\n
... or manage call Device.open()
/Device.close()
manually:
camera = Device.from_id(10)\ncamera.open()\ntry:\n ...\nfinally:\n camera.close()\n
"},{"location":"user_guide/video/#capture","title":"Capture","text":"Simple capture without any configuration is possible using the Device object as an infinite iterator:
from linuxpy.video.device import Device, VideoCapture\n\nwith Device.from_id(0) as camera:\n for frame in camera:\n ...\n
The resulting Frame
objects can safely and efficiently be converted to bytes.
To be able to configure the acquisition, you will need to use the VideoCapture
helper. Here is an example with image size and format configuration:
from linuxpy.video.device import Device, VideoCapture\n\nwith Device.from_id(0) as camera:\n capture = VideoCapture(camera)\n capture.set_format(640, 480, \"MJPG\")\n with capture:\n for frame in capture:\n ...\n
Note that VideoCapture
configuration must be done before the capture is started (ie, the the with capture:
statement.)
By default, VideoCapture will use memory map if the device has STREAMING capability and falls back to standard read if not. It is also possible to force a specific reader:
from linuxpy.video.device import Capability, Device, VideoCapture\n\nwith Device.from_id(0) as cam:\n with VideoCapture(cam, source=Capability.READWRITE):\n for frame in capture:\n ...\n
"},{"location":"user_guide/video/#information","title":"Information","text":"Getting information about the device:
>>> from linuxpy.video.device import Device, BufferType\n\n>>> cam = Device.from_id(0)\n>>> cam.open()\n>>> cam.info.card\n'Integrated_Webcam_HD: Integrate'\n\n>>> cam.info.capabilities\n<Capability.STREAMING|EXT_PIX_FORMAT|VIDEO_CAPTURE: 69206017>\n\n>>> cam.info.formats\n[ImageFormat(type=<BufferType.VIDEO_CAPTURE: 1>, description=b'Motion-JPEG',\n flags=<ImageFormatFlag.COMPRESSED: 1>, pixelformat=<PixelFormat.MJPEG: 1196444237>),\n ImageFormat(type=<BufferType.VIDEO_CAPTURE: 1>, description=b'YUYV 4:2:2',\n flags=<ImageFormatFlag.0: 0>, pixelformat=<PixelFormat.YUYV: 1448695129>)]\n\n>>> cam.get_format(BufferType.VIDEO_CAPTURE)\nFormat(width=640, height=480, pixelformat=<PixelFormat.MJPEG: 1196444237>}\n\n>>> for ctrl in cam.controls.values(): print(ctrl)\n<IntegerControl brightness min=0 max=255 step=1 default=128 value=128>\n<IntegerControl contrast min=0 max=255 step=1 default=32 value=32>\n<IntegerControl saturation min=0 max=100 step=1 default=64 value=64>\n<IntegerControl hue min=-180 max=180 step=1 default=0 value=0>\n<BooleanControl white_balance_automatic default=True value=True>\n<IntegerControl gamma min=90 max=150 step=1 default=120 value=120>\n<MenuControl power_line_frequency default=1 value=1>\n<IntegerControl white_balance_temperature min=2800 max=6500 step=1 default=4000 value=4000 flags=inactive>\n<IntegerControl sharpness min=0 max=7 step=1 default=2 value=2>\n<IntegerControl backlight_compensation min=0 max=2 step=1 default=1 value=1>\n<MenuControl auto_exposure default=3 value=3>\n<IntegerControl exposure_time_absolute min=4 max=1250 step=1 default=156 value=156 flags=inactive>\n<BooleanControl exposure_dynamic_framerate default=False value=False>\n\n>>> cam.controls[\"saturation\"]\n<IntegerControl saturation min=0 max=100 step=1 default=64 value=64>\n\n>>> cam.controls[\"saturation\"].id\n9963778\n>>> cam.controls[9963778]\n<IntegerControl saturation min=0 max=100 step=1 default=64 value=64>\n\n>>> cam.controls.brightness\n<IntegerControl brightness min=0 max=255 step=1 default=128 value=128>\n>>> cam.controls.brightness.value = 64\n>>> cam.controls.brightness\n<IntegerControl brightness min=0 max=255 step=1 default=128 value=64>\n
(see also v4l2py-ctl example)
"},{"location":"user_guide/video/#asyncio","title":"asyncio","text":"linuxpy.video is asyncio friendly:
python -m asyncio from linuxpy.video.device import Device with Device.from_id(0) as cam: async for frame in cam: print(f\"frame {len(frame)}\") frame 10224 frame 10304 frame 10136 ...(check basic async and web async examples)
"},{"location":"user_guide/video/#gevent","title":"gevent","text":"linuxpy.video is also gevent friendly:
>>> from linuxpy.io import GeventIO\n>>> from linuxpy.video.device import Device\n>>> with Device.from_id(0, io=GeventIO) as camera:\n... for frame in camera:\n... print(f\"frame {len(frame)}\")\nframe 10224\nframe 10304\nframe 10224\nframe 10136\n...\n
(check basic gevent and web gevent examples)
"},{"location":"user_guide/video/#video-output","title":"Video output","text":"It is possible to write to a video output capable device (ex: v4l2loopback). The following example shows how to grab frames from device 0 and write them to device 10:
>>> from linuxpy.video.device import Device, VideoOutput, BufferType\n>>> dev_source = Device.from_id(0)\n>>> dev_sink = Device.from_id(10)\n>>> with dev_source, dev_target:\n>>> source = VideoCapture(dev_source)\n>>> sink = VideoOutput(dev_sink)\n>>> source.set_format(640, 480, \"MJPG\")\n>>> sink.set_format(640, 480, \"MJPG\")\n>>> with source, sink:\n>>> for frame in source:\n>>> sink.write(frame.data)\n
By default, VideoOutput will use memory map if the device has STREAMING capability and falls back to standard write if not. It is also possible to force a specific writer with VideoOutput(cam, sink=Capability.READWRITE)
:
This is just an example on how to setup v4l2loopback.
Start from scratch:
# Remove kernel module and all devices (no client can be connected at this point)\nsudo modprobe -r v4l2loopback\n\n# Install some devices\nsudo modprobe v4l2loopback video_nr=20,21 card_label=\"Loopback 0\",\"Loopback 1\"\n
"},{"location":"user_guide/video/#references","title":"References","text":"See the linux/videodev2.h
header file for details.
Human friendly interface to linux subsystems using python.
Provides python access to several linux subsystems like V4L2, input and MIDI.
There is experimental, undocumented, incomplete and unstable access to USB.
Need fine control over Webcams, MIDI devices, input devices (joysticks, gamepads, keyboards, mice or even the keyboard light on your laptop)? Linuxpy has your back.
Requirements:
And yes, it is true: there are no python dependencies! Also there are no C libraries dependencies! Everything is done here through direct ioctl, read and write calls. Ain't linux wonderful?
"},{"location":"#goals","title":"Goals","text":"asyncio.to_thread()
From within your favorite python environment:
pip install linuxpy pip install linuxpyTo run the examples you'll need:
$ pip install linuxpy[examples]\n
To develop, run tests, build package, lint, etc you'll need:
$ pip install linuxpy[dev]\n
To run docs you'll need:
$ pip install linuxpy[docs]\n
"},{"location":"#faq","title":"FAQ","text":"Most python libraries try as hard as possible to be platform independent. Why create a library that is explicitly designed to work only on linux?
Well, first of all, one of the goals is to be able to access low level linux device capabilities like video controls. Second, I don't have access to proprietary OS like Windows or MacOS.
If this answer is not enough than think of this library as a low level dependency on linux systems of other libraries that will be concerned with providing a common API on different platforms.
"},{"location":"develop/","title":"Developers corner","text":""},{"location":"develop/#requirements","title":"Requirements","text":"A linux OS and python >= 3.9.
From within your favorite python environment:
$ pip install linuxpy[dev]\n
Additionally, to run the code generation tool you'll need castxml
installed on your system.
On a debian based run:
$ apt install castxml\n
"},{"location":"develop/#code-generation","title":"Code generation","text":"This project uses an internal simple code generator that reads linux kernel header files and produces several raw.py
ctypes based python files for each sub-system.
To re-generate these files for a newer linux kernel (or fix a bug) you'll need linux header files installed on your system + black.
To launch the tool call:
$ python -m linuxpy.codegen.cli\n
"},{"location":"develop/#running-tests","title":"Running tests","text":"Some video tests will only run with a properly configured v4l2loopback
.
$ sudo modprobe v4l2loopback video_nr=199 card_label=\"Loopback 199\"\n
Additionally the user which runs the tests will need read/write access to /dev/video199
. On most systems this can be achieved by adding the user to the video
group:
$ sudo addgroup $USER video\n
Some input tests require the user which runs the tests to have read/write access to /dev/uinput
. On most systems this can be achieved by adding the user to the input
group:
$ sudo addgroup $USER input\n
"},{"location":"api/","title":"LinuxPy reference API","text":"Here's the reference or code API, the classes, functions, parameters, attributes, and all the LinuxPy parts you can use in your applications.
If you want to learn LinuxPy you are much better off reading the LinuxPy User Guide.
"},{"location":"api/input/","title":"Input API","text":""},{"location":"api/input/#linuxpy.input.device","title":"linuxpy.input.device
","text":"Human friendly interface to linux Input subsystem.
The heart of linuxpy MIDI library is the Device
class. The recommended way is to use one of the find methods to create a Device object and use it within a context manager like:
from linuxpy.input.device import find_gamepad\n\nwith find_gamepad() as gamepad:\n print(f\"Gamepad name: {gamepad.name}\")\n
"},{"location":"api/input/#linuxpy.input.device.InputError","title":"InputError
","text":" Bases: Exception
Input error
"},{"location":"api/input/#linuxpy.input.device.Device","title":"Device(*args, **kwargs)
","text":" Bases: BaseDevice
Central linux input subsystem class.
You can create an instance directly if you know the device name:
from linuxpy.input.device import Device\n\nwith Device(\"/dev/input11\") as i11:\n print(i11.name)\n
... but it is generally easier to use the find
helper to get a device with a certain condition. Example:
from linuxpy.input.device import find\n\ntrack_point = find(name=\"TPPS/2 Elan TrackPoint\")\n
"},{"location":"api/input/#linuxpy.input.device.Device.name","title":"name: str
cached
property
","text":"The device name
"},{"location":"api/input/#linuxpy.input.device.Device.version","title":"version: Version
cached
property
","text":"The version
"},{"location":"api/input/#linuxpy.input.device.Device.physical_location","title":"physical_location: str
cached
property
","text":"The physical location
"},{"location":"api/input/#linuxpy.input.device.Device.device_id","title":"device_id: input_id
cached
property
","text":"The device input ID
"},{"location":"api/input/#linuxpy.input.device.Device.capabilities","title":"capabilities
cached
property
","text":"The device capabilities
"},{"location":"api/input/#linuxpy.input.device.Device.active_keys","title":"active_keys
property
","text":"All active keys at the moment of calling this
"},{"location":"api/input/#linuxpy.input.device.Device.x","title":"x
property
","text":"Current absolute X value
"},{"location":"api/input/#linuxpy.input.device.Device.y","title":"y
property
","text":"Current absolute Y value
"},{"location":"api/input/#linuxpy.input.device.Device.z","title":"z
property
","text":"Current absolute Z value
"},{"location":"api/input/#linuxpy.input.device.Device.rx","title":"rx
property
","text":"Current relative X value
"},{"location":"api/input/#linuxpy.input.device.Device.ry","title":"ry
property
","text":"Current relative Y value
"},{"location":"api/input/#linuxpy.input.device.Device.rz","title":"rz
property
","text":"Current relative Z value
"},{"location":"api/input/#linuxpy.input.device.Device.__iter__","title":"__iter__() -> Iterable[InputEvent]
","text":"Build an infinite iterator that streams input events You'll need an open Device before using it:
from linuxpy.input.device import find_mouse\n\nwith find_mouse() as mouse:\n for event in mouse:\n print(event)\n
"},{"location":"api/input/#linuxpy.input.device.Device.__aiter__","title":"__aiter__() -> AsyncIterable[InputEvent]
async
","text":"Build an infinite async iterator that streams input events You'll need an open Device before using it:
import asyncio\nfrom linuxpy.input.device import find_mouse\n\nasync def main():\n with find_mouse() as mouse:\n async for event in mouse:\n print(event)\n\nasyncio.run(main())\n
"},{"location":"api/input/#linuxpy.input.device.Device.get_abs_info","title":"get_abs_info(abs_code)
","text":"Absolute information for the given abs code
"},{"location":"api/input/#linuxpy.input.device.Device.read_event","title":"read_event()
","text":"Read event. Event must be available to read or otherwise will raise an error
"},{"location":"api/input/#linuxpy.input.device.EventReader","title":"EventReader(device: Device, max_queue_size=1)
","text":""},{"location":"api/input/#linuxpy.input.device.EventReader.aread","title":"aread()
async
","text":"Wait for next event or return last event
"},{"location":"api/input/#linuxpy.input.device.BaseUDevice","title":"BaseUDevice(filename=PATH, bus=Bus.USB, vendor_id=1, product_id=1, name='linuxpy emulated device')
","text":" Bases: BaseDevice
A uinput device with no capabilities registered
"},{"location":"api/input/#linuxpy.input.device.iter_input_files","title":"iter_input_files(path: PathLike = '/dev/input', pattern: str = 'event*')
","text":"List readable character devices in the given path.
"},{"location":"api/input/#linuxpy.input.device.event_batch_stream","title":"event_batch_stream(fd) -> Iterable[Sequence[InputEvent]]
","text":"Yields packets of events occurring at the same moment in time.
"},{"location":"api/input/#linuxpy.input.device.async_event_batch_stream","title":"async_event_batch_stream(fd, maxsize: int = 1000) -> AsyncIterable[Sequence[InputEvent]]
async
","text":"Yields packets of events occurring at the same moment in time.
"},{"location":"api/input/#linuxpy.input.device.find","title":"find(find_all: bool = False, custom_match: Optional[Callable] = None, **kwargs) -> Union[Device, Iterable[Device], None]
","text":"If find_all is False:
Find a device follwing the criteria matched by custom_match and kwargs. If no device is found matching the criteria it returns None. Default is to return a random first device.
If find_all is True:
The result is an iterator. Find all devices that match the criteria custom_match and kwargs. If no device is found matching the criteria it returns an empty iterator. Default is to return an iterator over all input devices found on the system.
"},{"location":"api/input/#linuxpy.input.device.find_gamepad","title":"find_gamepad(find_all: bool = False, custom_match: Optional[Callable] = None, **kwargs) -> Union[Device, Iterable[Device], None]
","text":"If find_all is False:
Find a gamepad device follwing the criteria matched by custom_match and kwargs. If no device is found matching the criteria it returns None. Default is to return a random first gamepad.
If find_all is True:
The result is an iterator. Find all gamepad devices that match the criteria custom_match and kwargs. If no gamepad is found matching the criteria it returns an empty iterator. Default is to return an iterator over all gamepad devices found on the system.
"},{"location":"api/input/#linuxpy.input.device.find_keyboard","title":"find_keyboard(find_all: bool = False, custom_match: Optional[Callable] = None, **kwargs) -> Union[Device, Iterable[Device], None]
","text":"If find_all is False:
Find a keyboard device follwing the criteria matched by custom_match and kwargs. If no device is found matching the criteria it returns None. Default is to return a random first keyboard.
If find_all is True:
The result is an iterator. Find all keyboard devices that match the criteria custom_match and kwargs. If no keyboard is found matching the criteria it returns an empty iterator. Default is to return an iterator over all keyboard devices found on the system.
"},{"location":"api/input/#linuxpy.input.device.find_mouse","title":"find_mouse(find_all: bool = False, custom_match: Optional[Callable] = None, **kwargs) -> Union[Device, Iterable[Device], None]
","text":"If find_all is False:
Find a mouse device follwing the criteria matched by custom_match and kwargs. If no device is found matching the criteria it returns None. Default is to return a random first mouse.
If find_all is True:
The result is an iterator. Find all mouse devices that match the criteria custom_match and kwargs. If no mouse is found matching the criteria it returns an empty iterator. Default is to return an iterator over all mouse devices found on the system.
"},{"location":"api/midi/","title":"MIDI API","text":""},{"location":"api/midi/#linuxpy.midi.device","title":"linuxpy.midi.device
","text":"Human friendly interface to linux MIDI subsystem.
The heart of linuxpy MIDI library is the Sequencer
class. Usually you need only one instance of Sequencer for your application. The recommended way is to use it within a context manager like:
with Sequencer(\"My MIDI App\") as midi:\n print(f\"MIDI version: {midi.version}\")\n
which is roughly equivalent to:
midi = Sequencer(\"My MIDI App\")\nmidi.open()\ntry:\n print(f\"MIDI version: {midi.version}\")\nfinally:\n midi.close()\n
Here's a real world example:
from linuxpy.midi.device import Sequencer\n\nwith Sequencer(\"My MIDI App\") as midi:\n print(f\"I'm client {midi.client_id}\")\n print(f\"MIDI version: {midi.version}\")\n port = midi.create_port()\n port.connect_from((0, 1))\n for event in midi:\n print(event)\n
"},{"location":"api/midi/#linuxpy.midi.device.MidiError","title":"MidiError
","text":" Bases: Exception
MIDI error
"},{"location":"api/midi/#linuxpy.midi.device.Sequencer","title":"Sequencer(name: str = 'linuxpy client', **kwargs)
","text":" Bases: BaseDevice
Central MIDI class.
from linuxpy.midi.device import Sequencer\n\nwith Sequencer(\"My MIDI App\") as midi:\n print(f\"I'm client {midi.client_id}\")\n print(f\"MIDI version: {midi.version}\")\n port = midi.create_port()\n port.connect_from((0, 1))\n for event in midi:\n print(event)\n
"},{"location":"api/midi/#linuxpy.midi.device.Sequencer.client_info","title":"client_info: snd_seq_client_info
property
","text":"Current Client information
"},{"location":"api/midi/#linuxpy.midi.device.Sequencer.client","title":"client: Client
property
","text":"Current Client information
"},{"location":"api/midi/#linuxpy.midi.device.Sequencer.running_mode","title":"running_mode: snd_seq_running_info
property
","text":"Current running mode
"},{"location":"api/midi/#linuxpy.midi.device.Sequencer.system_info","title":"system_info: snd_seq_system_info
property
","text":"Current system information
"},{"location":"api/midi/#linuxpy.midi.device.Sequencer.iter_clients","title":"iter_clients: Iterable[Client]
property
","text":"An iterator over all open clients on the system. It returns new Client each time
"},{"location":"api/midi/#linuxpy.midi.device.Sequencer.clients","title":"clients: Sequence[Client]
property
","text":"Returns a new list of all clients on the system
"},{"location":"api/midi/#linuxpy.midi.device.Sequencer.iter_ports","title":"iter_ports: Iterable[Port]
property
","text":"An iterator over all open ports on the system. It returns new Port objects each time
"},{"location":"api/midi/#linuxpy.midi.device.Sequencer.ports","title":"ports: Sequence[Port]
property
","text":"Returns a new list of all open ports on the system. It returns new Port objects each time
"},{"location":"api/midi/#linuxpy.midi.device.Sequencer.__iter__","title":"__iter__() -> Iterable[Event]
","text":"Build an infinite iterator that streams MIDI events from the subscribed ports. You'll need an open sequencer before using it:
from linuxpy.midi.device import Sequencer\n\nwith Sequencer() as midi:\n port = midi.create_port()\n port.connect_from((0, 1))\n for event in midi:\n print(event)\n
"},{"location":"api/midi/#linuxpy.midi.device.Sequencer.__aiter__","title":"__aiter__() -> AsyncIterable[Event]
async
","text":"Build an infinite async iterator that streams MIDI events from the subscribed ports. You'll need an open sequencer before using it:
import asyncio\nfrom linuxpy.midi.device import Sequencer\n\nasync def main():\n with Sequencer() as midi:\n port = midi.create_port()\n port.connect_from((0, 1))\n async for event in midi:\n print(event)\n\nasyncio.run(main())\n
"},{"location":"api/midi/#linuxpy.midi.device.Sequencer.get_client","title":"get_client(client_id: int) -> Client
","text":"Returns a Client for the given ID or raises an error if the client doesn't exist. It returns new Client object each time
"},{"location":"api/midi/#linuxpy.midi.device.Sequencer.get_port","title":"get_port(address: FullPortAddress) -> Port
","text":"Returns a Port for the given address or raises an error if the port doesn't exist. It returns new Port object each time
"},{"location":"api/midi/#linuxpy.midi.device.Sequencer.create_port","title":"create_port(name: str = 'linuxpy port', capabilities: PortCapability = INPUT_OUTPUT, port_type: PortType = PortType.MIDI_GENERIC | PortType.APPLICATION) -> Port
","text":"Create a new local port. By default it will create a MIDI generic application Input/Output port.
"},{"location":"api/midi/#linuxpy.midi.device.Sequencer.delete_port","title":"delete_port(port: Union[int, Port])
","text":"Delete a previously created local port. If the port has any subscriptions they will be closed before the port is deleted
"},{"location":"api/midi/#linuxpy.midi.device.Sequencer.subscribe","title":"subscribe(src: FullPortAddress, dest: FullPortAddress)
","text":"Subscribe a source port to a destination port
"},{"location":"api/midi/#linuxpy.midi.device.Sequencer.unsubscribe","title":"unsubscribe(src: FullPortAddress, dest: FullPortAddress)
","text":"Unsubscribe a previously subscribed source port to a destination port
"},{"location":"api/midi/#linuxpy.midi.device.Sequencer.iter_raw_read","title":"iter_raw_read(max_nb_packets: int = 64) -> Iterable[Event]
","text":"Read list of pending events. If the sequencer is opened in blocking mode and there are no events it blocks until at least one event occurs otherwise as OSError is raised.
Use the read()
call instead because it handles blocking vs non-blocking variants transperently.
raw_read(max_nb_packets=64) -> Sequence[Event]
","text":"Read list of pending events. If there are no events it blocks until at least one event occurs and returns it.
Use the read()
call instead because it handles blocking vs non-blocking variants transperently.
wait_read() -> Sequence[Event]
","text":"Read list of pending events. If there are no events it blocks until at least one event occurs and returns it. This method assumes the internal file descriptior was opened in non-blocking mode.
Use the read()
call instead because it handles blocking vs non-blocking variants transperently
read() -> Sequence[Event]
","text":"Read list of pending events. If there are no events it blocks until at least one event occurs and returns it
"},{"location":"api/midi/#linuxpy.midi.device.Sequencer.write","title":"write(event: Event)
","text":"Send an event message
"},{"location":"api/midi/#linuxpy.midi.device.Sequencer.send","title":"send(port: PortAddress, event_type: Union[str, int, EventType], queue: int = QUEUE_DIRECT, to: Union[FullPortAddress, FullPortAddresses] = SUBSCRIBERS, **kwargs)
","text":"Send a message of the given type from a specific port to the destination address(es). Use kwargs to pass specific event arguments like velocity in a \"note on\" event.
event_type can be an instance of EventType or the equivalent number or a case insensitive string matching the event type (ex: \"noteon\", \"NOTEON\", \"note-on\" or \"note on\").
The following example sends \"note on\" with velocity 45 on port 0 of client 14:
midi.send((14, 0), \"note on\", velocity=45)\n
"},{"location":"api/midi/#linuxpy.midi.device.Client","title":"Client(sequencer: Sequencer, client: snd_seq_client_info)
","text":"MIDI sequencer client. Don't instantiate this object directly Use instead Sequencer.get_client()
name: str
property
","text":"Client name
"},{"location":"api/midi/#linuxpy.midi.device.Client.is_local","title":"is_local: bool
property
","text":"True if the client was created by the MIDI sequencer that it references or False otherwise\"
"},{"location":"api/midi/#linuxpy.midi.device.Client.iter_ports","title":"iter_ports: Iterable[Port]
property
","text":"An iterator over all open ports for this client. It returns new Port each time
"},{"location":"api/midi/#linuxpy.midi.device.Client.ports","title":"ports: Sequence[Port]
property
","text":"Returns a new list of all open ports for this client
"},{"location":"api/midi/#linuxpy.midi.device.Client.__int__","title":"__int__()
","text":"The client ID
"},{"location":"api/midi/#linuxpy.midi.device.Port","title":"Port(sequencer: Sequencer, port: snd_seq_port_info)
","text":"MIDI sequencer port. Don't instantiate this object directly Use instead Sequencer.get_port()
name: str
property
","text":"Port name
"},{"location":"api/midi/#linuxpy.midi.device.Port.is_local","title":"is_local: bool
property
","text":"True if the port was created by the MIDI sequencer that it references or False otherwise\"
"},{"location":"api/midi/#linuxpy.midi.device.Port.client_id","title":"client_id: int
property
","text":"The client ID
"},{"location":"api/midi/#linuxpy.midi.device.Port.port_id","title":"port_id: int
property
","text":"The port ID
"},{"location":"api/midi/#linuxpy.midi.device.Port.type","title":"type: PortType
property
","text":"The port type
"},{"location":"api/midi/#linuxpy.midi.device.Port.capability","title":"capability: PortCapability
property
","text":"The port capabilities
"},{"location":"api/midi/#linuxpy.midi.device.Port.address","title":"address: snd_seq_addr
property
","text":"The port address
"},{"location":"api/midi/#linuxpy.midi.device.Port.__int__","title":"__int__()
","text":"The port ID
"},{"location":"api/midi/#linuxpy.midi.device.Port.connect_from","title":"connect_from(src: FullPortAddress)
","text":"Connect this port to a remote port. After connecting, this port will receive events originating from the source port.
Example:
from linuxpy.midi.device import Sequencer\n\nwith Sequencer() as midi:\n port = midi.create_port()\n port.connect_from((0, 1))\n for event in midi:\n print(event)\n
"},{"location":"api/midi/#linuxpy.midi.device.Port.disconnect_from","title":"disconnect_from(src: FullPortAddress)
","text":"Disconnect this port from a previously connected source port.
"},{"location":"api/midi/#linuxpy.midi.device.Port.connect_to","title":"connect_to(dest: FullPortAddress)
","text":"Connect this port to a remote port. After connecting, events originating from this port will be sent to the destination port.
Example:
from linuxpy.midi.device import Sequencer\n\nwith Sequencer() as midi:\n port = midi.create_port()\n # Assume 14:0 is Midi Through\n port.connect_to((14, 0))\n port.send(\"note on\", note=11, velocity=10)\n
"},{"location":"api/midi/#linuxpy.midi.device.Port.disconnect_to","title":"disconnect_to(dest: FullPortAddress)
","text":"Disconnect this port from a previously connected destination port.
"},{"location":"api/midi/#linuxpy.midi.device.Port.delete","title":"delete()
","text":"Delete this port. Raises MidiError if port is not local. Any subscriptions are canceled before the port is deleted.
"},{"location":"api/midi/#linuxpy.midi.device.Port.send","title":"send(event_type: Union[str, int, EventType], **kwargs)
","text":"Send a message of the given type from to the destination address(es). Use kwargs to pass specific event arguments like velocity in a \"note on\" event.
event_type can be an instance of EventType or the equivalent number or a case insensitive string matching the event type (ex: \"noteon\", \"NOTEON\", \"note-on\" or \"note on\").
The following example sends \"note on\" on note 42, with velocity 45:
port.send(\"note on\", note=42, velocity=45)\n
"},{"location":"api/midi/#linuxpy.midi.device.Event","title":"Event(event: snd_seq_event)
","text":"Event message object result of listening on a sequencer
"},{"location":"api/midi/#linuxpy.midi.device.Event.__bytes__","title":"__bytes__()
","text":"Serialize the Event in a bytes ready to be sent
"},{"location":"api/midi/#linuxpy.midi.device.Event.new","title":"new(etype: EventT, **kwargs)
classmethod
","text":"Create new Event of the given type
"},{"location":"api/midi/#linuxpy.midi.device.to_address","title":"to_address(addr: FullPortAddress) -> snd_seq_addr
","text":"Convert to low level snd_seq_addr
"},{"location":"api/midi/#linuxpy.midi.device.event_stream","title":"event_stream(sequencer: Sequencer) -> Iterable[Event]
","text":"Infinite stream of events coming from the given sequencer
"},{"location":"api/midi/#linuxpy.midi.device.async_event_stream","title":"async_event_stream(sequencer: Sequencer, maxsize: int = 10) -> AsyncIterable[Event]
async
","text":"Infinite async stream of events coming from the given sequencer
"},{"location":"api/video/","title":"Video API","text":""},{"location":"api/video/#linuxpy.video.device","title":"linuxpy.video.device
","text":"Human friendly interface to V4L2 (Video 4 Linux 2) subsystem.
"},{"location":"api/video/#linuxpy.video.device.V4L2Error","title":"V4L2Error
","text":" Bases: Exception
Video for linux 2 error
"},{"location":"api/video/#linuxpy.video.device.Frame","title":"Frame(data: bytes, buff: raw.v4l2_buffer, format: Format)
","text":"The resulting object from an acquisition.
"},{"location":"api/video/#linuxpy.video.device.EventReader","title":"EventReader(device: Device, max_queue_size=100)
","text":""},{"location":"api/video/#linuxpy.video.device.EventReader.aread","title":"aread()
async
","text":"Wait for next event or return last event in queue
"},{"location":"api/video/#linuxpy.video.device.FrameReader","title":"FrameReader(device: Device, raw_read: Callable[[], Buffer], max_queue_size: int = 1)
","text":""},{"location":"api/video/#linuxpy.video.device.FrameReader.aread","title":"aread() -> Frame
async
","text":"Wait for next frame or return last frame
"},{"location":"api/video/#linuxpy.video.device.create_buffer","title":"create_buffer(fd, buffer_type: BufferType, memory: Memory) -> raw.v4l2_buffer
","text":"request + query buffers
"},{"location":"api/video/#linuxpy.video.device.create_buffers","title":"create_buffers(fd, buffer_type: BufferType, memory: Memory, count: int) -> list[raw.v4l2_buffer]
","text":"request + query buffers
"},{"location":"api/video/#linuxpy.video.device.create_mmap_buffers","title":"create_mmap_buffers(fd, buffer_type: BufferType, memory: Memory, count: int) -> list[mmap.mmap]
","text":"create buffers + mmap_from_buffer
"},{"location":"api/video/#linuxpy.video.device.iter_video_output_files","title":"iter_video_output_files(path: PathLike = '/dev') -> Iterable[Path]
","text":"Some drivers (ex: v4l2loopback) don't report being output capable so that apps like zoom recognize them as valid capture devices so some results might be missing
"},{"location":"user_guide/","title":"User guide","text":"This tutorial shows you how to use LinuxPy with most of its features.
"},{"location":"user_guide/input/","title":"Input","text":"Human friendly interface to the Linux Input subsystem.
API not documented yet. Just this example:
import time\nfrom linuxpy.input.device import find_gamepads\n\npad = next(find_gamepads())\nabs = pad.absolute\n\nwith pad:\n while True:\n print(f\"X:{abs.x:>3} | Y:{abs.y:>3} | RX:{abs.rx:>3} | RY:{abs.ry:>3}\", end=\"\\r\", flush=True)\n time.sleep(0.1)\n
"},{"location":"user_guide/input/#asyncio","title":"asyncio","text":"python -m asyncio from linuxpy.input.device import find_gamepad with find_gamepad() as pad: async for event in pad: print(event) InputEvent(time=1697520475.348099, type=<EventType.SYN: 0>, code=<Synchronization.REPORT: 0>, value=0) InputEvent(time=1697520475.361564, type=<EventType.REL: 2>, code=<Relative.X: 0>, value=-1) InputEvent(time=1697520475.361564, type=<EventType.REL: 2>, code=<Relative.Y: 1>, value=1) InputEvent(time=1697520475.361564, type=<EventType.SYN: 0>, code=<Synchronization.REPORT: 0>, value=0) InputEvent(time=1697520475.371128, type=<EventType.REL: 2>, code=<Relative.X: 0>, value=-1) InputEvent(time=1697520475.371128, type=<EventType.SYN: 0>, code=<Synchronization.REPORT: 0>, value=0) ..."},{"location":"user_guide/input/#references","title":"References","text":"Without further ado:
python from linuxpy.midi.device import Sequencer with Sequencer() as seq: port = seq.create_port() port.connect_from(14, 0) for event in seq: print(event) 14:0 Note on channel=0, note=100, velocity=3, off_velocity=0, duration=0 14:0 Clock queue=0, pad=b'' 14:0 System exclusive F0 61 62 63 F7 14:0 Note off channel=0, note=55, velocity=3, off_velocity=0, duration=0"},{"location":"user_guide/midi/#system-information","title":"System information","text":"$ python\n>>> from linuxpy.midi.device import Sequencer\n>>> seq = Sequencer(\"a midi client\")\n>>> seq.open()\n\n>>> seq.version\n1.0.2\n\n>>> seq.client_info\nsnd_seq_client_info(client=128, type=1, name=b'a midi client', filter=0, multicast_filter=b'', event_filter=b'', num_ports=0, event_lost=0, card=-1, pid=1288570)\n\n>>> seq.running_mode\nsnd_seq_running_info(client=0, big_endian=0, cpu_mode=0, pad=0)\n\n>>> seq.system_info\nsnd_seq_system_info(queues=32, clients=192, ports=254, channels=256, cur_clients=3, cur_queues=0)\n
"},{"location":"user_guide/midi/#asyncio","title":"asyncio","text":"asyncio is a first class citizen to linuxpy.midi:
$ python -m asyncio\n\n>>> from linuxpy.midi.device import Sequencer\n>>> with Sequencer() as seq:\n... port = seq.create_port()\n... port.connect_from(14, 0)\n... async for event in seq:\n... print(event)\n 14:0 Note on channel=0, note=100, velocity=3, off_velocity=0, duration=0\n 14:0 Clock queue=0, pad=b''\n 14:0 System exclusive F0 61 62 63 F7\n 14:0 Note off channel=0, note=55, velocity=3, off_velocity=0, duration=0\n
"},{"location":"user_guide/midi/#cli","title":"CLI","text":"A basic CLI is provided that allows listing MIDI clients & ports and dumping MIDI sequencer events:
List all ports:
python -m linuxpy.midi.cli ls Port Client Port Type Capabilities 0:0 System Timer 0 SR, W, R 0:1 System Announce 0 SR, R 14:0 Midi Through Midi Through Port-0 PORT, SOFTWARE, MIDI_GENERIC SW, SR, W, RListen to events on selected port(s):
python -m linuxpy.midi.cli listen 0:1 14:0 0:1 Port subscribed sender=(client=0, port=1), dest=(client=128, port=0) 0:1 Port start client=128, port=1 0:1 Port subscribed sender=(client=14, port=0), dest=(client=128, port=1) 0:1 Client start client=130, port=0 0:1 Port start client=130, port=0 0:1 Port subscribed sender=(client=130, port=0), dest=(client=14, port=0) 14:0 Note on channel=0, note=100, velocity=3, off_velocity=0, duration=0 0:1 Port unsubscribed sender=(client=130, port=0), dest=(client=14, port=0) 0:1 Port exit client=130, port=0 0:1 Client exit client=130, port=0 0:1 Port exit client=129, port=0 0:1 Client exit client=129, port=0 0:1 Client start client=129, port=0 0:1 Port start client=129, port=0 14:0 Note on channel=0, note=100, velocity=3, off_velocity=0, duration=0 14:0 Note on channel=0, note=0, velocity=255, off_velocity=0, duration=0 14:0 Note on channel=0, note=0, velocity=255, off_velocity=0, duration=0"},{"location":"user_guide/video/","title":"Video","text":"Human friendly interface to the Video for Linux 2 (V4L2) subsystem.
Without further ado:
python from linuxpy.video.device import Device with Device.from_id(0) as cam: for i, frame in enumerate(cam): print(f\"frame #{i}: {len(frame)} bytes\") frame #0: 54630 bytes frame #1: 50184 bytes frame #2: 44054 bytes frame #3: 42822 bytes frame #4: 42116 bytes frame #5: 41868 bytes frame #6: 41322 bytes frame #7: 40896 bytes frame #8: 40844 bytes frame #9: 40714 bytes frame #10: 40662 bytes ..."},{"location":"user_guide/video/#device-creation","title":"Device creation","text":"Create a device object from an ID:
from linuxpy.video.device import Device\ncamera = Device.from_id(10)\n
from a filename:
from linuxpy.video.device import Device\ncamera = Device(\"/dev/video10\")\n
or from an existing file object:
from linuxpy.video.device import Device\nwith open(\"/dev/video10\", \"rb+\", buffering=0) as fd:\n camera = Device(fd)\n
Before using video Device
object you need to open it (except in the example directly above when creating a device from a file object). You can either use the device object as a context manager (prefered):
with Device.from_id(10) as camera:\n ...\n
The Device object is a reusable, reentrant but not thread safe context manager. This means that Device object can not only be used in multiple with statements, but may also be used inside a with statement that is already using the same context manager.
So the following examples will work just fine:
with Device.from_id(10) as camera:\n ...\n with camera:\n ...\n\nwith camera:\n ...\n
Alternatively, you can manage calls Device.open()
/Device.close()
manually:
camera = Device.from_id(10)\ncamera.open()\ntry:\n ...\nfinally:\n camera.close()\n
"},{"location":"user_guide/video/#capture","title":"Capture","text":"Simple capture without any configuration is possible using the Device object as an infinite iterator:
from linuxpy.video.device import Device, VideoCapture\n\nwith Device.from_id(0) as camera:\n for frame in camera:\n ...\n
The resulting Frame
objects can safely and efficiently be converted to bytes.
To be able to configure the acquisition, you will need to use the VideoCapture
helper. Here is an example with image size and format configuration:
from linuxpy.video.device import Device, VideoCapture\n\nwith Device.from_id(0) as camera:\n capture = VideoCapture(camera)\n capture.set_format(640, 480, \"MJPG\")\n with capture:\n for frame in capture:\n ...\n
Note that VideoCapture
configuration must be done before the capture is started (ie, the with capture:
statement.)
By default, VideoCapture will use memory map if the device has STREAMING capability and falls back to standard read if not. It is also possible to force a specific reader:
from linuxpy.video.device import Capability, Device, VideoCapture\n\nwith Device.from_id(0) as cam:\n with VideoCapture(cam, source=Capability.READWRITE):\n for frame in capture:\n ...\n
"},{"location":"user_guide/video/#information","title":"Information","text":"Getting information about the device:
>>> from linuxpy.video.device import Device, BufferType\n\n>>> cam = Device.from_id(0)\n>>> cam.open()\n>>> cam.info.card\n'Integrated_Webcam_HD: Integrate'\n\n>>> cam.info.capabilities\n<Capability.STREAMING|EXT_PIX_FORMAT|VIDEO_CAPTURE: 69206017>\n\n>>> cam.info.formats\n[ImageFormat(type=<BufferType.VIDEO_CAPTURE: 1>, description=b'Motion-JPEG',\n flags=<ImageFormatFlag.COMPRESSED: 1>, pixelformat=<PixelFormat.MJPEG: 1196444237>),\n ImageFormat(type=<BufferType.VIDEO_CAPTURE: 1>, description=b'YUYV 4:2:2',\n flags=<ImageFormatFlag.0: 0>, pixelformat=<PixelFormat.YUYV: 1448695129>)]\n\n>>> cam.get_format(BufferType.VIDEO_CAPTURE)\nFormat(width=640, height=480, pixelformat=<PixelFormat.MJPEG: 1196444237>}\n\n>>> for ctrl in cam.controls.values(): print(ctrl)\n<IntegerControl brightness min=0 max=255 step=1 default=128 value=128>\n<IntegerControl contrast min=0 max=255 step=1 default=32 value=32>\n<IntegerControl saturation min=0 max=100 step=1 default=64 value=64>\n<IntegerControl hue min=-180 max=180 step=1 default=0 value=0>\n<BooleanControl white_balance_automatic default=True value=True>\n<IntegerControl gamma min=90 max=150 step=1 default=120 value=120>\n<MenuControl power_line_frequency default=1 value=1>\n<IntegerControl white_balance_temperature min=2800 max=6500 step=1 default=4000 value=4000 flags=inactive>\n<IntegerControl sharpness min=0 max=7 step=1 default=2 value=2>\n<IntegerControl backlight_compensation min=0 max=2 step=1 default=1 value=1>\n<MenuControl auto_exposure default=3 value=3>\n<IntegerControl exposure_time_absolute min=4 max=1250 step=1 default=156 value=156 flags=inactive>\n<BooleanControl exposure_dynamic_framerate default=False value=False>\n\n>>> cam.controls[\"saturation\"]\n<IntegerControl saturation min=0 max=100 step=1 default=64 value=64>\n\n>>> cam.controls[\"saturation\"].id\n9963778\n>>> cam.controls[9963778]\n<IntegerControl saturation min=0 max=100 step=1 default=64 value=64>\n\n>>> cam.controls.brightness\n<IntegerControl brightness min=0 max=255 step=1 default=128 value=128>\n>>> cam.controls.brightness.value = 64\n>>> cam.controls.brightness\n<IntegerControl brightness min=0 max=255 step=1 default=128 value=64>\n
(see also v4l2py-ctl example)
"},{"location":"user_guide/video/#asyncio","title":"asyncio","text":"linuxpy.video is asyncio friendly:
python -m asyncio from linuxpy.video.device import Device with Device.from_id(0) as cam: async for frame in cam: print(f\"frame {len(frame)}\") frame 10224 frame 10304 frame 10136 ...(check basic async and web async examples)
"},{"location":"user_guide/video/#gevent","title":"gevent","text":"linuxpy.video is also gevent friendly:
>>> from linuxpy.io import GeventIO\n>>> from linuxpy.video.device import Device\n>>> with Device.from_id(0, io=GeventIO) as camera:\n... for frame in camera:\n... print(f\"frame {len(frame)}\")\nframe 10224\nframe 10304\nframe 10224\nframe 10136\n...\n
(check basic gevent and web gevent examples)
"},{"location":"user_guide/video/#video-output","title":"Video output","text":"It is possible to write to a video output capable device (ex: v4l2loopback). The following example shows how to grab frames from device 0 and write them to device 10:
>>> from linuxpy.video.device import Device, VideoOutput, BufferType\n>>> dev_source = Device.from_id(0)\n>>> dev_sink = Device.from_id(10)\n>>> with dev_source, dev_target:\n>>> source = VideoCapture(dev_source)\n>>> sink = VideoOutput(dev_sink)\n>>> source.set_format(640, 480, \"MJPG\")\n>>> sink.set_format(640, 480, \"MJPG\")\n>>> with source, sink:\n>>> for frame in source:\n>>> sink.write(frame.data)\n
By default, VideoOutput will use memory map if the device has STREAMING capability and falls back to standard write if not. It is also possible to force a specific writer with VideoOutput(cam, sink=Capability.READWRITE)
:
This is just an example on how to setup v4l2loopback.
Start from scratch:
# Remove kernel module and all devices (no client can be connected at this point)\nsudo modprobe -r v4l2loopback\n\n# Install some devices\nsudo modprobe v4l2loopback video_nr=20,21 card_label=\"Loopback 0\",\"Loopback 1\"\n
"},{"location":"user_guide/video/#references","title":"References","text":"See the linux/videodev2.h
header file for details.
Human friendly interface to the Video for Linux 2 (V4L2) subsystem.
+Without further ado:
Before using video Device
object you need to open it.
+
Before using video Device
object you need to open it (except in the
+example directly above when creating a device from a file object).
You can either use the device object as a context manager (prefered):
with Device.from_id(10) as camera:
...
... or manage call Device.open()
/Device.close()
manually:
The Device object is a reusable, reentrant but not thread safe context +manager. This means that Device object can not only be used in multiple with +statements, but may also be used inside a with statement that is already +using the same context manager.
+So the following examples will work just fine:
+with Device.from_id(10) as camera:
+ ...
+ with camera:
+ ...
+
+with camera:
+ ...
+
Alternatively, you can manage calls Device.open()
/Device.close()
manually:
camera = Device.from_id(10)
camera.open()
try:
@@ -872,7 +887,7 @@ Capture...
Note that VideoCapture
configuration must be done before the capture is started
-(ie, the the with capture:
statement.)
with capture:
statement.)
By default, VideoCapture will use memory map if the device has STREAMING capability and falls back to standard read if not. It is also possible to force a specific reader:
diff --git a/user_guide/video_demo.svg b/user_guide/video_demo.svg new file mode 100644 index 0000000..79754d2 --- /dev/null +++ b/user_guide/video_demo.svg @@ -0,0 +1,67 @@ +