talks-reducer 0.2.18__tar.gz → 0.2.21__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.2.18/talks_reducer.egg-info → talks_reducer-0.2.21}/PKG-INFO +17 -1
- {talks_reducer-0.2.18 → talks_reducer-0.2.21}/README.md +15 -0
- {talks_reducer-0.2.18 → talks_reducer-0.2.21}/pyproject.toml +2 -1
- {talks_reducer-0.2.18 → talks_reducer-0.2.21}/talks_reducer/audio.py +14 -1
- {talks_reducer-0.2.18 → talks_reducer-0.2.21}/talks_reducer/cli.py +11 -3
- {talks_reducer-0.2.18 → talks_reducer-0.2.21}/talks_reducer/ffmpeg.py +32 -2
- {talks_reducer-0.2.18 → talks_reducer-0.2.21}/talks_reducer/gui.py +111 -42
- {talks_reducer-0.2.18 → talks_reducer-0.2.21/talks_reducer.egg-info}/PKG-INFO +17 -1
- {talks_reducer-0.2.18 → talks_reducer-0.2.21}/talks_reducer.egg-info/requires.txt +1 -0
- {talks_reducer-0.2.18 → talks_reducer-0.2.21}/LICENSE +0 -0
- {talks_reducer-0.2.18 → talks_reducer-0.2.21}/setup.cfg +0 -0
- {talks_reducer-0.2.18 → talks_reducer-0.2.21}/talks_reducer/__init__.py +0 -0
- {talks_reducer-0.2.18 → talks_reducer-0.2.21}/talks_reducer/__main__.py +0 -0
- {talks_reducer-0.2.18 → talks_reducer-0.2.21}/talks_reducer/chunks.py +0 -0
- {talks_reducer-0.2.18 → talks_reducer-0.2.21}/talks_reducer/models.py +0 -0
- {talks_reducer-0.2.18 → talks_reducer-0.2.21}/talks_reducer/pipeline.py +0 -0
- {talks_reducer-0.2.18 → talks_reducer-0.2.21}/talks_reducer/progress.py +0 -0
- {talks_reducer-0.2.18 → talks_reducer-0.2.21}/talks_reducer.egg-info/SOURCES.txt +0 -0
- {talks_reducer-0.2.18 → talks_reducer-0.2.21}/talks_reducer.egg-info/dependency_links.txt +0 -0
- {talks_reducer-0.2.18 → talks_reducer-0.2.21}/talks_reducer.egg-info/entry_points.txt +0 -0
- {talks_reducer-0.2.18 → talks_reducer-0.2.21}/talks_reducer.egg-info/top_level.txt +0 -0
- {talks_reducer-0.2.18 → talks_reducer-0.2.21}/tests/test_pipeline_service.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: talks-reducer
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.21
|
4
4
|
Summary: CLI for speeding up long-form talks by removing silence
|
5
5
|
Author: Talks Reducer Maintainers
|
6
6
|
License-Expression: MIT
|
@@ -13,6 +13,7 @@ Requires-Dist: numpy<2.0.0,>=1.22.0
|
|
13
13
|
Requires-Dist: tqdm>=4.65.0
|
14
14
|
Requires-Dist: tkinterdnd2>=0.3.0
|
15
15
|
Requires-Dist: Pillow>=9.0.0
|
16
|
+
Requires-Dist: imageio-ffmpeg>=0.4.8
|
16
17
|
Provides-Extra: dev
|
17
18
|
Requires-Dist: build>=1.0.0; extra == "dev"
|
18
19
|
Requires-Dist: twine>=4.0.0; extra == "dev"
|
@@ -44,6 +45,8 @@ Go to the [releases page](https://github.com/popstas/talks-reducer/releases) and
|
|
44
45
|
pip install talks-reducer
|
45
46
|
```
|
46
47
|
|
48
|
+
**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.
|
49
|
+
|
47
50
|
The `--small` preset applies a 720p video scale and 128 kbps audio bitrate, making it useful for sharing talks over constrained
|
48
51
|
connections. Without `--small`, the script aims to preserve original quality while removing silence.
|
49
52
|
|
@@ -122,6 +125,19 @@ file manager.
|
|
122
125
|
- **October 2025** — Added `--small` preset with 720p/128 kbps defaults for bandwidth-friendly exports.
|
123
126
|
- **October 2025** — Removed the `--cuda` flag; CUDA/NVENC support is now auto-detected.
|
124
127
|
|
128
|
+
## Changelog
|
129
|
+
Major and minor releases are tracked in `CHANGELOG.md`. The log is generated from
|
130
|
+
Conventional Commits that start with either `feat:` or `fix:`. Only tags in the
|
131
|
+
form `v<major>.<minor>.0` are included so patch releases (for example,
|
132
|
+
`v1.1.1`) are omitted. Regenerate the file whenever you cut a release:
|
133
|
+
|
134
|
+
```bash
|
135
|
+
python scripts/generate_changelog.py
|
136
|
+
```
|
137
|
+
|
138
|
+
CI will fail if the generated changelog does not match the committed version, so
|
139
|
+
run the script before opening a pull request that updates release tags.
|
140
|
+
|
125
141
|
## Contributing
|
126
142
|
See `CONTRIBUTION.md` for development setup details and guidance on sharing improvements.
|
127
143
|
|
@@ -20,6 +20,8 @@ Go to the [releases page](https://github.com/popstas/talks-reducer/releases) and
|
|
20
20
|
pip install talks-reducer
|
21
21
|
```
|
22
22
|
|
23
|
+
**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.
|
24
|
+
|
23
25
|
The `--small` preset applies a 720p video scale and 128 kbps audio bitrate, making it useful for sharing talks over constrained
|
24
26
|
connections. Without `--small`, the script aims to preserve original quality while removing silence.
|
25
27
|
|
@@ -98,6 +100,19 @@ file manager.
|
|
98
100
|
- **October 2025** — Added `--small` preset with 720p/128 kbps defaults for bandwidth-friendly exports.
|
99
101
|
- **October 2025** — Removed the `--cuda` flag; CUDA/NVENC support is now auto-detected.
|
100
102
|
|
103
|
+
## Changelog
|
104
|
+
Major and minor releases are tracked in `CHANGELOG.md`. The log is generated from
|
105
|
+
Conventional Commits that start with either `feat:` or `fix:`. Only tags in the
|
106
|
+
form `v<major>.<minor>.0` are included so patch releases (for example,
|
107
|
+
`v1.1.1`) are omitted. Regenerate the file whenever you cut a release:
|
108
|
+
|
109
|
+
```bash
|
110
|
+
python scripts/generate_changelog.py
|
111
|
+
```
|
112
|
+
|
113
|
+
CI will fail if the generated changelog does not match the committed version, so
|
114
|
+
run the script before opening a pull request that updates release tags.
|
115
|
+
|
101
116
|
## Contributing
|
102
117
|
See `CONTRIBUTION.md` for development setup details and guidance on sharing improvements.
|
103
118
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "talks-reducer"
|
7
|
-
version = "0.2.
|
7
|
+
version = "0.2.21"
|
8
8
|
description = "CLI for speeding up long-form talks by removing silence"
|
9
9
|
readme = "README.md"
|
10
10
|
requires-python = ">=3.9"
|
@@ -19,6 +19,7 @@ dependencies = [
|
|
19
19
|
"tqdm>=4.65.0",
|
20
20
|
"tkinterdnd2>=0.3.0",
|
21
21
|
"Pillow>=9.0.0",
|
22
|
+
"imageio-ffmpeg>=0.4.8",
|
22
23
|
]
|
23
24
|
|
24
25
|
[project.optional-dependencies]
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
4
4
|
|
5
5
|
import math
|
6
6
|
import subprocess
|
7
|
+
import sys
|
7
8
|
from typing import List, Sequence, Tuple
|
8
9
|
|
9
10
|
import numpy as np
|
@@ -35,7 +36,19 @@ def is_valid_input_file(filename: str) -> bool:
|
|
35
36
|
"-show_entries",
|
36
37
|
"stream=codec_type",
|
37
38
|
]
|
38
|
-
|
39
|
+
|
40
|
+
# Hide console window on Windows
|
41
|
+
creationflags = 0
|
42
|
+
if sys.platform == "win32":
|
43
|
+
# CREATE_NO_WINDOW = 0x08000000
|
44
|
+
creationflags = 0x08000000
|
45
|
+
|
46
|
+
process = subprocess.Popen(
|
47
|
+
command,
|
48
|
+
stdout=subprocess.PIPE,
|
49
|
+
stderr=subprocess.PIPE,
|
50
|
+
creationflags=creationflags
|
51
|
+
)
|
39
52
|
outs, errs = None, None
|
40
53
|
try:
|
41
54
|
outs, errs = process.communicate(timeout=1)
|
@@ -113,14 +113,22 @@ def main(argv: Optional[Sequence[str]] = None) -> None:
|
|
113
113
|
|
114
114
|
# Launch GUI if no arguments provided
|
115
115
|
if not argv_list:
|
116
|
+
gui_launched = False
|
117
|
+
|
116
118
|
try:
|
117
119
|
from .gui import main as gui_main
|
118
120
|
|
119
|
-
gui_main()
|
120
|
-
return
|
121
|
+
gui_launched = gui_main([])
|
121
122
|
except ImportError:
|
122
123
|
# GUI dependencies not available, show help instead
|
123
|
-
|
124
|
+
gui_launched = False
|
125
|
+
|
126
|
+
if gui_launched:
|
127
|
+
return
|
128
|
+
|
129
|
+
parser = _build_parser()
|
130
|
+
parser.print_help()
|
131
|
+
return
|
124
132
|
|
125
133
|
parser = _build_parser()
|
126
134
|
parsed_args = parser.parse_args(argv)
|
@@ -35,6 +35,18 @@ def find_ffmpeg() -> Optional[str]:
|
|
35
35
|
else env_override
|
36
36
|
)
|
37
37
|
|
38
|
+
# Try bundled ffmpeg from imageio-ffmpeg first
|
39
|
+
try:
|
40
|
+
import imageio_ffmpeg
|
41
|
+
bundled_path = imageio_ffmpeg.get_ffmpeg_exe()
|
42
|
+
if bundled_path and os.path.isfile(bundled_path):
|
43
|
+
return bundled_path
|
44
|
+
except ImportError:
|
45
|
+
pass
|
46
|
+
except Exception:
|
47
|
+
# If imageio_ffmpeg is installed but fails, continue to other methods
|
48
|
+
pass
|
49
|
+
|
38
50
|
common_paths = [
|
39
51
|
"C:\\ProgramData\\chocolatey\\bin\\ffmpeg.exe",
|
40
52
|
"C:\\Program Files\\ffmpeg\\bin\\ffmpeg.exe",
|
@@ -95,7 +107,8 @@ def _resolve_ffmpeg_path() -> str:
|
|
95
107
|
ffmpeg_path = find_ffmpeg()
|
96
108
|
if not ffmpeg_path:
|
97
109
|
raise FFmpegNotFoundError(
|
98
|
-
"FFmpeg not found.
|
110
|
+
"FFmpeg not found. Please install imageio-ffmpeg (pip install imageio-ffmpeg) "
|
111
|
+
"or install FFmpeg manually and add it to PATH, or set TALKS_REDUCER_FFMPEG environment variable."
|
99
112
|
)
|
100
113
|
|
101
114
|
print(f"Using FFmpeg at: {ffmpeg_path}")
|
@@ -139,10 +152,20 @@ def get_ffprobe_path() -> str:
|
|
139
152
|
def check_cuda_available(ffmpeg_path: Optional[str] = None) -> bool:
|
140
153
|
"""Return whether CUDA hardware encoders are available in the FFmpeg build."""
|
141
154
|
|
155
|
+
# Hide console window on Windows
|
156
|
+
creationflags = 0
|
157
|
+
if sys.platform == "win32":
|
158
|
+
# CREATE_NO_WINDOW = 0x08000000
|
159
|
+
creationflags = 0x08000000
|
160
|
+
|
142
161
|
try:
|
143
162
|
ffmpeg_path = ffmpeg_path or get_ffmpeg_path()
|
144
163
|
result = subprocess.run(
|
145
|
-
[ffmpeg_path, "-encoders"],
|
164
|
+
[ffmpeg_path, "-encoders"],
|
165
|
+
capture_output=True,
|
166
|
+
text=True,
|
167
|
+
timeout=5,
|
168
|
+
creationflags=creationflags
|
146
169
|
)
|
147
170
|
except (
|
148
171
|
subprocess.TimeoutExpired,
|
@@ -178,6 +201,12 @@ def run_timed_ffmpeg_command(
|
|
178
201
|
print(f"Error parsing command: {exc}", file=sys.stderr)
|
179
202
|
raise
|
180
203
|
|
204
|
+
# Hide console window on Windows
|
205
|
+
creationflags = 0
|
206
|
+
if sys.platform == "win32":
|
207
|
+
# CREATE_NO_WINDOW = 0x08000000
|
208
|
+
creationflags = 0x08000000
|
209
|
+
|
181
210
|
try:
|
182
211
|
process = subprocess.Popen(
|
183
212
|
args,
|
@@ -186,6 +215,7 @@ def run_timed_ffmpeg_command(
|
|
186
215
|
universal_newlines=True,
|
187
216
|
bufsize=1,
|
188
217
|
errors="replace",
|
218
|
+
creationflags=creationflags,
|
189
219
|
)
|
190
220
|
except Exception as exc: # pragma: no cover - defensive logging
|
191
221
|
print(f"Error starting FFmpeg: {exc}", file=sys.stderr)
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
|
+
import json
|
5
6
|
import os
|
6
7
|
import subprocess
|
7
8
|
import sys
|
@@ -39,29 +40,70 @@ except ImportError: # pragma: no cover - handled at runtime
|
|
39
40
|
def _check_tkinter_available() -> tuple[bool, str]:
|
40
41
|
"""Check if tkinter can create windows without importing it globally."""
|
41
42
|
# Test in a subprocess to avoid crashing the main process
|
42
|
-
test_code = """
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
43
|
+
test_code = """
|
44
|
+
import json
|
45
|
+
|
46
|
+
def run_check():
|
47
|
+
try:
|
48
|
+
import tkinter as tk # noqa: F401 - imported for side effect
|
49
|
+
except Exception as exc: # pragma: no cover - runs in subprocess
|
50
|
+
return {
|
51
|
+
"status": "import_error",
|
52
|
+
"error": f"{exc.__class__.__name__}: {exc}",
|
53
|
+
}
|
54
|
+
|
55
|
+
try:
|
56
|
+
import tkinter as tk
|
57
|
+
|
58
|
+
root = tk.Tk()
|
59
|
+
root.destroy()
|
60
|
+
except Exception as exc: # pragma: no cover - runs in subprocess
|
61
|
+
return {
|
62
|
+
"status": "init_error",
|
63
|
+
"error": f"{exc.__class__.__name__}: {exc}",
|
64
|
+
}
|
65
|
+
|
66
|
+
return {"status": "ok"}
|
67
|
+
|
68
|
+
|
69
|
+
if __name__ == "__main__":
|
70
|
+
print(json.dumps(run_check()))
|
71
|
+
"""
|
49
72
|
|
50
73
|
try:
|
51
74
|
result = subprocess.run(
|
52
75
|
[sys.executable, "-c", test_code], capture_output=True, text=True, timeout=5
|
53
76
|
)
|
54
77
|
|
55
|
-
|
78
|
+
output = result.stdout.strip() or result.stderr.strip()
|
79
|
+
|
80
|
+
if not output:
|
81
|
+
return False, "Window creation failed"
|
82
|
+
|
83
|
+
try:
|
84
|
+
payload = json.loads(output)
|
85
|
+
except json.JSONDecodeError:
|
86
|
+
return False, output
|
87
|
+
|
88
|
+
status = payload.get("status")
|
89
|
+
|
90
|
+
if status == "ok":
|
56
91
|
return True, ""
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
92
|
+
|
93
|
+
if status == "import_error":
|
94
|
+
return (
|
95
|
+
False,
|
96
|
+
f"tkinter is not installed ({payload.get('error', 'unknown error')})",
|
62
97
|
)
|
63
|
-
|
64
|
-
|
98
|
+
|
99
|
+
if status == "init_error":
|
100
|
+
return (
|
101
|
+
False,
|
102
|
+
f"tkinter could not open a window ({payload.get('error', 'unknown error')})",
|
103
|
+
)
|
104
|
+
|
105
|
+
return False, output
|
106
|
+
except Exception as e: # pragma: no cover - defensive fallback
|
65
107
|
return False, f"Error testing tkinter: {e}"
|
66
108
|
|
67
109
|
|
@@ -213,17 +255,30 @@ class TalksReducerGUI:
|
|
213
255
|
def _apply_window_icon(self) -> None:
|
214
256
|
"""Configure the application icon when the asset is available."""
|
215
257
|
|
216
|
-
|
217
|
-
Path(__file__).resolve().parent.parent
|
258
|
+
base_path = Path(
|
259
|
+
getattr(sys, "_MEIPASS", Path(__file__).resolve().parent.parent)
|
218
260
|
)
|
219
|
-
if not icon_path.is_file():
|
220
|
-
return
|
221
261
|
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
262
|
+
icon_candidates: list[tuple[Path, str]] = []
|
263
|
+
if sys.platform.startswith("win"):
|
264
|
+
icon_candidates.append((base_path / "docs" / "assets" / "icon.ico", "ico"))
|
265
|
+
icon_candidates.append((base_path / "docs" / "assets" / "icon.png", "png"))
|
266
|
+
|
267
|
+
for icon_path, icon_type in icon_candidates:
|
268
|
+
if not icon_path.is_file():
|
269
|
+
continue
|
270
|
+
|
271
|
+
try:
|
272
|
+
if icon_type == "ico" and sys.platform.startswith("win"):
|
273
|
+
# On Windows, iconbitmap works better without the 'default' parameter
|
274
|
+
self.root.iconbitmap(str(icon_path))
|
275
|
+
else:
|
276
|
+
self.root.iconphoto(False, self.tk.PhotoImage(file=str(icon_path)))
|
277
|
+
# If we got here without exception, icon was set successfully
|
278
|
+
return
|
279
|
+
except (self.tk.TclError, Exception) as e:
|
280
|
+
# Missing Tk image support or invalid icon format - try next candidate
|
281
|
+
continue
|
227
282
|
|
228
283
|
def _build_layout(self) -> None:
|
229
284
|
main = self.ttk.Frame(self.root, padding=16)
|
@@ -621,7 +676,7 @@ class TalksReducerGUI:
|
|
621
676
|
|
622
677
|
# -------------------------------------------------------------- actions --
|
623
678
|
def _add_files(self) -> None:
|
624
|
-
files = filedialog.askopenfilenames(
|
679
|
+
files = self.filedialog.askopenfilenames(
|
625
680
|
title="Select input files",
|
626
681
|
filetypes=[
|
627
682
|
("Video files", "*.mp4 *.mkv *.mov *.avi *.m4v"),
|
@@ -631,7 +686,7 @@ class TalksReducerGUI:
|
|
631
686
|
self._extend_inputs(files)
|
632
687
|
|
633
688
|
def _add_directory(self) -> None:
|
634
|
-
directory = filedialog.askdirectory(title="Select input folder")
|
689
|
+
directory = self.filedialog.askdirectory(title="Select input folder")
|
635
690
|
if directory:
|
636
691
|
self._extend_inputs([directory])
|
637
692
|
|
@@ -659,22 +714,26 @@ class TalksReducerGUI:
|
|
659
714
|
cleaned = [path.strip("{}") for path in paths]
|
660
715
|
self._extend_inputs(cleaned, auto_run=True)
|
661
716
|
|
662
|
-
def _browse_path(
|
717
|
+
def _browse_path(
|
718
|
+
self, variable, label: str
|
719
|
+
) -> None: # type: (tk.StringVar, str) -> None
|
663
720
|
if "folder" in label.lower():
|
664
|
-
result = filedialog.askdirectory()
|
721
|
+
result = self.filedialog.askdirectory()
|
665
722
|
else:
|
666
723
|
initial = variable.get() or os.getcwd()
|
667
|
-
result = filedialog.asksaveasfilename(
|
724
|
+
result = self.filedialog.asksaveasfilename(
|
725
|
+
initialfile=os.path.basename(initial)
|
726
|
+
)
|
668
727
|
if result:
|
669
728
|
variable.set(result)
|
670
729
|
|
671
730
|
def _start_run(self) -> None:
|
672
731
|
if self._processing_thread and self._processing_thread.is_alive():
|
673
|
-
messagebox.showinfo("Processing", "A job is already running.")
|
732
|
+
self.messagebox.showinfo("Processing", "A job is already running.")
|
674
733
|
return
|
675
734
|
|
676
735
|
if not self.input_files:
|
677
|
-
messagebox.showwarning(
|
736
|
+
self.messagebox.showwarning(
|
678
737
|
"Missing input", "Please add at least one file or folder."
|
679
738
|
)
|
680
739
|
return
|
@@ -682,11 +741,11 @@ class TalksReducerGUI:
|
|
682
741
|
try:
|
683
742
|
args = self._collect_arguments()
|
684
743
|
except ValueError as exc:
|
685
|
-
messagebox.showerror("Invalid value", str(exc))
|
744
|
+
self.messagebox.showerror("Invalid value", str(exc))
|
686
745
|
return
|
687
746
|
|
688
747
|
self._append_log("Starting processing…")
|
689
|
-
self.run_button.configure(state=tk.DISABLED)
|
748
|
+
self.run_button.configure(state=self.tk.DISABLED)
|
690
749
|
|
691
750
|
def worker() -> None:
|
692
751
|
reporter = _TkProgressReporter(self._append_log)
|
@@ -694,7 +753,7 @@ class TalksReducerGUI:
|
|
694
753
|
files = gather_input_files(self.input_files)
|
695
754
|
if not files:
|
696
755
|
self._notify(
|
697
|
-
lambda: messagebox.showwarning(
|
756
|
+
lambda: self.messagebox.showwarning(
|
698
757
|
"No files", "No supported media files were found."
|
699
758
|
)
|
700
759
|
)
|
@@ -716,11 +775,15 @@ class TalksReducerGUI:
|
|
716
775
|
self._append_log("All jobs finished successfully.")
|
717
776
|
self._notify(lambda: self.open_button.configure(state=self.tk.NORMAL))
|
718
777
|
except FFmpegNotFoundError as exc:
|
719
|
-
self._notify(
|
778
|
+
self._notify(
|
779
|
+
lambda: self.messagebox.showerror("FFmpeg not found", str(exc))
|
780
|
+
)
|
720
781
|
self._set_status("Error")
|
721
782
|
except Exception as exc: # pragma: no cover - GUI level safeguard
|
722
783
|
self._notify(
|
723
|
-
lambda: messagebox.showerror(
|
784
|
+
lambda: self.messagebox.showerror(
|
785
|
+
"Error", f"Processing failed: {exc}"
|
786
|
+
)
|
724
787
|
)
|
725
788
|
self._set_status("Error")
|
726
789
|
finally:
|
@@ -883,20 +946,24 @@ class TalksReducerGUI:
|
|
883
946
|
self.root.mainloop()
|
884
947
|
|
885
948
|
|
886
|
-
def main(argv: Optional[Sequence[str]] = None) ->
|
887
|
-
"""Launch the GUI when run without arguments, otherwise defer to the CLI.
|
949
|
+
def main(argv: Optional[Sequence[str]] = None) -> bool:
|
950
|
+
"""Launch the GUI when run without arguments, otherwise defer to the CLI.
|
951
|
+
|
952
|
+
Returns ``True`` if the GUI event loop started successfully. ``False``
|
953
|
+
indicates that execution was delegated to the CLI or aborted early.
|
954
|
+
"""
|
888
955
|
|
889
956
|
if argv is None:
|
890
957
|
argv = sys.argv[1:]
|
891
958
|
|
892
959
|
if argv:
|
893
960
|
cli_main(argv)
|
894
|
-
return
|
961
|
+
return False
|
895
962
|
|
896
963
|
# Skip tkinter check if running as a PyInstaller frozen app
|
897
964
|
# In that case, tkinter is bundled and the subprocess check would fail
|
898
|
-
is_frozen = getattr(sys,
|
899
|
-
|
965
|
+
is_frozen = getattr(sys, "frozen", False)
|
966
|
+
|
900
967
|
if not is_frozen:
|
901
968
|
# Check if tkinter is available before creating GUI (only when not frozen)
|
902
969
|
tkinter_available, error_msg = _check_tkinter_available()
|
@@ -926,14 +993,16 @@ def main(argv: Optional[Sequence[str]] = None) -> None:
|
|
926
993
|
except UnicodeEncodeError:
|
927
994
|
# Fallback for extreme encoding issues
|
928
995
|
sys.stderr.write("GUI not available. Use CLI mode instead.\n")
|
929
|
-
return
|
996
|
+
return False
|
930
997
|
|
931
998
|
# Catch and report any errors during GUI initialization
|
932
999
|
try:
|
933
1000
|
app = TalksReducerGUI()
|
934
1001
|
app.run()
|
1002
|
+
return True
|
935
1003
|
except Exception as e:
|
936
1004
|
import traceback
|
1005
|
+
|
937
1006
|
sys.stderr.write(f"Error starting GUI: {e}\n")
|
938
1007
|
sys.stderr.write(traceback.format_exc())
|
939
1008
|
sys.stderr.write("\nPlease use the CLI mode instead:\n")
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: talks-reducer
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.21
|
4
4
|
Summary: CLI for speeding up long-form talks by removing silence
|
5
5
|
Author: Talks Reducer Maintainers
|
6
6
|
License-Expression: MIT
|
@@ -13,6 +13,7 @@ Requires-Dist: numpy<2.0.0,>=1.22.0
|
|
13
13
|
Requires-Dist: tqdm>=4.65.0
|
14
14
|
Requires-Dist: tkinterdnd2>=0.3.0
|
15
15
|
Requires-Dist: Pillow>=9.0.0
|
16
|
+
Requires-Dist: imageio-ffmpeg>=0.4.8
|
16
17
|
Provides-Extra: dev
|
17
18
|
Requires-Dist: build>=1.0.0; extra == "dev"
|
18
19
|
Requires-Dist: twine>=4.0.0; extra == "dev"
|
@@ -44,6 +45,8 @@ Go to the [releases page](https://github.com/popstas/talks-reducer/releases) and
|
|
44
45
|
pip install talks-reducer
|
45
46
|
```
|
46
47
|
|
48
|
+
**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.
|
49
|
+
|
47
50
|
The `--small` preset applies a 720p video scale and 128 kbps audio bitrate, making it useful for sharing talks over constrained
|
48
51
|
connections. Without `--small`, the script aims to preserve original quality while removing silence.
|
49
52
|
|
@@ -122,6 +125,19 @@ file manager.
|
|
122
125
|
- **October 2025** — Added `--small` preset with 720p/128 kbps defaults for bandwidth-friendly exports.
|
123
126
|
- **October 2025** — Removed the `--cuda` flag; CUDA/NVENC support is now auto-detected.
|
124
127
|
|
128
|
+
## Changelog
|
129
|
+
Major and minor releases are tracked in `CHANGELOG.md`. The log is generated from
|
130
|
+
Conventional Commits that start with either `feat:` or `fix:`. Only tags in the
|
131
|
+
form `v<major>.<minor>.0` are included so patch releases (for example,
|
132
|
+
`v1.1.1`) are omitted. Regenerate the file whenever you cut a release:
|
133
|
+
|
134
|
+
```bash
|
135
|
+
python scripts/generate_changelog.py
|
136
|
+
```
|
137
|
+
|
138
|
+
CI will fail if the generated changelog does not match the committed version, so
|
139
|
+
run the script before opening a pull request that updates release tags.
|
140
|
+
|
125
141
|
## Contributing
|
126
142
|
See `CONTRIBUTION.md` for development setup details and guidance on sharing improvements.
|
127
143
|
|
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
|