talks-reducer 0.3.2__py3-none-any.whl → 0.3.4__py3-none-any.whl
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/__about__.py +5 -0
- talks_reducer/__init__.py +2 -1
- talks_reducer/cli.py +28 -5
- talks_reducer/gui.py +49 -42
- talks_reducer/models.py +3 -0
- talks_reducer/pipeline.py +11 -0
- talks_reducer-0.3.4.dist-info/METADATA +71 -0
- talks_reducer-0.3.4.dist-info/RECORD +17 -0
- talks_reducer-0.3.2.dist-info/METADATA +0 -156
- talks_reducer-0.3.2.dist-info/RECORD +0 -16
- {talks_reducer-0.3.2.dist-info → talks_reducer-0.3.4.dist-info}/WHEEL +0 -0
- {talks_reducer-0.3.2.dist-info → talks_reducer-0.3.4.dist-info}/entry_points.txt +0 -0
- {talks_reducer-0.3.2.dist-info → talks_reducer-0.3.4.dist-info}/licenses/LICENSE +0 -0
- {talks_reducer-0.3.2.dist-info → talks_reducer-0.3.4.dist-info}/top_level.txt +0 -0
talks_reducer/__init__.py
CHANGED
talks_reducer/cli.py
CHANGED
@@ -7,11 +7,16 @@ import os
|
|
7
7
|
import sys
|
8
8
|
import time
|
9
9
|
from importlib import import_module
|
10
|
-
from importlib.metadata import version
|
10
|
+
from importlib.metadata import PackageNotFoundError, version
|
11
11
|
from pathlib import Path
|
12
12
|
from typing import Dict, List, Optional, Sequence
|
13
13
|
|
14
14
|
from . import audio
|
15
|
+
|
16
|
+
try:
|
17
|
+
from .__about__ import __version__ as _about_version
|
18
|
+
except Exception: # pragma: no cover - fallback if metadata file missing
|
19
|
+
_about_version = ""
|
15
20
|
from .ffmpeg import FFmpegNotFoundError
|
16
21
|
from .models import ProcessingOptions
|
17
22
|
from .pipeline import speed_up_video
|
@@ -26,10 +31,7 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
26
31
|
)
|
27
32
|
|
28
33
|
# Add version argument
|
29
|
-
|
30
|
-
pkg_version = version("talks-reducer")
|
31
|
-
except Exception:
|
32
|
-
pkg_version = "unknown"
|
34
|
+
pkg_version = _resolve_version()
|
33
35
|
|
34
36
|
parser.add_argument(
|
35
37
|
"--version",
|
@@ -99,6 +101,18 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
99
101
|
return parser
|
100
102
|
|
101
103
|
|
104
|
+
def _resolve_version() -> str:
|
105
|
+
"""Determine the package version for CLI reporting."""
|
106
|
+
|
107
|
+
if _about_version:
|
108
|
+
return _about_version
|
109
|
+
|
110
|
+
try:
|
111
|
+
return version("talks-reducer")
|
112
|
+
except (PackageNotFoundError, Exception):
|
113
|
+
return "unknown"
|
114
|
+
|
115
|
+
|
102
116
|
def gather_input_files(paths: List[str]) -> List[str]:
|
103
117
|
"""Expand provided paths into a flat list of files that contain audio streams."""
|
104
118
|
|
@@ -196,6 +210,15 @@ def main(argv: Optional[Sequence[str]] = None) -> None:
|
|
196
210
|
sys.exit(1)
|
197
211
|
|
198
212
|
reporter.log(f"Completed: {result.output_file}")
|
213
|
+
summary_parts = []
|
214
|
+
time_ratio = getattr(result, "time_ratio", None)
|
215
|
+
size_ratio = getattr(result, "size_ratio", None)
|
216
|
+
if time_ratio is not None:
|
217
|
+
summary_parts.append(f"{time_ratio * 100:.0f}% time")
|
218
|
+
if size_ratio is not None:
|
219
|
+
summary_parts.append(f"{size_ratio * 100:.0f}% size")
|
220
|
+
if summary_parts:
|
221
|
+
reporter.log("Result: " + ", ".join(summary_parts))
|
199
222
|
|
200
223
|
end_time = time.time()
|
201
224
|
total_time = end_time - start_time
|
talks_reducer/gui.py
CHANGED
@@ -291,6 +291,8 @@ class TalksReducerGUI:
|
|
291
291
|
|
292
292
|
self._processing_thread: Optional[threading.Thread] = None
|
293
293
|
self._last_output: Optional[Path] = None
|
294
|
+
self._last_time_ratio: Optional[float] = None
|
295
|
+
self._last_size_ratio: Optional[float] = None
|
294
296
|
self._status_state = "Idle"
|
295
297
|
self.status_var = tk.StringVar(value=self._status_state)
|
296
298
|
self._status_animation_job: Optional[str] = None
|
@@ -384,7 +386,7 @@ class TalksReducerGUI:
|
|
384
386
|
|
385
387
|
self.drop_zone = self.tk.Label(
|
386
388
|
input_frame,
|
387
|
-
text="Drop
|
389
|
+
text="Drop video here",
|
388
390
|
relief=self.tk.FLAT,
|
389
391
|
borderwidth=0,
|
390
392
|
padx=self.PADDING,
|
@@ -445,7 +447,9 @@ class TalksReducerGUI:
|
|
445
447
|
variable=self.simple_mode_var,
|
446
448
|
command=self._toggle_simple_mode,
|
447
449
|
)
|
448
|
-
self.simple_mode_check.grid(
|
450
|
+
self.simple_mode_check.grid(
|
451
|
+
row=1, column=0, columnspan=3, sticky="w", pady=(8, 0)
|
452
|
+
)
|
449
453
|
|
450
454
|
self.advanced_visible = self.tk.BooleanVar(value=False)
|
451
455
|
self.advanced_button = self.ttk.Button(
|
@@ -980,7 +984,15 @@ class TalksReducerGUI:
|
|
980
984
|
options = self._build_options(Path(file), args)
|
981
985
|
result = speed_up_video(options, reporter=reporter)
|
982
986
|
self._last_output = result.output_file
|
983
|
-
self.
|
987
|
+
self._last_time_ratio = result.time_ratio
|
988
|
+
self._last_size_ratio = result.size_ratio
|
989
|
+
|
990
|
+
# Create completion message with ratios if available
|
991
|
+
completion_msg = f"Completed: {result.output_file}"
|
992
|
+
if result.time_ratio is not None and result.size_ratio is not None:
|
993
|
+
completion_msg += f" (Time: {result.time_ratio:.2%}, Size: {result.size_ratio:.2%})"
|
994
|
+
|
995
|
+
self._append_log(completion_msg)
|
984
996
|
if open_after_convert:
|
985
997
|
self._notify(
|
986
998
|
lambda path=result.output_file: self._open_in_file_manager(
|
@@ -1135,17 +1147,22 @@ class TalksReducerGUI:
|
|
1135
1147
|
def _update_status_from_message(self, message: str) -> None:
|
1136
1148
|
normalized = message.strip().lower()
|
1137
1149
|
if "all jobs finished successfully" in normalized:
|
1138
|
-
|
1150
|
+
# Create status message with ratios if available
|
1151
|
+
status_msg = "Success"
|
1152
|
+
if self._last_time_ratio is not None and self._last_size_ratio is not None:
|
1153
|
+
status_msg = f"Time: {self._last_time_ratio:.0%}, Size: {self._last_size_ratio:.0%}"
|
1154
|
+
|
1155
|
+
self._set_status("success", status_msg)
|
1139
1156
|
self._set_progress(100) # 100% on success
|
1140
1157
|
self._video_duration_seconds = None # Reset for next video
|
1141
1158
|
elif normalized.startswith("extracting audio"):
|
1142
|
-
self._set_status("Extracting audio...")
|
1159
|
+
self._set_status("processing", "Extracting audio...")
|
1143
1160
|
self._set_progress(0) # 0% on start
|
1144
1161
|
self._video_duration_seconds = None # Reset for new processing
|
1145
1162
|
elif normalized.startswith("starting processing") or normalized.startswith(
|
1146
1163
|
"processing"
|
1147
1164
|
):
|
1148
|
-
self._set_status("Processing")
|
1165
|
+
self._set_status("processing", "Processing")
|
1149
1166
|
self._set_progress(0) # 0% on start
|
1150
1167
|
self._video_duration_seconds = None # Reset for new processing
|
1151
1168
|
|
@@ -1174,10 +1191,12 @@ class TalksReducerGUI:
|
|
1174
1191
|
percentage = min(
|
1175
1192
|
100, int((current_seconds / self._video_duration_seconds) * 100)
|
1176
1193
|
)
|
1177
|
-
self._set_status(
|
1194
|
+
self._set_status(
|
1195
|
+
"processing", f"{time_str}, {speed_str}x ({percentage}%)"
|
1196
|
+
)
|
1178
1197
|
self._set_progress(percentage) # Update progress bar
|
1179
1198
|
else:
|
1180
|
-
self._set_status(f"{time_str}, {speed_str}x")
|
1199
|
+
self._set_status("processing", f"{time_str}, {speed_str}x")
|
1181
1200
|
|
1182
1201
|
def _apply_status_style(self, status: str) -> None:
|
1183
1202
|
color = STATUS_COLORS.get(status.lower())
|
@@ -1185,30 +1204,40 @@ class TalksReducerGUI:
|
|
1185
1204
|
self.status_label.configure(fg=color)
|
1186
1205
|
else:
|
1187
1206
|
# For extracting audio or FFmpeg progress messages, use processing color
|
1188
|
-
|
1189
|
-
|
1207
|
+
# Also handle the new "Time: X%, Size: Y%" format as success
|
1208
|
+
status_lower = status.lower()
|
1209
|
+
if (
|
1210
|
+
"extracting audio" in status_lower
|
1211
|
+
or re.search(r"\d{2}:\d{2}:\d{2}.*\d+\.?\d*x", status)
|
1212
|
+
or ("time:" in status_lower and "size:" in status_lower)
|
1190
1213
|
):
|
1191
|
-
|
1214
|
+
if "time:" in status_lower and "size:" in status_lower:
|
1215
|
+
# This is our new success format with ratios
|
1216
|
+
self.status_label.configure(fg=STATUS_COLORS["success"])
|
1217
|
+
else:
|
1218
|
+
self.status_label.configure(fg=STATUS_COLORS["processing"])
|
1192
1219
|
|
1193
|
-
def _set_status(self, status: str) -> None:
|
1220
|
+
def _set_status(self, status: str, status_msg: str = "") -> None:
|
1194
1221
|
def apply() -> None:
|
1195
|
-
self._stop_status_animation()
|
1196
1222
|
self._status_state = status
|
1197
|
-
|
1198
|
-
|
1223
|
+
# Use status_msg if provided, otherwise use status
|
1224
|
+
display_text = status_msg if status_msg else status
|
1225
|
+
self.status_var.set(display_text)
|
1226
|
+
self._apply_status_style(
|
1227
|
+
status
|
1228
|
+
) # Colors depend on status, not display text
|
1199
1229
|
self._set_progress_bar_style(status)
|
1200
1230
|
lowered = status.lower()
|
1201
1231
|
is_processing = lowered == "processing" or "extracting audio" in lowered
|
1202
1232
|
|
1203
1233
|
if is_processing:
|
1204
|
-
self._start_status_animation()
|
1205
1234
|
# Show stop button during processing
|
1206
1235
|
if hasattr(self, "status_frame"):
|
1207
1236
|
self.status_frame.grid()
|
1208
1237
|
self.stop_button.grid()
|
1209
1238
|
self.drop_hint_button.grid_remove()
|
1210
1239
|
|
1211
|
-
if lowered == "success":
|
1240
|
+
if lowered == "success" or "time:" in lowered and "size:" in lowered:
|
1212
1241
|
if self.simple_mode_var.get() and hasattr(self, "status_frame"):
|
1213
1242
|
self.status_frame.grid()
|
1214
1243
|
self.stop_button.grid_remove()
|
@@ -1232,30 +1261,6 @@ class TalksReducerGUI:
|
|
1232
1261
|
|
1233
1262
|
self.root.after(0, apply)
|
1234
1263
|
|
1235
|
-
def _start_status_animation(self) -> None:
|
1236
|
-
self._status_animation_phase = 0
|
1237
|
-
self._schedule_status_animation()
|
1238
|
-
|
1239
|
-
def _schedule_status_animation(self) -> None:
|
1240
|
-
if self._status_state.lower() != "processing":
|
1241
|
-
return
|
1242
|
-
|
1243
|
-
dots = self._status_animation_phase % 4
|
1244
|
-
suffix = "." * dots
|
1245
|
-
text = "Processing" + suffix
|
1246
|
-
self.status_var.set(text)
|
1247
|
-
self._status_animation_phase = (self._status_animation_phase + 1) % 4
|
1248
|
-
self._status_animation_job = self.root.after(
|
1249
|
-
400, self._schedule_status_animation
|
1250
|
-
)
|
1251
|
-
|
1252
|
-
def _stop_status_animation(self) -> None:
|
1253
|
-
if self._status_animation_job is not None:
|
1254
|
-
self.root.after_cancel(self._status_animation_job)
|
1255
|
-
self._status_animation_job = None
|
1256
|
-
if self._status_state.lower() != "processing":
|
1257
|
-
self.status_var.set(self._status_state)
|
1258
|
-
|
1259
1264
|
def _calculate_gradient_color(self, percentage: int, darken: float = 1.0) -> str:
|
1260
1265
|
"""Calculate color gradient from red (0%) to green (100%).
|
1261
1266
|
|
@@ -1334,7 +1339,9 @@ class TalksReducerGUI:
|
|
1334
1339
|
def updater() -> None:
|
1335
1340
|
# Map status to progress bar style
|
1336
1341
|
status_lower = status.lower()
|
1337
|
-
if status_lower == "success"
|
1342
|
+
if status_lower == "success" or (
|
1343
|
+
"time:" in status_lower and "size:" in status_lower
|
1344
|
+
):
|
1338
1345
|
style = "Success.Horizontal.TProgressbar"
|
1339
1346
|
elif status_lower == "error":
|
1340
1347
|
style = "Error.Horizontal.TProgressbar"
|
talks_reducer/models.py
CHANGED
talks_reducer/pipeline.py
CHANGED
@@ -270,12 +270,23 @@ def speed_up_video(
|
|
270
270
|
finally:
|
271
271
|
_delete_path(temp_path)
|
272
272
|
|
273
|
+
output_metadata = _extract_video_metadata(output_path, frame_rate)
|
274
|
+
output_duration = output_metadata.get("duration", 0.0)
|
275
|
+
time_ratio = output_duration / original_duration if original_duration > 0 else None
|
276
|
+
|
277
|
+
input_size = input_path.stat().st_size if input_path.exists() else 0
|
278
|
+
output_size = output_path.stat().st_size if output_path.exists() else 0
|
279
|
+
size_ratio = (output_size / input_size) if input_size > 0 else None
|
280
|
+
|
273
281
|
return ProcessingResult(
|
274
282
|
input_file=input_path,
|
275
283
|
output_file=output_path,
|
276
284
|
frame_rate=frame_rate,
|
277
285
|
original_duration=original_duration,
|
286
|
+
output_duration=output_duration,
|
278
287
|
chunk_count=len(chunks),
|
279
288
|
used_cuda=use_cuda_encoder,
|
280
289
|
max_audio_volume=max_audio_volume,
|
290
|
+
time_ratio=time_ratio,
|
291
|
+
size_ratio=size_ratio,
|
281
292
|
)
|
@@ -0,0 +1,71 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: talks-reducer
|
3
|
+
Version: 0.3.4
|
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
|
+
Provides-Extra: dev
|
18
|
+
Requires-Dist: build>=1.0.0; extra == "dev"
|
19
|
+
Requires-Dist: twine>=4.0.0; extra == "dev"
|
20
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
21
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
22
|
+
Requires-Dist: isort>=5.12.0; extra == "dev"
|
23
|
+
Requires-Dist: bump-my-version>=0.5.0; extra == "dev"
|
24
|
+
Requires-Dist: pyinstaller>=6.0.0; extra == "dev"
|
25
|
+
Dynamic: license-file
|
26
|
+
|
27
|
+
# Talks Reducer
|
28
|
+
Talks Reducer shortens long-form presentations by removing silent gaps and optionally re-encoding them to smaller files. The
|
29
|
+
project was renamed from **jumpcutter** to emphasize its focus on conference talks and screencasts.
|
30
|
+
|
31
|
+

|
32
|
+
|
33
|
+
## Example
|
34
|
+
- 1h 37m, 571 MB — Original OBS video recording
|
35
|
+
- 1h 19m, 751 MB — Talks Reducer
|
36
|
+
- 1h 19m, 171 MB — Talks Reducer `--small`
|
37
|
+
|
38
|
+
## Changelog
|
39
|
+
|
40
|
+
See [CHANGELOG.md](CHANGELOG.md).
|
41
|
+
|
42
|
+
## Install GUI (Windows, macOS)
|
43
|
+
Go to the [releases page](https://github.com/popstas/talks-reducer/releases) and download the appropriate artifact:
|
44
|
+
|
45
|
+
- **Windows** — `talks-reducer-windows.zip`
|
46
|
+
- **macOS** — `talks-reducer.app.zip` (but it doesn't work for me)
|
47
|
+
|
48
|
+
## Install CLI (Linux, Windows, macOS)
|
49
|
+
```
|
50
|
+
pip install talks-reducer
|
51
|
+
```
|
52
|
+
|
53
|
+
**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.
|
54
|
+
|
55
|
+
The `--small` preset applies a 720p video scale and 128 kbps audio bitrate, making it useful for sharing talks over constrained
|
56
|
+
connections. Without `--small`, the script aims to preserve original quality while removing silence.
|
57
|
+
|
58
|
+
Example CLI usage:
|
59
|
+
|
60
|
+
```sh
|
61
|
+
talks-reducer --small input.mp4
|
62
|
+
```
|
63
|
+
|
64
|
+
When CUDA-capable hardware is available the pipeline leans on GPU encoders to keep export times low, but it still runs great on
|
65
|
+
CPUs.
|
66
|
+
|
67
|
+
## Contributing
|
68
|
+
See `CONTRIBUTION.md` for development setup details and guidance on sharing improvements.
|
69
|
+
|
70
|
+
## License
|
71
|
+
Talks Reducer is released under the MIT License. See `LICENSE` for the full text.
|
@@ -0,0 +1,17 @@
|
|
1
|
+
talks_reducer/__about__.py,sha256=QKvksbkeX8Nh1lKRKVvST5MfsCfD8RoEaybMMqQU64k,92
|
2
|
+
talks_reducer/__init__.py,sha256=Kzh1hXaw6Vq3DyTqrnJGOq8pn0P8lvaDcsg1bFUjFKk,208
|
3
|
+
talks_reducer/__main__.py,sha256=azR_vh8HFPLaOnh-L6gUFWsL67I6iHtbeH5rQhsipGY,299
|
4
|
+
talks_reducer/audio.py,sha256=sjHMeY0H9ESG-Gn5BX0wFRBX7sXjWwsgS8u9Vb0bJ88,4396
|
5
|
+
talks_reducer/chunks.py,sha256=IpdZxRFPURSG5wP-OQ_p09CVP8wcKwIFysV29zOTSWI,2959
|
6
|
+
talks_reducer/cli.py,sha256=OYmahiEo7ivhix4861pN9Kp1DkRvU7WBj6fBE2cVVWU,7377
|
7
|
+
talks_reducer/ffmpeg.py,sha256=CVrxwNcWHrzvxTzoALtx5UdNWXxxfOFYF3FES7lvaO4,11680
|
8
|
+
talks_reducer/gui.py,sha256=xsJj1uO1WX14rNVSrkQf2b4K6BdNDbeZ-A1bB0fsSIM,54463
|
9
|
+
talks_reducer/models.py,sha256=vdQLliiHKUuYtNlZzS796kGK39cbtjkUfYcT95KwwKE,1197
|
10
|
+
talks_reducer/pipeline.py,sha256=nfAX8dooN3-009WqMyYTv4nINNMtVmbWtsmzQeBM9Wg,9415
|
11
|
+
talks_reducer/progress.py,sha256=Mh43M6VWhjjUv9CI22xfD2EJ_7Aq3PCueqefQ9Bd5-o,4565
|
12
|
+
talks_reducer-0.3.4.dist-info/licenses/LICENSE,sha256=jN17mHNR3e84awmH3AbpWBcBDBzPxEH0rcOFoj1s7sQ,1124
|
13
|
+
talks_reducer-0.3.4.dist-info/METADATA,sha256=FCWbqUziHsU3pAsIeKGXhw_P00ex6yF3r1_ebdVH2t8,2443
|
14
|
+
talks_reducer-0.3.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
15
|
+
talks_reducer-0.3.4.dist-info/entry_points.txt,sha256=LCzfSnh_7VXhvl9twoFSAj0C3sG7bayWs2LkxpH7hoI,100
|
16
|
+
talks_reducer-0.3.4.dist-info/top_level.txt,sha256=pJWGcy__LR9JIEKH3QJyFmk9XrIsiFtqvuMNxFdIzDU,14
|
17
|
+
talks_reducer-0.3.4.dist-info/RECORD,,
|
@@ -1,156 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.4
|
2
|
-
Name: talks-reducer
|
3
|
-
Version: 0.3.2
|
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
|
-
Provides-Extra: dev
|
18
|
-
Requires-Dist: build>=1.0.0; extra == "dev"
|
19
|
-
Requires-Dist: twine>=4.0.0; extra == "dev"
|
20
|
-
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
21
|
-
Requires-Dist: black>=23.0.0; extra == "dev"
|
22
|
-
Requires-Dist: isort>=5.12.0; extra == "dev"
|
23
|
-
Requires-Dist: pyinstaller>=6.0.0; extra == "dev"
|
24
|
-
Dynamic: license-file
|
25
|
-
|
26
|
-
# Talks Reducer
|
27
|
-
Talks Reducer shortens long-form presentations by removing silent gaps and optionally re-encoding them to smaller files. The
|
28
|
-
project was renamed from **jumpcutter** to emphasize its focus on conference talks and screencasts.
|
29
|
-
|
30
|
-
## Example
|
31
|
-
- 1h 37m, 571 MB — Original OBS video recording
|
32
|
-
- 1h 19m, 751 MB — Talks Reducer
|
33
|
-
- 1h 19m, 171 MB — Talks Reducer `--small`
|
34
|
-
|
35
|
-
## Changelog
|
36
|
-
|
37
|
-
See [CHANGELOG.md](CHANGELOG.md).
|
38
|
-
|
39
|
-
## Install GUI (Windows, macOS)
|
40
|
-
Go to the [releases page](https://github.com/popstas/talks-reducer/releases) and download the appropriate artifact:
|
41
|
-
|
42
|
-
- **Windows** — `talks-reducer-gui.exe`
|
43
|
-
- **macOS** — `talks-reducer-gui-macos-universal` (requires macOS 10.13 High Sierra or
|
44
|
-
newer). The bundle is built as a universal (`x86_64` + `arm64`) app so it runs
|
45
|
-
natively on Apple Silicon without Rosetta.
|
46
|
-
|
47
|
-
## Install CLI (Linux, Windows, macOS)
|
48
|
-
```
|
49
|
-
pip install talks-reducer
|
50
|
-
```
|
51
|
-
|
52
|
-
**Note:** FFmpeg is now bundled automatically with the package, so you don't need to install it separately. However, if you have FFmpeg already installed on your system, it will be used instead of the bundled version.
|
53
|
-
|
54
|
-
The `--small` preset applies a 720p video scale and 128 kbps audio bitrate, making it useful for sharing talks over constrained
|
55
|
-
connections. Without `--small`, the script aims to preserve original quality while removing silence.
|
56
|
-
|
57
|
-
> **Tip:** The `talks-reducer` and `talks-reducer-gui` commands now behave the same way: launching them without arguments opens
|
58
|
-
> the Tkinter interface, while passing regular CLI options (for example, `talks-reducer --small input.mp4`) executes the
|
59
|
-
> command-line pipeline. You can keep a single shortcut for both workflows.
|
60
|
-
|
61
|
-
When CUDA-capable hardware is available the pipeline leans on GPU encoders to keep export times low, but it still runs great on
|
62
|
-
CPUs.
|
63
|
-
|
64
|
-
### macOS codesigning and notarization
|
65
|
-
|
66
|
-
Maintainers with Apple Developer credentials can optionally sign and notarize
|
67
|
-
the GUI release to avoid Gatekeeper warnings on download:
|
68
|
-
|
69
|
-
1. Export or create a keychain profile for `notarytool` (see `man
|
70
|
-
notarytool`) and note the profile name.
|
71
|
-
2. Set the following environment variables before running `scripts/build-gui.sh`:
|
72
|
-
- `MACOS_CODESIGN_IDENTITY` — the signing identity, for example
|
73
|
-
`Developer ID Application: Example Corp (TEAMID)`.
|
74
|
-
- `MACOS_CODESIGN_ENTITLEMENTS` *(optional)* — path to an entitlements plist
|
75
|
-
used during codesigning.
|
76
|
-
- `MACOS_NOTARIZE_PROFILE` *(optional)* — the keychain profile name to submit
|
77
|
-
the archive for notarization. When present, the script zips the `.app`,
|
78
|
-
submits it with `notarytool --wait`, and staples the returned ticket.
|
79
|
-
|
80
|
-
The codesigning step executes only when the variables are provided, so the build
|
81
|
-
continues to work unchanged for local development.
|
82
|
-
|
83
|
-
### Graphical Interface
|
84
|
-
|
85
|
-
- **Simple mode** — the default experience shrinks the window to a large drop
|
86
|
-
zone, hides the manual run controls and log, and automatically processes new
|
87
|
-
files as soon as you drop them. Uncheck the box to return to the full layout
|
88
|
-
with file pickers, the Run button, and detailed logging.
|
89
|
-
- **Input drop zone** — drag files or folders from your desktop, click to open
|
90
|
-
the system file picker, or add them via the Explorer/Finder dialog; duplicates
|
91
|
-
are ignored.
|
92
|
-
- **Small video** — toggles the `--small` preset used by the CLI.
|
93
|
-
- **Open after convert** — controls whether the exported file is revealed in
|
94
|
-
your system file manager as soon as each job finishes.
|
95
|
-
- **Advanced** — reveals optional controls for the output path, temp folder,
|
96
|
-
timing/audio knobs mirrored from the command line, and an appearance picker
|
97
|
-
that can force dark or light mode or follow your operating system.
|
98
|
-
|
99
|
-
Progress updates stream into the 10-line log panel while the processing runs in
|
100
|
-
a background thread. Once every queued job succeeds an **Open last output**
|
101
|
-
button appears so you can jump straight to the exported file in your system
|
102
|
-
file manager.
|
103
|
-
|
104
|
-
The GUI stores your last-used Simple mode, Small video, Open after convert, and
|
105
|
-
theme preferences in a cross-platform configuration file so they persist across
|
106
|
-
launches.
|
107
|
-
|
108
|
-
## Repository Structure
|
109
|
-
- `talks_reducer/` — Python package that exposes the CLI and reusable pipeline:
|
110
|
-
- `cli.py` parses arguments and dispatches to the pipeline.
|
111
|
-
- `pipeline.py` orchestrates FFmpeg, audio processing, and temporary assets.
|
112
|
-
- `audio.py` handles audio validation, volume analysis, and phase vocoder processing.
|
113
|
-
- `chunks.py` builds timing metadata and FFmpeg expressions for frame selection.
|
114
|
-
- `ffmpeg.py` discovers the FFmpeg binary, checks CUDA availability, and assembles command strings.
|
115
|
-
- `requirements.txt` — Python dependencies for local development.
|
116
|
-
- `default.nix` — reproducible environment definition for Nix users.
|
117
|
-
- `CONTRIBUTION.md` — development workflow, formatting expectations, and release checklist.
|
118
|
-
- `AGENTS.md` — maintainer tips and coding conventions for this repository.
|
119
|
-
|
120
|
-
## Highlights
|
121
|
-
- Builds on gegell's classic jumpcutter workflow with more efficient frame and audio processing
|
122
|
-
- Generates FFmpeg filter graphs instead of writing temporary frames to disk
|
123
|
-
- Streams audio transformations in memory to avoid slow intermediate files
|
124
|
-
- Accepts multiple inputs or directories of recordings in a single run
|
125
|
-
- Provides progress feedback via `tqdm`
|
126
|
-
- Automatically detects NVENC availability, so you no longer need to pass `--cuda`
|
127
|
-
|
128
|
-
## Processing Pipeline
|
129
|
-
1. Validate that each input file contains an audio stream using `ffprobe`.
|
130
|
-
2. Extract audio and calculate loudness to identify silent regions.
|
131
|
-
3. Stretch the non-silent segments with `audiotsm` to maintain speech clarity.
|
132
|
-
4. Stitch the processed audio and video together with FFmpeg, using NVENC if the GPU encoders are detected.
|
133
|
-
|
134
|
-
## Recent Updates
|
135
|
-
- **October 2025** — Project renamed to *Talks Reducer* across documentation and scripts.
|
136
|
-
- **October 2025** — Added `--small` preset with 720p/128 kbps defaults for bandwidth-friendly exports.
|
137
|
-
- **October 2025** — Removed the `--cuda` flag; CUDA/NVENC support is now auto-detected.
|
138
|
-
|
139
|
-
## Changelog
|
140
|
-
Major and minor releases are tracked in `CHANGELOG.md`. The log is generated from
|
141
|
-
Conventional Commits that start with either `feat:` or `fix:`. Only tags in the
|
142
|
-
form `v<major>.<minor>.0` are included so patch releases (for example,
|
143
|
-
`v1.1.1`) are omitted. Regenerate the file whenever you cut a release:
|
144
|
-
|
145
|
-
```bash
|
146
|
-
python scripts/generate_changelog.py
|
147
|
-
```
|
148
|
-
|
149
|
-
CI will fail if the generated changelog does not match the committed version, so
|
150
|
-
run the script before opening a pull request that updates release tags.
|
151
|
-
|
152
|
-
## Contributing
|
153
|
-
See `CONTRIBUTION.md` for development setup details and guidance on sharing improvements.
|
154
|
-
|
155
|
-
## License
|
156
|
-
Talks Reducer is released under the MIT License. See `LICENSE` for the full text.
|
@@ -1,16 +0,0 @@
|
|
1
|
-
talks_reducer/__init__.py,sha256=lb50C4_o_SLERyMyVpQfgHnXf49FJOIF9j05MZ8KAvM,158
|
2
|
-
talks_reducer/__main__.py,sha256=azR_vh8HFPLaOnh-L6gUFWsL67I6iHtbeH5rQhsipGY,299
|
3
|
-
talks_reducer/audio.py,sha256=sjHMeY0H9ESG-Gn5BX0wFRBX7sXjWwsgS8u9Vb0bJ88,4396
|
4
|
-
talks_reducer/chunks.py,sha256=IpdZxRFPURSG5wP-OQ_p09CVP8wcKwIFysV29zOTSWI,2959
|
5
|
-
talks_reducer/cli.py,sha256=QmQeezgZmkEkZDfdbfQpRlZJy5rtVBm81HLM-ibh670,6567
|
6
|
-
talks_reducer/ffmpeg.py,sha256=CVrxwNcWHrzvxTzoALtx5UdNWXxxfOFYF3FES7lvaO4,11680
|
7
|
-
talks_reducer/gui.py,sha256=Oq2kXERlDEGqxgYsVSjObOPt811tGl-a6DbVPBR8YLs,53683
|
8
|
-
talks_reducer/models.py,sha256=6cZRcJf0EBZIzNd-PWrh4Wdsoa4EBj5nSdB6BnFiOXM,1106
|
9
|
-
talks_reducer/pipeline.py,sha256=yQtvoDGMGjveTo35OtY11CX25-l4EQZCOLMFVtvHFbk,8871
|
10
|
-
talks_reducer/progress.py,sha256=Mh43M6VWhjjUv9CI22xfD2EJ_7Aq3PCueqefQ9Bd5-o,4565
|
11
|
-
talks_reducer-0.3.2.dist-info/licenses/LICENSE,sha256=jN17mHNR3e84awmH3AbpWBcBDBzPxEH0rcOFoj1s7sQ,1124
|
12
|
-
talks_reducer-0.3.2.dist-info/METADATA,sha256=IhoNZ2kWejZdp0QbgUHoZbFSx2dK36sbKHSyrS7NxAE,7773
|
13
|
-
talks_reducer-0.3.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
14
|
-
talks_reducer-0.3.2.dist-info/entry_points.txt,sha256=LCzfSnh_7VXhvl9twoFSAj0C3sG7bayWs2LkxpH7hoI,100
|
15
|
-
talks_reducer-0.3.2.dist-info/top_level.txt,sha256=pJWGcy__LR9JIEKH3QJyFmk9XrIsiFtqvuMNxFdIzDU,14
|
16
|
-
talks_reducer-0.3.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|