File size: 2,160 Bytes
1e3ce4c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
"""I/O helpers: FrameStack <-> .npz round-trip, ndarray -> video file.

GIF export uses Pillow (always available). MP4/WebM/MKV export uses imageio
when installed (optional extra: `pip install 'venture-studio[video]'`); without
it, those extensions raise a clean ImportError with the install instruction.
"""
from __future__ import annotations
from pathlib import Path

import numpy as np
from PIL import Image as PILImage

from pixel_cursor import FrameStack
from pixel_cursor.artifact import _new_framestack


def save_framestack_npz(stack: FrameStack, path: Path | str) -> Path:
    out = Path(path)
    np.savez_compressed(
        str(out),
        frames=stack.frames,
        fps=np.array(stack.fps, dtype=np.int32),
        name=np.array(stack.name, dtype="U256"),
    )
    return out


def load_framestack_npz(path: Path | str) -> FrameStack:
    data = np.load(str(path), allow_pickle=False)
    name = str(data["name"]) if "name" in data.files else ""
    return _new_framestack(data["frames"], fps=int(data["fps"]), name=name)


def export_video(stack: FrameStack, out_path: Path | str) -> Path:
    out = Path(out_path)
    ext = out.suffix.lower()
    if ext == ".gif":
        return _export_gif(stack, out)
    if ext in (".mp4", ".webm", ".mkv", ".avi"):
        return _export_imageio(stack, out)
    raise ValueError(
        f"Unsupported export extension {ext!r}; supported: .gif .mp4 .webm .mkv .avi"
    )


def _export_gif(stack: FrameStack, out: Path) -> Path:
    frames = [PILImage.fromarray(stack.frames[i]) for i in range(stack.num_frames)]
    duration_ms = max(1, 1000 // max(1, stack.fps))
    frames[0].save(
        out, save_all=True, append_images=frames[1:], duration=duration_ms, loop=0
    )
    return out


def _export_imageio(stack: FrameStack, out: Path) -> Path:
    try:
        import imageio.v3 as iio
    except ImportError as e:
        raise ImportError(
            f"Exporting to {out.suffix} requires imageio. Install with: "
            f"pip install 'venture-studio[video]'  (or: pip install imageio imageio-ffmpeg)"
        ) from e
    iio.imwrite(str(out), stack.frames, fps=stack.fps)
    return out