- Add IPFSHLSOutput class that uploads segments to IPFS as they're created - Update streaming task to use IPFS HLS output for distributed streaming - Add /ipfs-stream endpoint to get IPFS playlist URL - Update /stream endpoint to redirect to IPFS when available - Add GPU persistence mode (STREAMING_GPU_PERSIST=1) to keep frames on GPU - Add hardware video decoding (NVDEC) support for faster video processing - Add GPU-accelerated primitive libraries: blending_gpu, color_ops_gpu, geometry_gpu - Add streaming_gpu module with GPUFrame class for tracking CPU/GPU data location - Add Dockerfile.gpu for building GPU-enabled worker image Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
151 lines
3.7 KiB
Python
151 lines
3.7 KiB
Python
"""
|
|
Image Primitives Library
|
|
|
|
Basic image operations: dimensions, pixels, resize, crop, paste.
|
|
"""
|
|
import numpy as np
|
|
import cv2
|
|
|
|
|
|
def prim_width(img):
|
|
if isinstance(img, (list, tuple)):
|
|
raise TypeError(f"image:width expects an image array, got {type(img).__name__} with {len(img)} elements")
|
|
return img.shape[1]
|
|
|
|
|
|
def prim_height(img):
|
|
if isinstance(img, (list, tuple)):
|
|
import sys
|
|
print(f"DEBUG image:height got list: {img[:3]}... (types: {[type(x).__name__ for x in img[:3]]})", file=sys.stderr)
|
|
raise TypeError(f"image:height expects an image array, got {type(img).__name__} with {len(img)} elements: {img}")
|
|
return img.shape[0]
|
|
|
|
|
|
def prim_make_image(w, h, color=None):
|
|
"""Create a new image filled with color (default black)."""
|
|
if color is None:
|
|
color = [0, 0, 0]
|
|
img = np.zeros((h, w, 3), dtype=np.uint8)
|
|
img[:] = color
|
|
return img
|
|
|
|
|
|
def prim_copy(img):
|
|
return img.copy()
|
|
|
|
|
|
def prim_pixel(img, x, y):
|
|
"""Get pixel color at (x, y) as [r, g, b]."""
|
|
h, w = img.shape[:2]
|
|
if 0 <= x < w and 0 <= y < h:
|
|
return list(img[int(y), int(x)])
|
|
return [0, 0, 0]
|
|
|
|
|
|
def prim_set_pixel(img, x, y, color):
|
|
"""Set pixel at (x, y) to color, returns modified image."""
|
|
result = img.copy()
|
|
h, w = result.shape[:2]
|
|
if 0 <= x < w and 0 <= y < h:
|
|
result[int(y), int(x)] = color
|
|
return result
|
|
|
|
|
|
def prim_sample(img, x, y):
|
|
"""Bilinear sample at float coordinates, returns [r, g, b] as floats."""
|
|
h, w = img.shape[:2]
|
|
x = max(0, min(w - 1.001, x))
|
|
y = max(0, min(h - 1.001, y))
|
|
|
|
x0, y0 = int(x), int(y)
|
|
x1, y1 = min(x0 + 1, w - 1), min(y0 + 1, h - 1)
|
|
fx, fy = x - x0, y - y0
|
|
|
|
c00 = img[y0, x0].astype(float)
|
|
c10 = img[y0, x1].astype(float)
|
|
c01 = img[y1, x0].astype(float)
|
|
c11 = img[y1, x1].astype(float)
|
|
|
|
top = c00 * (1 - fx) + c10 * fx
|
|
bottom = c01 * (1 - fx) + c11 * fx
|
|
return list(top * (1 - fy) + bottom * fy)
|
|
|
|
|
|
def prim_channel(img, c):
|
|
"""Extract single channel (0=R, 1=G, 2=B)."""
|
|
return img[:, :, c]
|
|
|
|
|
|
def prim_merge_channels(r, g, b):
|
|
"""Merge three single-channel arrays into RGB image."""
|
|
return np.stack([r, g, b], axis=2).astype(np.uint8)
|
|
|
|
|
|
def prim_resize(img, w, h, mode="linear"):
|
|
"""Resize image to w x h."""
|
|
interp = cv2.INTER_LINEAR
|
|
if mode == "nearest":
|
|
interp = cv2.INTER_NEAREST
|
|
elif mode == "cubic":
|
|
interp = cv2.INTER_CUBIC
|
|
elif mode == "area":
|
|
interp = cv2.INTER_AREA
|
|
return cv2.resize(img, (int(w), int(h)), interpolation=interp)
|
|
|
|
|
|
def prim_crop(img, x, y, w, h):
|
|
"""Crop rectangle from image."""
|
|
x, y, w, h = int(x), int(y), int(w), int(h)
|
|
ih, iw = img.shape[:2]
|
|
x = max(0, min(x, iw - 1))
|
|
y = max(0, min(y, ih - 1))
|
|
w = min(w, iw - x)
|
|
h = min(h, ih - y)
|
|
return img[y:y+h, x:x+w].copy()
|
|
|
|
|
|
def prim_paste(dst, src, x, y):
|
|
"""Paste src onto dst at position (x, y)."""
|
|
result = dst.copy()
|
|
x, y = int(x), int(y)
|
|
sh, sw = src.shape[:2]
|
|
dh, dw = dst.shape[:2]
|
|
|
|
# Clip to bounds
|
|
sx1 = max(0, -x)
|
|
sy1 = max(0, -y)
|
|
dx1 = max(0, x)
|
|
dy1 = max(0, y)
|
|
sx2 = min(sw, dw - x)
|
|
sy2 = min(sh, dh - y)
|
|
|
|
if sx2 > sx1 and sy2 > sy1:
|
|
result[dy1:dy1+(sy2-sy1), dx1:dx1+(sx2-sx1)] = src[sy1:sy2, sx1:sx2]
|
|
|
|
return result
|
|
|
|
|
|
PRIMITIVES = {
|
|
# Dimensions
|
|
'width': prim_width,
|
|
'height': prim_height,
|
|
|
|
# Creation
|
|
'make-image': prim_make_image,
|
|
'copy': prim_copy,
|
|
|
|
# Pixel access
|
|
'pixel': prim_pixel,
|
|
'set-pixel': prim_set_pixel,
|
|
'sample': prim_sample,
|
|
|
|
# Channels
|
|
'channel': prim_channel,
|
|
'merge-channels': prim_merge_channels,
|
|
|
|
# Geometry
|
|
'resize': prim_resize,
|
|
'crop': prim_crop,
|
|
'paste': prim_paste,
|
|
}
|