> This is the documentation for LibreYOLO 1.6.0. Current version: https://www.libreyolo.com/docs/predict/sources

# Prediction sources
The source argument is classified before anything is opened, so one call handles a JPEG, a folder, an MP4, a webcam index, an RTSP URL, a screen region, or a list of cameras.
Verified against LibreYOLO v1.6.0.

## How a source is classified

`classify_source` inspects the value before anything is opened or downloaded,
in this order. The first rule that matches wins.

| Source | Read as |
|---|---|
| `"screen"`, `"screen 1"`, `"screen 1 100 200 512 256"` | Screen capture |
| A non-negative `int`, or a digit string with no file of that name | Webcam |
| An `rtsp://`, `rtmp://`, `tcp://` or `udp://` URL | Network stream |
| An `http(s)://` URL whose path ends in `.m3u8` | Network stream |
| A YouTube page URL | Network stream |
| A list or tuple whose entries are all live or video | Several live streams |
| Any other list or tuple | Batch of images |
| A path ending in `.streams` | Several live streams |
| A path with a video extension | Video file |
| An existing directory | Folder of images |
| Anything else | Single image |

A list that mixes live sources with images raises `TypeError`. A negative
webcam index raises `ValueError`.

The classifier never touches the network, so a mistyped URL surfaces when the
capture opens, not when `predict` is called.

## Images

**One image**

```python
from libreyolo import LibreYOLO, SAMPLE_IMAGE

model = LibreYOLO("LibreYOLO9s.pt")

# A single image source returns one Results, not a list.
result = model(SAMPLE_IMAGE)
print(len(result.boxes), "detections")
```

**In-memory images**

```python
import numpy as np
from PIL import Image

from libreyolo import LibreYOLO, SAMPLE_IMAGE

model = LibreYOLO("LibreYOLO9s.pt")

pil_image = Image.open(SAMPLE_IMAGE)
array = np.asarray(pil_image)
raw_bytes = open(SAMPLE_IMAGE, "rb").read()

# NumPy arrays are read as BGR unless told otherwise; this one is RGB.
result = model(array, color_format="rgb")
print(type(array).__name__, len(result.boxes))

for source in (pil_image, raw_bytes):
    result = model(source)
    print(type(source).__name__, len(result.boxes))
```

**A folder**

```python
from pathlib import Path
from PIL import Image

from libreyolo import LibreYOLO, SAMPLE_IMAGE

folder = Path("sample_folder")
folder.mkdir(exist_ok=True)
image = Image.open(SAMPLE_IMAGE)
for index in range(3):
    image.save(folder / f"frame_{index}.jpg")

model = LibreYOLO("LibreYOLO9s.pt")

# A folder returns a list, one Results per image, sorted by path.
results = model(str(folder))
print(len(results), "images")
```

**CLI**

```bash
libreyolo predict model=LibreYOLO9s.pt \
  source=https://raw.githubusercontent.com/LibreYOLO/libreyolo/release/libreyolo/assets/parkour.jpg
```

A single image source accepts seven types.

| Type | Read as |
|---|---|
| `str` or `pathlib.Path` | Local file, `http(s)://`, `s3://` or `gs://` |
| `PIL.Image.Image` | Converted to RGB |
| `numpy.ndarray` | 2D grayscale, or 3D HWC or CHW in BGR order; a 4D array is a batch |
| `torch.Tensor` | CHW or NCHW, read as RGB; a 4D tensor is a batch |
| `bytes` | Encoded image data |
| `io.BytesIO` | Encoded image data |

A 4D array or tensor returns a list with one `Results` per image.

Everything is converted to RGB before preprocessing. NumPy arrays are the one
case where channel order is ambiguous, so `color_format` controls it:
`"auto"` (the default) and `"bgr"` read the array as BGR, the order OpenCV
returns, and `"rgb"` leaves it as-is, which is what an array made from a PIL
image needs.

Float arrays are rescaled by their own range: values at or below `1.0` are
multiplied by 255, higher values are clipped into `[0, 255]`. An RGBA array
drops its alpha channel.

Remote paths need one package each. `requests`, for `http(s)://`, comes with the
base install; `boto3` for `s3://` and `gcsfs` for `gs://` do not.

Tracking accepts images, filename-sorted folders, lists, tuples and lazy image iterators as consecutive frames. Pass `fps=30.0` to define image-sequence timing and `color_format="auto"` to select input interpretation. See [tracking](/docs/v1.6.0/tasks/object-tracking).

## Folders

A directory is scanned recursively and sorted, and every file with one of these
suffixes becomes an image: `.jpg`, `.jpeg`, `.png`, `.gif`, `.webp`, `.bmp`,
`.tiff`, `.tif`. Anything else in the folder is skipped. An empty folder returns
an empty list rather than raising.

Folders and lists are the two sources that accept `batch`, which runs one
stacked forward pass per chunk on families that support it. See
[Inference performance](/docs/v1.6.0/predict/performance).

## Video files

**A video file (supply your own clip)**

```python
from libreyolo import LibreYOLO

model = LibreYOLO("LibreYOLO9s.pt")

# Replace clip.mp4 with a video file on disk.
for result in model("clip.mp4", stream=True):
    print(result.frame_idx, len(result.boxes))
```

**Every third frame, written to disk**

```python
from libreyolo import LibreYOLO

model = LibreYOLO("LibreYOLO9s.pt")

for result in model("clip.mp4", stream=True, vid_stride=3, save=True):
    pass
```

A path counts as video when its suffix is one of `.3g2`, `.3gp`, `.asf`,
`.avi`, `.dav`, `.f4v`, `.flv`, `.gif`, `.h264`, `.h265`, `.hevc`, `.m2ts`,
`.m4v`, `.mkv`, `.mov`, `.mp4`, `.mpeg`, `.mpg`, `.mts`, `.mxf`, `.ogv`, `.ts`,
`.vob`, `.wmv`, `.webm`.

`.gif` appears in both lists. A `.gif` path passed directly to `predict` is
opened as video, because the video check runs first; a `.gif` sitting inside a
scanned folder is loaded as a still image.

`vid_stride` processes every N-th frame and defaults to `1`. Without
`stream=True` the whole video is decoded into a list, and anything above 500
frames after striding emits a warning suggesting `stream=True`.

Each `Results` from a video carries `frame_idx`.

Successful video encoding falls back to an available codec when H.264 cannot open. The fallback is logged at INFO and cached per codec and canvas only after another codec succeeds.

## Webcams, network streams and YouTube

**Webcam (needs a camera attached)**

```python
import itertools

from libreyolo import LibreYOLO

model = LibreYOLO("LibreYOLO9s.pt")

# Webcam index 0. Live sources never end, so bound the loop.
for result in itertools.islice(model(0, stream=True), 100):
    print(result.frame_idx, len(result.boxes))
```

**RTSP (needs a reachable camera URL)**

```python
import itertools

from libreyolo import LibreYOLO

model = LibreYOLO("LibreYOLO9s.pt")
source = "rtsp://user:password@192.168.1.64:554/Streaming/Channels/101"

for result in itertools.islice(model(source, stream=True), 100):
    print(result.frame_idx, len(result.boxes))
```

Live sources are unbounded, so they require `stream=True`. Without it,
`predict` raises `ValueError` rather than trying to collect an endless list.

Frames are read on a background thread, one per capture. By default the queue
holds only the newest frame, so a model slower than the camera skips frames
instead of falling behind. `stream_buffer=True` keeps every captured frame,
which preserves them at the cost of growing latency.

A webcam index is an `int` or a digit string. On Windows the capture is opened
through the DirectShow backend first and falls back to the default backend if
that fails.

YouTube page URLs are resolved to a direct media URL without downloading the
video, which needs `yt-dlp`:

```bash
pip install "libreyolo[stream]"
```

Stream labels are redacted before they are logged or used as filenames. A URL
carrying credentials appears as `user:***@host`, and query strings are dropped
from direct stream labels because signed URLs and bearer tokens live there. A
YouTube video id is kept, since it is not a credential.

## Several cameras at once

**A .streams file (supply your own cameras)**

```python
import itertools
from pathlib import Path

from libreyolo import LibreYOLO

Path("cameras.streams").write_text(
    "# one source per line, blank lines and comments are skipped\n"
    "rtsp://192.168.1.64:554/Streaming/Channels/101\n"
    "rtsp://192.168.1.65:554/Streaming/Channels/101\n",
    encoding="utf-8",
)

model = LibreYOLO("LibreYOLO9s.pt")
for result in itertools.islice(model("cameras.streams", stream=True), 100):
    print(result.frame_idx, len(result.boxes))
```

**A list of cameras**

```python
import itertools

from libreyolo import LibreYOLO

model = LibreYOLO("LibreYOLO9s.pt")
cameras = [0, "rtsp://192.168.1.64:554/Streaming/Channels/101"]

for result in itertools.islice(model(cameras, stream=True), 100):
    print(result.frame_idx, len(result.boxes))
```

A `.streams` file is one source per line. Blank lines and lines starting with
`#` are ignored. Every remaining line must itself be a webcam index, a network
stream, a YouTube URL or a video file path; anything else raises `ValueError`
naming the line number. An empty file raises rather than starting with no
cameras.

A list or tuple of live sources does the same thing without a file.

Each capture gets its own thread, and frames from all of them are multiplexed
into one generator. Every pass polls each active stream and yields whatever is
ready, so a slow camera does not hold up a fast one, and frames from different
cameras interleave. A stream that ends drops out of the rotation while the
others continue.

## Screen capture

**One screenshot (needs mss and a desktop session)**

```python
from libreyolo import LibreYOLO

model = LibreYOLO("LibreYOLO9s.pt")

# Without stream=True this grabs a single frame.
result = model("screen")
print(len(result.boxes), "detections")
```

**A region of one monitor, continuously**

```python
import itertools

from libreyolo import LibreYOLO

model = LibreYOLO("LibreYOLO9s.pt")

# "screen <monitor> <left> <top> <width> <height>"
for result in itertools.islice(model("screen 1 100 200 512 256", stream=True), 50):
    print(len(result.boxes))
```

A screen source is the word `screen` followed by zero, one, four or five
integers. Any other count raises `ValueError`.

| Form | Captures |
|---|---|
| `"screen"` | Every monitor, merged |
| `"screen 1"` | Monitor 1 |
| `"screen 100 200 512 256"` | A box on the merged desktop |
| `"screen 1 100 200 512 256"` | A box on monitor 1 |

Box coordinates are `left top width height`, relative to the top-left corner of
the chosen monitor. A screen source reports its frame rate as 30 divided by
`vid_stride`, which is the rate a saved video is written at. Capture needs the
`mss` package:

```bash
pip install mss
```

Without `stream=True`, a screen source grabs one frame and returns a single
`Results`, which is the screenshot equivalent of predicting on an image file.
With `stream=True` it captures until the loop is broken.

## What predict returns

The shape of the return value depends on the source and on `stream`.

| Source | `stream=False` | `stream=True` |
|---|---|---|
| Single image | One `Results` | Generator of one `Results` |
| List of images | List of `Results` | Generator |
| Folder | List of `Results` | Generator |
| Video file | List of `Results` | Generator |
| Screen | One `Results` | Generator, unbounded |
| Webcam, network stream, `.streams` | `ValueError` | Generator, unbounded |

A single image returns the `Results` object itself. Indexing it selects a
detection, not an image, so `result[0]` on a single-image prediction is the
first box rather than the first picture. For what those objects carry, see
[Working with results](/docs/v1.6.0/predict/results).

## Where save writes

`save=True` writes annotated output next to a run directory rather than
returning it.

Images go to an auto-incrementing `runs/detect/predict`, `runs/detect/predict2`
and so on, keeping the source filename. Every image in one process lands in the
same directory, so two input folders holding the same filename overwrite each
other. In-memory images have no filename to reuse. A single one is saved as
`inference`, so repeated calls overwrite it; a list or batch is numbered
`image0`, `image1` and so on.

Video and live sources are written as a single `.mp4` named after the source.

`output_path` overrides the directory. A path with a suffix is treated as a
file, a path without one as a directory. `output_file_format` selects the
still-image encoding and accepts `jpg`, `png` or `webp`.

After an image is saved, the written path is also attached to the result as
`result.saved_path`.
