• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

popstas / talks-reducer / 28131157214

24 Jun 2026 09:35PM UTC coverage: 74.925% (+0.9%) from 74.044%
28131157214

push

github

web-flow
feat: Add video trim (cut begin/end) before converting (#140)

Add an optional trim step that keeps only a chosen [start, end] fragment of each
input before the speed-up pipeline runs.

- CLI: --cut-start / --cut-end (seconds or HH:MM:SS), keep-range in/out semantics;
  0/0 means no trim (behaviour unchanged when off).
- ffmpeg: input-level -ss/-t injected into both audio-extract and final-video
  commands so audio/video stay in sync; pipeline recomputes effective duration so
  progress/target-duration math stays correct on a trimmed clip.
- Desktop GUI: a "Cut video" panel with manual start/end time inputs and a
  frame-scrub thumbnail preview; enabled flag + start/end persist via preferences.
- Web UI (gradio): Cut video checkbox + start/end inputs wired through process_video
  and the remote server path.
- Docs + tests across timecode/cli/ffmpeg/pipeline/models/server/gui (557 tests).

286 of 332 new or added lines in 10 files covered. (86.14%)

611 existing lines in 10 files now uncovered.

8480 of 11318 relevant lines covered (74.92%)

0.75 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.74
/talks_reducer/timecode.py
1
"""Helpers for parsing and formatting trim timecodes.
2

3
Timecodes describe positions within a video and are accepted in several
4
forms by the CLI and GUIs: a bare number of seconds (``12.5``) or a
5
colon-separated clock string (``SS``, ``MM:SS`` or ``HH:MM:SS`` with an
6
optional fractional ``.ms`` suffix). All helpers operate on non-negative
7
values and reject malformed input with :class:`ValueError`.
8
"""
9

10
from __future__ import annotations
1✔
11

12
import math
1✔
13
from numbers import Real
1✔
14

15
__all__ = ["parse_timecode", "format_timecode"]
1✔
16

17

18
def _validate_seconds(seconds: float, value) -> float:
1✔
19
    """Return ``seconds`` after rejecting negative or non-finite values."""
20

21
    if not math.isfinite(seconds):
1✔
NEW
22
        raise ValueError(f"Invalid timecode: {value!r}")
×
23
    if seconds < 0:
1✔
24
        raise ValueError(f"Timecode cannot be negative: {value!r}")
1✔
25
    return seconds
1✔
26

27

28
def parse_timecode(value) -> float:
1✔
29
    """Return the number of seconds described by ``value``.
30

31
    ``value`` may be a numeric seconds value (``int``/``float``) or a string
32
    holding either a decimal number of seconds or a ``SS`` / ``MM:SS`` /
33
    ``HH:MM:SS`` clock with an optional ``.ms`` fractional part. Negative or
34
    malformed values raise :class:`ValueError`.
35
    """
36

37
    if isinstance(value, bool):  # bool is a subclass of int; reject explicitly
1✔
NEW
38
        raise ValueError(f"Invalid timecode: {value!r}")
×
39

40
    if isinstance(value, Real):
1✔
41
        return _validate_seconds(float(value), value)
1✔
42

43
    if not isinstance(value, str):
1✔
44
        raise ValueError(f"Invalid timecode: {value!r}")
1✔
45

46
    text = value.strip()
1✔
47
    if not text:
1✔
48
        raise ValueError("Timecode cannot be empty")
1✔
49

50
    if ":" in text:
1✔
51
        parts = text.split(":")
1✔
52
        if len(parts) > 3:
1✔
53
            raise ValueError(f"Invalid timecode: {value!r}")
1✔
54
        try:
1✔
55
            numbers = [float(part) for part in parts]
1✔
56
        except ValueError as exc:
1✔
57
            raise ValueError(f"Invalid timecode: {value!r}") from exc
1✔
58
        if any(number < 0 for number in numbers):
1✔
NEW
59
            raise ValueError(f"Timecode cannot be negative: {value!r}")
×
60
        seconds = 0.0
1✔
61
        for number in numbers:
1✔
62
            seconds = seconds * 60 + number
1✔
63
        return _validate_seconds(seconds, value)
1✔
64

65
    try:
1✔
66
        seconds = float(text)
1✔
67
    except ValueError as exc:
1✔
68
        raise ValueError(f"Invalid timecode: {value!r}") from exc
1✔
69
    return _validate_seconds(seconds, value)
1✔
70

71

72
def format_timecode(seconds, *, milliseconds: bool = False) -> str:
1✔
73
    """Return ``seconds`` formatted as a ``HH:MM:SS`` clock string.
74

75
    When ``milliseconds`` is true the fractional part is appended as a
76
    three-digit ``.mmm`` suffix (``HH:MM:SS.mmm``) so the value can round-trip
77
    through :func:`parse_timecode` without losing sub-second precision.
78
    """
79

80
    if isinstance(seconds, bool) or not isinstance(seconds, Real):
1✔
NEW
81
        raise ValueError(f"Invalid seconds value: {seconds!r}")
×
82
    if seconds < 0:
1✔
83
        raise ValueError(f"Seconds cannot be negative: {seconds!r}")
1✔
84

85
    total = int(seconds)
1✔
86
    hours, remainder = divmod(total, 3600)
1✔
87
    minutes, secs = divmod(remainder, 60)
1✔
88
    clock = f"{hours:02d}:{minutes:02d}:{secs:02d}"
1✔
89
    if not milliseconds:
1✔
90
        return clock
1✔
91
    millis = int(round((float(seconds) - total) * 1000))
1✔
92
    if millis >= 1000:  # rounding can spill into the next second
1✔
NEW
93
        millis = 999
×
94
    return f"{clock}.{millis:03d}"
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc