File size: 2,958 Bytes
170c23f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import numpy as np
import pytest

from camguard.video import (
    H264Writer,
    VideoInfo,
    first_frame,
    frame_limit,
    probe,
    sample_frames,
    sampling_step,
)


def make_video(path, n_frames, fps=25, width=64, height=48):
    """Video sintético donde cada cuadro tiene un brillo distinto."""
    with H264Writer(path, fps, width, height) as writer:
        for i in range(n_frames):
            writer.write(np.full((height, width, 3), 4 * i, np.uint8))
    return path


@pytest.fixture
def video(tmp_path):
    """2 s de video a 25 fps: 50 cuadros."""
    return make_video(tmp_path / "video.mp4", n_frames=50)


def test_written_video_can_be_read_back(video):
    assert probe(video) == VideoInfo(fps=25.0, width=64, height=48, frame_count=50)


def test_writer_crops_odd_size_to_even(tmp_path):
    path = make_video(tmp_path / "impar.mp4", n_frames=3, width=65, height=49)
    info = probe(path)
    assert (info.width, info.height) == (64, 48)


def test_sample_frames_takes_one_every_step(video):
    every_frame = list(sample_frames(video, step=1, max_frames=50))
    sampled = list(sample_frames(video, step=5, max_frames=50))
    assert len(every_frame) == 50
    assert len(sampled) == 10
    # Los cuadros salteados con grab() tienen que ser exactamente los cuadros 0, 5, 10...
    for got, expected in zip(sampled, every_frame[::5], strict=True):
        assert np.array_equal(got, expected)


def test_sample_frames_stops_at_max_frames(video):
    assert len(list(sample_frames(video, step=5, max_frames=20))) == 4


def test_sample_frames_stops_at_end_of_video(video):
    assert len(list(sample_frames(video, step=5, max_frames=1000))) == 10


@pytest.mark.parametrize(
    ("source_fps", "step"),
    [(25, 5), (30, 6), (29.97, 6), (3, 1)],
)
def test_sampling_step(source_fps, step):
    assert sampling_step(source_fps, target_fps=5) == step


@pytest.mark.parametrize(
    ("frame_count", "limit"),
    [
        pytest.param(100, 50, id="video-largo-se-corta"),
        pytest.param(10, 10, id="video-corto-entero"),
        pytest.param(0, 50, id="sin-cantidad-de-cuadros-usa-el-tope"),
    ],
)
def test_frame_limit(frame_count, limit):
    info = VideoInfo(fps=25.0, width=64, height=48, frame_count=frame_count)
    assert frame_limit(info, max_seconds=2) == limit


def test_first_frame_is_rgb(tmp_path):
    path = tmp_path / "azul.mp4"
    with H264Writer(path, fps=25, width=64, height=48) as writer:
        writer.write(np.full((48, 64, 3), (255, 0, 0), np.uint8))  # azul en BGR
    frame = first_frame(path)
    assert frame[..., 2].mean() > 200  # en RGB el azul es el último canal
    assert frame[..., 0].mean() < 50


def test_unreadable_file_raises(tmp_path):
    path = tmp_path / "roto.mp4"
    path.write_text("esto no es un video")
    with pytest.raises(ValueError, match="No pude leer"):
        probe(path)
    with pytest.raises(ValueError, match="No pude leer"):
        first_frame(path)