talks-reducer 0.5.0__tar.gz → 0.5.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {talks_reducer-0.5.0/talks_reducer.egg-info → talks_reducer-0.5.1}/PKG-INFO +119 -93
- {talks_reducer-0.5.0 → talks_reducer-0.5.1}/README.md +27 -1
- {talks_reducer-0.5.0 → talks_reducer-0.5.1}/setup.cfg +4 -4
- {talks_reducer-0.5.0 → talks_reducer-0.5.1}/talks_reducer/__about__.py +1 -1
- {talks_reducer-0.5.0 → talks_reducer-0.5.1}/talks_reducer/cli.py +2 -2
- {talks_reducer-0.5.0 → talks_reducer-0.5.1}/talks_reducer/ffmpeg.py +6 -0
- {talks_reducer-0.5.0 → talks_reducer-0.5.1}/talks_reducer/gui.py +3 -3
- {talks_reducer-0.5.0 → talks_reducer-0.5.1}/talks_reducer/models.py +27 -2
- {talks_reducer-0.5.0 → talks_reducer-0.5.1}/talks_reducer/pipeline.py +1 -1
- {talks_reducer-0.5.0 → talks_reducer-0.5.1}/talks_reducer/server.py +1 -0
- talks_reducer-0.5.1/talks_reducer/service_client.py +102 -0
- {talks_reducer-0.5.0 → talks_reducer-0.5.1/talks_reducer.egg-info}/PKG-INFO +119 -93
- {talks_reducer-0.5.0 → talks_reducer-0.5.1}/talks_reducer.egg-info/SOURCES.txt +3 -1
- talks_reducer-0.5.1/tests/test_service_client.py +112 -0
- {talks_reducer-0.5.0 → talks_reducer-0.5.1}/LICENSE +0 -0
- {talks_reducer-0.5.0 → talks_reducer-0.5.1}/pyproject.toml +0 -0
- {talks_reducer-0.5.0 → talks_reducer-0.5.1}/talks_reducer/__init__.py +0 -0
- {talks_reducer-0.5.0 → talks_reducer-0.5.1}/talks_reducer/__main__.py +0 -0
- {talks_reducer-0.5.0 → talks_reducer-0.5.1}/talks_reducer/audio.py +0 -0
- {talks_reducer-0.5.0 → talks_reducer-0.5.1}/talks_reducer/chunks.py +0 -0
- {talks_reducer-0.5.0 → talks_reducer-0.5.1}/talks_reducer/progress.py +0 -0
- {talks_reducer-0.5.0 → talks_reducer-0.5.1}/talks_reducer.egg-info/dependency_links.txt +0 -0
- {talks_reducer-0.5.0 → talks_reducer-0.5.1}/talks_reducer.egg-info/entry_points.txt +0 -0
- {talks_reducer-0.5.0 → talks_reducer-0.5.1}/talks_reducer.egg-info/requires.txt +0 -0
- {talks_reducer-0.5.0 → talks_reducer-0.5.1}/talks_reducer.egg-info/top_level.txt +0 -0
- {talks_reducer-0.5.0 → talks_reducer-0.5.1}/tests/test_audio.py +0 -0
- {talks_reducer-0.5.0 → talks_reducer-0.5.1}/tests/test_cli.py +0 -0
- {talks_reducer-0.5.0 → talks_reducer-0.5.1}/tests/test_pipeline_service.py +0 -0
- {talks_reducer-0.5.0 → talks_reducer-0.5.1}/tests/test_server.py +0 -0
@@ -1,93 +1,119 @@
|
|
1
|
-
Metadata-Version: 2.4
|
2
|
-
Name: talks-reducer
|
3
|
-
Version: 0.5.
|
4
|
-
Summary: CLI for speeding up long-form talks by removing silence
|
5
|
-
Author: Talks Reducer Maintainers
|
6
|
-
License-Expression: MIT
|
7
|
-
Requires-Python: >=3.9
|
8
|
-
Description-Content-Type: text/markdown
|
9
|
-
License-File: LICENSE
|
10
|
-
Requires-Dist: audiotsm>=0.1.2
|
11
|
-
Requires-Dist: scipy>=1.10.0
|
12
|
-
Requires-Dist: numpy>=1.22.0
|
13
|
-
Requires-Dist: tqdm>=4.65.0
|
14
|
-
Requires-Dist: tkinterdnd2>=0.3.0
|
15
|
-
Requires-Dist: Pillow>=9.0.0
|
16
|
-
Requires-Dist: imageio-ffmpeg>=0.4.8
|
17
|
-
Requires-Dist: gradio>=4.0.0
|
18
|
-
Provides-Extra: dev
|
19
|
-
Requires-Dist: build>=1.0.0; extra == "dev"
|
20
|
-
Requires-Dist: twine>=4.0.0; extra == "dev"
|
21
|
-
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
22
|
-
Requires-Dist: black>=23.0.0; extra == "dev"
|
23
|
-
Requires-Dist: isort>=5.12.0; extra == "dev"
|
24
|
-
Requires-Dist: bump-my-version>=0.5.0; extra == "dev"
|
25
|
-
Requires-Dist: pyinstaller>=6.4.0; extra == "dev"
|
26
|
-
Dynamic: license-file
|
27
|
-
|
28
|
-
# Talks Reducer
|
29
|
-
Talks Reducer shortens long-form presentations by removing silent gaps and optionally re-encoding them to smaller files. The
|
30
|
-
project was renamed from **jumpcutter** to emphasize its focus on conference talks and screencasts.
|
31
|
-
|
32
|
-

|
33
|
-
|
34
|
-
## Example
|
35
|
-
- 1h 37m, 571 MB — Original OBS video recording
|
36
|
-
- 1h 19m, 751 MB — Talks Reducer
|
37
|
-
- 1h 19m, 171 MB — Talks Reducer `--small`
|
38
|
-
|
39
|
-
## Changelog
|
40
|
-
|
41
|
-
See [CHANGELOG.md](CHANGELOG.md).
|
42
|
-
|
43
|
-
## Install GUI (Windows, macOS)
|
44
|
-
Go to the [releases page](https://github.com/popstas/talks-reducer/releases) and download the appropriate artifact:
|
45
|
-
|
46
|
-
- **Windows** — `talks-reducer-windows-0.4.0.zip`
|
47
|
-
- **macOS** — `talks-reducer.app.zip`
|
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
|
-
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: talks-reducer
|
3
|
+
Version: 0.5.1
|
4
|
+
Summary: CLI for speeding up long-form talks by removing silence
|
5
|
+
Author: Talks Reducer Maintainers
|
6
|
+
License-Expression: MIT
|
7
|
+
Requires-Python: >=3.9
|
8
|
+
Description-Content-Type: text/markdown
|
9
|
+
License-File: LICENSE
|
10
|
+
Requires-Dist: audiotsm>=0.1.2
|
11
|
+
Requires-Dist: scipy>=1.10.0
|
12
|
+
Requires-Dist: numpy>=1.22.0
|
13
|
+
Requires-Dist: tqdm>=4.65.0
|
14
|
+
Requires-Dist: tkinterdnd2>=0.3.0
|
15
|
+
Requires-Dist: Pillow>=9.0.0
|
16
|
+
Requires-Dist: imageio-ffmpeg>=0.4.8
|
17
|
+
Requires-Dist: gradio>=4.0.0
|
18
|
+
Provides-Extra: dev
|
19
|
+
Requires-Dist: build>=1.0.0; extra == "dev"
|
20
|
+
Requires-Dist: twine>=4.0.0; extra == "dev"
|
21
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
22
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
23
|
+
Requires-Dist: isort>=5.12.0; extra == "dev"
|
24
|
+
Requires-Dist: bump-my-version>=0.5.0; extra == "dev"
|
25
|
+
Requires-Dist: pyinstaller>=6.4.0; extra == "dev"
|
26
|
+
Dynamic: license-file
|
27
|
+
|
28
|
+
# Talks Reducer
|
29
|
+
Talks Reducer shortens long-form presentations by removing silent gaps and optionally re-encoding them to smaller files. The
|
30
|
+
project was renamed from **jumpcutter** to emphasize its focus on conference talks and screencasts.
|
31
|
+
|
32
|
+

|
33
|
+
|
34
|
+
## Example
|
35
|
+
- 1h 37m, 571 MB — Original OBS video recording
|
36
|
+
- 1h 19m, 751 MB — Talks Reducer
|
37
|
+
- 1h 19m, 171 MB — Talks Reducer `--small`
|
38
|
+
|
39
|
+
## Changelog
|
40
|
+
|
41
|
+
See [CHANGELOG.md](CHANGELOG.md).
|
42
|
+
|
43
|
+
## Install GUI (Windows, macOS)
|
44
|
+
Go to the [releases page](https://github.com/popstas/talks-reducer/releases) and download the appropriate artifact:
|
45
|
+
|
46
|
+
- **Windows** — `talks-reducer-windows-0.4.0.zip`
|
47
|
+
- **macOS** — `talks-reducer.app.zip`
|
48
|
+
|
49
|
+
> **Troubleshooting:** If launching the bundle (or running `python talks_reducer/gui.py`) prints `macOS 26 (2600) or later required, have instead 16 (1600)!`, make sure you're using a Python build that ships a modern Tk. The stock [python.org 3.13.5 installer](https://www.python.org/downloads/release/python-3135/) includes Tk 8.6 and has been verified to work.
|
50
|
+
|
51
|
+
When extracted on Windows the bundled `talks-reducer.exe` behaves like the
|
52
|
+
`python talks_reducer/gui.py` entry point: double-clicking it launches the GUI
|
53
|
+
and passing a video file path (for example via *Open with…* or drag-and-drop
|
54
|
+
onto the executable) automatically queues that recording for processing.
|
55
|
+
|
56
|
+
## Install CLI (Linux, Windows, macOS)
|
57
|
+
```
|
58
|
+
pip install talks-reducer
|
59
|
+
```
|
60
|
+
|
61
|
+
**Note:** FFmpeg is now bundled automatically with the package, so you don't need to install it separately. You you need, don't know actually.
|
62
|
+
|
63
|
+
The `--small` preset applies a 720p video scale and 128 kbps audio bitrate, making it useful for sharing talks over constrained
|
64
|
+
connections. Without `--small`, the script aims to preserve original quality while removing silence.
|
65
|
+
|
66
|
+
Example CLI usage:
|
67
|
+
|
68
|
+
```sh
|
69
|
+
talks-reducer --small input.mp4
|
70
|
+
```
|
71
|
+
|
72
|
+
### Speech detection
|
73
|
+
|
74
|
+
Talks Reducer now relies on its built-in volume thresholding to detect speech. Adjust `--silent_threshold` if you need to fine-tune when segments count as silence. Dropping the optional Silero VAD integration keeps the install lightweight and avoids pulling in PyTorch.
|
75
|
+
|
76
|
+
When CUDA-capable hardware is available the pipeline leans on GPU encoders to keep export times low, but it still runs great on
|
77
|
+
CPUs.
|
78
|
+
|
79
|
+
## Simple web server
|
80
|
+
|
81
|
+
Prefer a lightweight browser interface? Launch the Gradio-powered simple mode with:
|
82
|
+
|
83
|
+
```sh
|
84
|
+
talks-reducer server
|
85
|
+
```
|
86
|
+
|
87
|
+
This opens a local web page featuring a drag-and-drop upload zone, a **Small video** checkbox that mirrors the CLI preset, a live
|
88
|
+
progress indicator, and automatic previews of the processed output. Once the job completes you can inspect the resulting compression
|
89
|
+
ratio and download the rendered video directly from the page.
|
90
|
+
|
91
|
+
### Uploading and retrieving a processed video
|
92
|
+
|
93
|
+
1. Open the printed `http://localhost:<port>` address (the default port is `9005`).
|
94
|
+
2. Drag a video onto the **Video file** drop zone or click to browse and select one from disk.
|
95
|
+
3. (Optional) Enable **Small video** before the upload finishes to apply the 720p/128 kbps preset.
|
96
|
+
4. Wait for the progress bar and log to report completion—the interface queues work automatically after the file arrives.
|
97
|
+
5. Watch the processed preview in the **Processed video** player and click **Download processed file** to save the result locally.
|
98
|
+
|
99
|
+
Need to change where the server listens? Run `talks-reducer server --host 0.0.0.0 --port 7860` (or any other port) to bind to a
|
100
|
+
different address.
|
101
|
+
|
102
|
+
### Automating uploads from the command line
|
103
|
+
|
104
|
+
Prefer to script uploads instead of using the browser UI? Start the server and use the bundled helper to submit a job and save
|
105
|
+
the processed video locally:
|
106
|
+
|
107
|
+
```sh
|
108
|
+
python -m talks_reducer.service_client --server http://127.0.0.1:9005/ --input demo.mp4 --output output/demo_processed.mp4
|
109
|
+
```
|
110
|
+
|
111
|
+
The helper wraps the Gradio API exposed by `server.py`, waits for processing to complete, then copies the rendered file to the
|
112
|
+
path you provide. Pass `--small` to mirror the **Small video** checkbox or `--print-log` to stream the server log after the
|
113
|
+
download finishes.
|
114
|
+
|
115
|
+
## Contributing
|
116
|
+
See `CONTRIBUTION.md` for development setup details and guidance on sharing improvements.
|
117
|
+
|
118
|
+
## License
|
119
|
+
Talks Reducer is released under the MIT License. See `LICENSE` for the full text.
|
@@ -17,7 +17,9 @@ See [CHANGELOG.md](CHANGELOG.md).
|
|
17
17
|
Go to the [releases page](https://github.com/popstas/talks-reducer/releases) and download the appropriate artifact:
|
18
18
|
|
19
19
|
- **Windows** — `talks-reducer-windows-0.4.0.zip`
|
20
|
-
- **macOS** — `talks-reducer.app.zip`
|
20
|
+
- **macOS** — `talks-reducer.app.zip`
|
21
|
+
|
22
|
+
> **Troubleshooting:** If launching the bundle (or running `python talks_reducer/gui.py`) prints `macOS 26 (2600) or later required, have instead 16 (1600)!`, make sure you're using a Python build that ships a modern Tk. The stock [python.org 3.13.5 installer](https://www.python.org/downloads/release/python-3135/) includes Tk 8.6 and has been verified to work.
|
21
23
|
|
22
24
|
When extracted on Windows the bundled `talks-reducer.exe` behaves like the
|
23
25
|
`python talks_reducer/gui.py` entry point: double-clicking it launches the GUI
|
@@ -59,6 +61,30 @@ This opens a local web page featuring a drag-and-drop upload zone, a **Small vid
|
|
59
61
|
progress indicator, and automatic previews of the processed output. Once the job completes you can inspect the resulting compression
|
60
62
|
ratio and download the rendered video directly from the page.
|
61
63
|
|
64
|
+
### Uploading and retrieving a processed video
|
65
|
+
|
66
|
+
1. Open the printed `http://localhost:<port>` address (the default port is `9005`).
|
67
|
+
2. Drag a video onto the **Video file** drop zone or click to browse and select one from disk.
|
68
|
+
3. (Optional) Enable **Small video** before the upload finishes to apply the 720p/128 kbps preset.
|
69
|
+
4. Wait for the progress bar and log to report completion—the interface queues work automatically after the file arrives.
|
70
|
+
5. Watch the processed preview in the **Processed video** player and click **Download processed file** to save the result locally.
|
71
|
+
|
72
|
+
Need to change where the server listens? Run `talks-reducer server --host 0.0.0.0 --port 7860` (or any other port) to bind to a
|
73
|
+
different address.
|
74
|
+
|
75
|
+
### Automating uploads from the command line
|
76
|
+
|
77
|
+
Prefer to script uploads instead of using the browser UI? Start the server and use the bundled helper to submit a job and save
|
78
|
+
the processed video locally:
|
79
|
+
|
80
|
+
```sh
|
81
|
+
python -m talks_reducer.service_client --server http://127.0.0.1:9005/ --input demo.mp4 --output output/demo_processed.mp4
|
82
|
+
```
|
83
|
+
|
84
|
+
The helper wraps the Gradio API exposed by `server.py`, waits for processing to complete, then copies the rendered file to the
|
85
|
+
path you provide. Pass `--small` to mirror the **Small video** checkbox or `--print-log` to stream the server log after the
|
86
|
+
download finishes.
|
87
|
+
|
62
88
|
## Contributing
|
63
89
|
See `CONTRIBUTION.md` for development setup details and guidance on sharing improvements.
|
64
90
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
[egg_info]
|
2
|
-
tag_build =
|
3
|
-
tag_date = 0
|
4
|
-
|
1
|
+
[egg_info]
|
2
|
+
tag_build =
|
3
|
+
tag_date = 0
|
4
|
+
|
@@ -18,7 +18,7 @@ try:
|
|
18
18
|
except Exception: # pragma: no cover - fallback if metadata file missing
|
19
19
|
_about_version = ""
|
20
20
|
from .ffmpeg import FFmpegNotFoundError
|
21
|
-
from .models import ProcessingOptions
|
21
|
+
from .models import ProcessingOptions, default_temp_folder
|
22
22
|
from .pipeline import speed_up_video
|
23
23
|
from .progress import TqdmProgressReporter
|
24
24
|
|
@@ -55,7 +55,7 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
55
55
|
parser.add_argument(
|
56
56
|
"--temp_folder",
|
57
57
|
type=str,
|
58
|
-
default=
|
58
|
+
default=str(default_temp_folder()),
|
59
59
|
help="The file path of the temporary working folder.",
|
60
60
|
)
|
61
61
|
parser.add_argument(
|
@@ -52,6 +52,9 @@ def find_ffmpeg() -> Optional[str]:
|
|
52
52
|
"C:\\ProgramData\\chocolatey\\bin\\ffmpeg.exe",
|
53
53
|
"C:\\Program Files\\ffmpeg\\bin\\ffmpeg.exe",
|
54
54
|
"C:\\ffmpeg\\bin\\ffmpeg.exe",
|
55
|
+
"/usr/local/bin/ffmpeg",
|
56
|
+
"/opt/homebrew/bin/ffmpeg",
|
57
|
+
"/usr/bin/ffmpeg",
|
55
58
|
"ffmpeg",
|
56
59
|
]
|
57
60
|
|
@@ -92,6 +95,9 @@ def find_ffprobe() -> Optional[str]:
|
|
92
95
|
"C:\\ProgramData\\chocolatey\\bin\\ffprobe.exe",
|
93
96
|
"C:\\Program Files\\ffmpeg\\bin\\ffprobe.exe",
|
94
97
|
"C:\\ffmpeg\\bin\\ffprobe.exe",
|
98
|
+
"/usr/local/bin/ffprobe",
|
99
|
+
"/opt/homebrew/bin/ffprobe",
|
100
|
+
"/usr/bin/ffprobe",
|
95
101
|
"ffprobe",
|
96
102
|
]
|
97
103
|
|
@@ -20,7 +20,7 @@ try:
|
|
20
20
|
from .cli import gather_input_files
|
21
21
|
from .cli import main as cli_main
|
22
22
|
from .ffmpeg import FFmpegNotFoundError
|
23
|
-
from .models import ProcessingOptions
|
23
|
+
from .models import ProcessingOptions, default_temp_folder
|
24
24
|
from .pipeline import speed_up_video
|
25
25
|
from .progress import ProgressHandle, SignalProgressReporter
|
26
26
|
except ImportError: # pragma: no cover - handled at runtime
|
@@ -34,7 +34,7 @@ except ImportError: # pragma: no cover - handled at runtime
|
|
34
34
|
from talks_reducer.cli import gather_input_files
|
35
35
|
from talks_reducer.cli import main as cli_main
|
36
36
|
from talks_reducer.ffmpeg import FFmpegNotFoundError
|
37
|
-
from talks_reducer.models import ProcessingOptions
|
37
|
+
from talks_reducer.models import ProcessingOptions, default_temp_folder
|
38
38
|
from talks_reducer.pipeline import speed_up_video
|
39
39
|
from talks_reducer.progress import ProgressHandle, SignalProgressReporter
|
40
40
|
|
@@ -480,7 +480,7 @@ class TalksReducerGUI:
|
|
480
480
|
self.advanced_frame, "Output file", self.output_var, row=0, browse=True
|
481
481
|
)
|
482
482
|
|
483
|
-
self.temp_var = self.tk.StringVar(value=
|
483
|
+
self.temp_var = self.tk.StringVar(value=str(default_temp_folder()))
|
484
484
|
self._add_entry(
|
485
485
|
self.advanced_frame, "Temp folder", self.temp_var, row=1, browse=True
|
486
486
|
)
|
@@ -2,11 +2,36 @@
|
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
|
-
|
5
|
+
import os
|
6
|
+
import sys
|
7
|
+
import tempfile
|
8
|
+
from dataclasses import dataclass, field
|
6
9
|
from pathlib import Path
|
7
10
|
from typing import Optional
|
8
11
|
|
9
12
|
|
13
|
+
def default_temp_folder() -> Path:
|
14
|
+
"""Return an OS-appropriate default temporary workspace directory."""
|
15
|
+
|
16
|
+
if sys.platform == "darwin":
|
17
|
+
base = Path.home() / "Library" / "Application Support" / "talks-reducer"
|
18
|
+
elif sys.platform == "win32":
|
19
|
+
appdata = os.environ.get("LOCALAPPDATA") or os.environ.get("APPDATA")
|
20
|
+
base = (
|
21
|
+
Path(appdata)
|
22
|
+
if appdata
|
23
|
+
else Path.home() / "AppData" / "Local" / "talks-reducer"
|
24
|
+
)
|
25
|
+
else:
|
26
|
+
xdg_runtime = os.environ.get("XDG_RUNTIME_DIR")
|
27
|
+
if xdg_runtime:
|
28
|
+
base = Path(xdg_runtime) / "talks-reducer"
|
29
|
+
else:
|
30
|
+
base = Path(tempfile.gettempdir()) / "talks-reducer"
|
31
|
+
|
32
|
+
return base / "temp"
|
33
|
+
|
34
|
+
|
10
35
|
@dataclass(frozen=True)
|
11
36
|
class ProcessingOptions:
|
12
37
|
"""Configuration values controlling how the talks reducer processes media.
|
@@ -24,7 +49,7 @@ class ProcessingOptions:
|
|
24
49
|
sounded_speed: float = 1.0
|
25
50
|
frame_spreadage: int = 2
|
26
51
|
audio_fade_envelope_size: int = 400
|
27
|
-
temp_folder: Path =
|
52
|
+
temp_folder: Path = field(default_factory=default_temp_folder)
|
28
53
|
small: bool = False
|
29
54
|
|
30
55
|
|
@@ -46,7 +46,7 @@ def _input_to_output_filename(filename: Path, small: bool = False) -> Path:
|
|
46
46
|
|
47
47
|
def _create_path(path: Path) -> None:
|
48
48
|
try:
|
49
|
-
path.mkdir()
|
49
|
+
path.mkdir(parents=True, exist_ok=True)
|
50
50
|
except OSError as exc: # pragma: no cover - defensive logging
|
51
51
|
raise AssertionError(
|
52
52
|
"Creation of the directory failed. (The TEMP folder may already exist. Delete or rename it, and try again.)"
|
@@ -0,0 +1,102 @@
|
|
1
|
+
"""Command-line helper for sending videos to the Talks Reducer server."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
import argparse
|
6
|
+
import shutil
|
7
|
+
from pathlib import Path
|
8
|
+
from typing import Optional, Sequence, Tuple
|
9
|
+
|
10
|
+
from gradio_client import Client
|
11
|
+
from gradio_client import file as gradio_file
|
12
|
+
|
13
|
+
|
14
|
+
def send_video(
|
15
|
+
input_path: Path,
|
16
|
+
output_path: Optional[Path],
|
17
|
+
server_url: str,
|
18
|
+
small: bool = False,
|
19
|
+
) -> Tuple[Path, str, str]:
|
20
|
+
"""Upload *input_path* to the Gradio server and download the processed video."""
|
21
|
+
|
22
|
+
if not input_path.exists():
|
23
|
+
raise FileNotFoundError(f"Input file does not exist: {input_path}")
|
24
|
+
|
25
|
+
client = Client(server_url)
|
26
|
+
prediction = client.predict(
|
27
|
+
gradio_file(str(input_path)),
|
28
|
+
bool(small),
|
29
|
+
api_name="/process_video",
|
30
|
+
)
|
31
|
+
|
32
|
+
try:
|
33
|
+
_, log_text, summary, download_path = prediction
|
34
|
+
except (TypeError, ValueError) as exc: # pragma: no cover - defensive
|
35
|
+
raise RuntimeError("Unexpected response from server") from exc
|
36
|
+
|
37
|
+
if not download_path:
|
38
|
+
raise RuntimeError("Server did not return a processed file")
|
39
|
+
|
40
|
+
download_source = Path(str(download_path))
|
41
|
+
if output_path is None:
|
42
|
+
destination = Path.cwd() / download_source.name
|
43
|
+
else:
|
44
|
+
destination = output_path
|
45
|
+
if destination.is_dir():
|
46
|
+
destination = destination / download_source.name
|
47
|
+
|
48
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
49
|
+
if download_source.resolve() != destination.resolve():
|
50
|
+
shutil.copy2(download_source, destination)
|
51
|
+
|
52
|
+
return destination, summary, log_text
|
53
|
+
|
54
|
+
|
55
|
+
def _build_parser() -> argparse.ArgumentParser:
|
56
|
+
parser = argparse.ArgumentParser(
|
57
|
+
description="Send a video to a running talks-reducer server and download the result.",
|
58
|
+
)
|
59
|
+
parser.add_argument("input", type=Path, help="Path to the video file to upload.")
|
60
|
+
parser.add_argument(
|
61
|
+
"--server",
|
62
|
+
default="http://127.0.0.1:9005/",
|
63
|
+
help="Base URL for the talks-reducer server (default: http://127.0.0.1:9005/).",
|
64
|
+
)
|
65
|
+
parser.add_argument(
|
66
|
+
"--output",
|
67
|
+
type=Path,
|
68
|
+
default=None,
|
69
|
+
help="Where to store the processed video. Defaults to the working directory.",
|
70
|
+
)
|
71
|
+
parser.add_argument(
|
72
|
+
"--small",
|
73
|
+
action="store_true",
|
74
|
+
help="Toggle the 'Small video' preset before processing.",
|
75
|
+
)
|
76
|
+
parser.add_argument(
|
77
|
+
"--print-log",
|
78
|
+
action="store_true",
|
79
|
+
help="Print the server log after processing completes.",
|
80
|
+
)
|
81
|
+
return parser
|
82
|
+
|
83
|
+
|
84
|
+
def main(argv: Optional[Sequence[str]] = None) -> None:
|
85
|
+
parser = _build_parser()
|
86
|
+
args = parser.parse_args(argv)
|
87
|
+
|
88
|
+
destination, summary, log_text = send_video(
|
89
|
+
input_path=args.input.expanduser(),
|
90
|
+
output_path=args.output.expanduser() if args.output else None,
|
91
|
+
server_url=args.server,
|
92
|
+
small=args.small,
|
93
|
+
)
|
94
|
+
|
95
|
+
print(summary)
|
96
|
+
print(f"Saved processed video to {destination}")
|
97
|
+
if args.print_log:
|
98
|
+
print("\nServer log:\n" + log_text)
|
99
|
+
|
100
|
+
|
101
|
+
if __name__ == "__main__": # pragma: no cover
|
102
|
+
main()
|
@@ -1,93 +1,119 @@
|
|
1
|
-
Metadata-Version: 2.4
|
2
|
-
Name: talks-reducer
|
3
|
-
Version: 0.5.
|
4
|
-
Summary: CLI for speeding up long-form talks by removing silence
|
5
|
-
Author: Talks Reducer Maintainers
|
6
|
-
License-Expression: MIT
|
7
|
-
Requires-Python: >=3.9
|
8
|
-
Description-Content-Type: text/markdown
|
9
|
-
License-File: LICENSE
|
10
|
-
Requires-Dist: audiotsm>=0.1.2
|
11
|
-
Requires-Dist: scipy>=1.10.0
|
12
|
-
Requires-Dist: numpy>=1.22.0
|
13
|
-
Requires-Dist: tqdm>=4.65.0
|
14
|
-
Requires-Dist: tkinterdnd2>=0.3.0
|
15
|
-
Requires-Dist: Pillow>=9.0.0
|
16
|
-
Requires-Dist: imageio-ffmpeg>=0.4.8
|
17
|
-
Requires-Dist: gradio>=4.0.0
|
18
|
-
Provides-Extra: dev
|
19
|
-
Requires-Dist: build>=1.0.0; extra == "dev"
|
20
|
-
Requires-Dist: twine>=4.0.0; extra == "dev"
|
21
|
-
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
22
|
-
Requires-Dist: black>=23.0.0; extra == "dev"
|
23
|
-
Requires-Dist: isort>=5.12.0; extra == "dev"
|
24
|
-
Requires-Dist: bump-my-version>=0.5.0; extra == "dev"
|
25
|
-
Requires-Dist: pyinstaller>=6.4.0; extra == "dev"
|
26
|
-
Dynamic: license-file
|
27
|
-
|
28
|
-
# Talks Reducer
|
29
|
-
Talks Reducer shortens long-form presentations by removing silent gaps and optionally re-encoding them to smaller files. The
|
30
|
-
project was renamed from **jumpcutter** to emphasize its focus on conference talks and screencasts.
|
31
|
-
|
32
|
-

|
33
|
-
|
34
|
-
## Example
|
35
|
-
- 1h 37m, 571 MB — Original OBS video recording
|
36
|
-
- 1h 19m, 751 MB — Talks Reducer
|
37
|
-
- 1h 19m, 171 MB — Talks Reducer `--small`
|
38
|
-
|
39
|
-
## Changelog
|
40
|
-
|
41
|
-
See [CHANGELOG.md](CHANGELOG.md).
|
42
|
-
|
43
|
-
## Install GUI (Windows, macOS)
|
44
|
-
Go to the [releases page](https://github.com/popstas/talks-reducer/releases) and download the appropriate artifact:
|
45
|
-
|
46
|
-
- **Windows** — `talks-reducer-windows-0.4.0.zip`
|
47
|
-
- **macOS** — `talks-reducer.app.zip`
|
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
|
-
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: talks-reducer
|
3
|
+
Version: 0.5.1
|
4
|
+
Summary: CLI for speeding up long-form talks by removing silence
|
5
|
+
Author: Talks Reducer Maintainers
|
6
|
+
License-Expression: MIT
|
7
|
+
Requires-Python: >=3.9
|
8
|
+
Description-Content-Type: text/markdown
|
9
|
+
License-File: LICENSE
|
10
|
+
Requires-Dist: audiotsm>=0.1.2
|
11
|
+
Requires-Dist: scipy>=1.10.0
|
12
|
+
Requires-Dist: numpy>=1.22.0
|
13
|
+
Requires-Dist: tqdm>=4.65.0
|
14
|
+
Requires-Dist: tkinterdnd2>=0.3.0
|
15
|
+
Requires-Dist: Pillow>=9.0.0
|
16
|
+
Requires-Dist: imageio-ffmpeg>=0.4.8
|
17
|
+
Requires-Dist: gradio>=4.0.0
|
18
|
+
Provides-Extra: dev
|
19
|
+
Requires-Dist: build>=1.0.0; extra == "dev"
|
20
|
+
Requires-Dist: twine>=4.0.0; extra == "dev"
|
21
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
22
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
23
|
+
Requires-Dist: isort>=5.12.0; extra == "dev"
|
24
|
+
Requires-Dist: bump-my-version>=0.5.0; extra == "dev"
|
25
|
+
Requires-Dist: pyinstaller>=6.4.0; extra == "dev"
|
26
|
+
Dynamic: license-file
|
27
|
+
|
28
|
+
# Talks Reducer
|
29
|
+
Talks Reducer shortens long-form presentations by removing silent gaps and optionally re-encoding them to smaller files. The
|
30
|
+
project was renamed from **jumpcutter** to emphasize its focus on conference talks and screencasts.
|
31
|
+
|
32
|
+

|
33
|
+
|
34
|
+
## Example
|
35
|
+
- 1h 37m, 571 MB — Original OBS video recording
|
36
|
+
- 1h 19m, 751 MB — Talks Reducer
|
37
|
+
- 1h 19m, 171 MB — Talks Reducer `--small`
|
38
|
+
|
39
|
+
## Changelog
|
40
|
+
|
41
|
+
See [CHANGELOG.md](CHANGELOG.md).
|
42
|
+
|
43
|
+
## Install GUI (Windows, macOS)
|
44
|
+
Go to the [releases page](https://github.com/popstas/talks-reducer/releases) and download the appropriate artifact:
|
45
|
+
|
46
|
+
- **Windows** — `talks-reducer-windows-0.4.0.zip`
|
47
|
+
- **macOS** — `talks-reducer.app.zip`
|
48
|
+
|
49
|
+
> **Troubleshooting:** If launching the bundle (or running `python talks_reducer/gui.py`) prints `macOS 26 (2600) or later required, have instead 16 (1600)!`, make sure you're using a Python build that ships a modern Tk. The stock [python.org 3.13.5 installer](https://www.python.org/downloads/release/python-3135/) includes Tk 8.6 and has been verified to work.
|
50
|
+
|
51
|
+
When extracted on Windows the bundled `talks-reducer.exe` behaves like the
|
52
|
+
`python talks_reducer/gui.py` entry point: double-clicking it launches the GUI
|
53
|
+
and passing a video file path (for example via *Open with…* or drag-and-drop
|
54
|
+
onto the executable) automatically queues that recording for processing.
|
55
|
+
|
56
|
+
## Install CLI (Linux, Windows, macOS)
|
57
|
+
```
|
58
|
+
pip install talks-reducer
|
59
|
+
```
|
60
|
+
|
61
|
+
**Note:** FFmpeg is now bundled automatically with the package, so you don't need to install it separately. You you need, don't know actually.
|
62
|
+
|
63
|
+
The `--small` preset applies a 720p video scale and 128 kbps audio bitrate, making it useful for sharing talks over constrained
|
64
|
+
connections. Without `--small`, the script aims to preserve original quality while removing silence.
|
65
|
+
|
66
|
+
Example CLI usage:
|
67
|
+
|
68
|
+
```sh
|
69
|
+
talks-reducer --small input.mp4
|
70
|
+
```
|
71
|
+
|
72
|
+
### Speech detection
|
73
|
+
|
74
|
+
Talks Reducer now relies on its built-in volume thresholding to detect speech. Adjust `--silent_threshold` if you need to fine-tune when segments count as silence. Dropping the optional Silero VAD integration keeps the install lightweight and avoids pulling in PyTorch.
|
75
|
+
|
76
|
+
When CUDA-capable hardware is available the pipeline leans on GPU encoders to keep export times low, but it still runs great on
|
77
|
+
CPUs.
|
78
|
+
|
79
|
+
## Simple web server
|
80
|
+
|
81
|
+
Prefer a lightweight browser interface? Launch the Gradio-powered simple mode with:
|
82
|
+
|
83
|
+
```sh
|
84
|
+
talks-reducer server
|
85
|
+
```
|
86
|
+
|
87
|
+
This opens a local web page featuring a drag-and-drop upload zone, a **Small video** checkbox that mirrors the CLI preset, a live
|
88
|
+
progress indicator, and automatic previews of the processed output. Once the job completes you can inspect the resulting compression
|
89
|
+
ratio and download the rendered video directly from the page.
|
90
|
+
|
91
|
+
### Uploading and retrieving a processed video
|
92
|
+
|
93
|
+
1. Open the printed `http://localhost:<port>` address (the default port is `9005`).
|
94
|
+
2. Drag a video onto the **Video file** drop zone or click to browse and select one from disk.
|
95
|
+
3. (Optional) Enable **Small video** before the upload finishes to apply the 720p/128 kbps preset.
|
96
|
+
4. Wait for the progress bar and log to report completion—the interface queues work automatically after the file arrives.
|
97
|
+
5. Watch the processed preview in the **Processed video** player and click **Download processed file** to save the result locally.
|
98
|
+
|
99
|
+
Need to change where the server listens? Run `talks-reducer server --host 0.0.0.0 --port 7860` (or any other port) to bind to a
|
100
|
+
different address.
|
101
|
+
|
102
|
+
### Automating uploads from the command line
|
103
|
+
|
104
|
+
Prefer to script uploads instead of using the browser UI? Start the server and use the bundled helper to submit a job and save
|
105
|
+
the processed video locally:
|
106
|
+
|
107
|
+
```sh
|
108
|
+
python -m talks_reducer.service_client --server http://127.0.0.1:9005/ --input demo.mp4 --output output/demo_processed.mp4
|
109
|
+
```
|
110
|
+
|
111
|
+
The helper wraps the Gradio API exposed by `server.py`, waits for processing to complete, then copies the rendered file to the
|
112
|
+
path you provide. Pass `--small` to mirror the **Small video** checkbox or `--print-log` to stream the server log after the
|
113
|
+
download finishes.
|
114
|
+
|
115
|
+
## Contributing
|
116
|
+
See `CONTRIBUTION.md` for development setup details and guidance on sharing improvements.
|
117
|
+
|
118
|
+
## License
|
119
|
+
Talks Reducer is released under the MIT License. See `LICENSE` for the full text.
|
@@ -13,6 +13,7 @@ talks_reducer/models.py
|
|
13
13
|
talks_reducer/pipeline.py
|
14
14
|
talks_reducer/progress.py
|
15
15
|
talks_reducer/server.py
|
16
|
+
talks_reducer/service_client.py
|
16
17
|
talks_reducer.egg-info/PKG-INFO
|
17
18
|
talks_reducer.egg-info/SOURCES.txt
|
18
19
|
talks_reducer.egg-info/dependency_links.txt
|
@@ -22,4 +23,5 @@ talks_reducer.egg-info/top_level.txt
|
|
22
23
|
tests/test_audio.py
|
23
24
|
tests/test_cli.py
|
24
25
|
tests/test_pipeline_service.py
|
25
|
-
tests/test_server.py
|
26
|
+
tests/test_server.py
|
27
|
+
tests/test_service_client.py
|
@@ -0,0 +1,112 @@
|
|
1
|
+
from types import SimpleNamespace
|
2
|
+
|
3
|
+
import pytest
|
4
|
+
|
5
|
+
from talks_reducer import service_client
|
6
|
+
|
7
|
+
|
8
|
+
class DummyClient:
|
9
|
+
def __init__(self, server_url: str) -> None:
|
10
|
+
self.server_url = server_url
|
11
|
+
self.predictions = []
|
12
|
+
|
13
|
+
def predict(self, *args, **kwargs): # pragma: no cover - replaced in tests
|
14
|
+
raise NotImplementedError
|
15
|
+
|
16
|
+
|
17
|
+
def test_send_video_downloads_file(monkeypatch, tmp_path):
|
18
|
+
input_file = tmp_path / "input.mp4"
|
19
|
+
input_file.write_bytes(b"input")
|
20
|
+
server_file = tmp_path / "server_output.mp4"
|
21
|
+
server_file.write_bytes(b"processed")
|
22
|
+
|
23
|
+
client_instance = DummyClient("http://localhost:9005/")
|
24
|
+
|
25
|
+
def fake_predict(file_arg, small_flag, api_name):
|
26
|
+
assert api_name == "/process_video"
|
27
|
+
assert small_flag is True
|
28
|
+
client_instance.predictions.append((file_arg, small_flag, api_name))
|
29
|
+
return (None, "log", "summary", str(server_file))
|
30
|
+
|
31
|
+
client_instance.predict = fake_predict
|
32
|
+
|
33
|
+
monkeypatch.setattr(service_client, "Client", lambda url: client_instance)
|
34
|
+
monkeypatch.setattr(
|
35
|
+
service_client, "gradio_file", lambda path: SimpleNamespace(path=path)
|
36
|
+
)
|
37
|
+
|
38
|
+
destination, summary, log_text = service_client.send_video(
|
39
|
+
input_path=input_file,
|
40
|
+
output_path=tmp_path / "output.mp4",
|
41
|
+
server_url="http://localhost:9005/",
|
42
|
+
small=True,
|
43
|
+
)
|
44
|
+
|
45
|
+
assert destination == tmp_path / "output.mp4"
|
46
|
+
assert destination.read_bytes() == server_file.read_bytes()
|
47
|
+
assert summary == "summary"
|
48
|
+
assert log_text == "log"
|
49
|
+
assert client_instance.predictions, "predict was not called"
|
50
|
+
|
51
|
+
|
52
|
+
def test_send_video_defaults_to_current_directory(monkeypatch, tmp_path, cwd_tmp_path):
|
53
|
+
input_file = tmp_path / "input.mp4"
|
54
|
+
input_file.write_bytes(b"input")
|
55
|
+
server_file = tmp_path / "server_output.mp4"
|
56
|
+
server_file.write_bytes(b"processed")
|
57
|
+
|
58
|
+
client_instance = DummyClient("http://localhost:9005/")
|
59
|
+
client_instance.predict = lambda *_, **__: (
|
60
|
+
None,
|
61
|
+
"log",
|
62
|
+
"summary",
|
63
|
+
str(server_file),
|
64
|
+
)
|
65
|
+
|
66
|
+
monkeypatch.setattr(service_client, "Client", lambda url: client_instance)
|
67
|
+
monkeypatch.setattr(
|
68
|
+
service_client, "gradio_file", lambda path: SimpleNamespace(path=path)
|
69
|
+
)
|
70
|
+
|
71
|
+
destination, _, _ = service_client.send_video(
|
72
|
+
input_path=input_file,
|
73
|
+
output_path=None,
|
74
|
+
server_url="http://localhost:9005/",
|
75
|
+
)
|
76
|
+
|
77
|
+
assert destination.parent == cwd_tmp_path
|
78
|
+
assert destination.name == server_file.name
|
79
|
+
assert destination.read_bytes() == server_file.read_bytes()
|
80
|
+
|
81
|
+
|
82
|
+
def test_main_prints_summary(monkeypatch, tmp_path, capsys):
|
83
|
+
input_file = tmp_path / "input.mp4"
|
84
|
+
destination_file = tmp_path / "output.mp4"
|
85
|
+
|
86
|
+
def fake_send_video(**kwargs):
|
87
|
+
assert kwargs["small"] is False
|
88
|
+
return destination_file, "summary", "log"
|
89
|
+
|
90
|
+
monkeypatch.setattr(
|
91
|
+
service_client, "send_video", lambda **kwargs: fake_send_video(**kwargs)
|
92
|
+
)
|
93
|
+
|
94
|
+
service_client.main(
|
95
|
+
[
|
96
|
+
str(input_file),
|
97
|
+
"--server",
|
98
|
+
"http://localhost:9005/",
|
99
|
+
"--output",
|
100
|
+
str(destination_file),
|
101
|
+
]
|
102
|
+
)
|
103
|
+
|
104
|
+
captured = capsys.readouterr()
|
105
|
+
assert "summary" in captured.out
|
106
|
+
assert str(destination_file) in captured.out
|
107
|
+
|
108
|
+
|
109
|
+
@pytest.fixture
|
110
|
+
def cwd_tmp_path(monkeypatch, tmp_path):
|
111
|
+
monkeypatch.chdir(tmp_path)
|
112
|
+
return tmp_path
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|