talks-reducer 0.7.1__tar.gz → 0.7.2__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.7.1 → talks_reducer-0.7.2}/PKG-INFO +23 -1
- talks_reducer-0.7.1/talks_reducer.egg-info/PKG-INFO → talks_reducer-0.7.2/README.md +175 -181
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/talks_reducer/__about__.py +1 -1
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/talks_reducer/gui/__init__.py +1 -1
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/talks_reducer/gui/layout.py +17 -30
- talks_reducer-0.7.2/talks_reducer/icons.py +121 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/talks_reducer/server.py +5 -6
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/talks_reducer/server_tray.py +83 -204
- talks_reducer-0.7.1/README.md → talks_reducer-0.7.2/talks_reducer.egg-info/PKG-INFO +203 -153
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/talks_reducer.egg-info/SOURCES.txt +1 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/LICENSE +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/pyproject.toml +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/setup.cfg +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/talks_reducer/__init__.py +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/talks_reducer/__main__.py +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/talks_reducer/audio.py +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/talks_reducer/chunks.py +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/talks_reducer/cli.py +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/talks_reducer/discovery.py +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/talks_reducer/ffmpeg.py +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/talks_reducer/gui/__main__.py +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/talks_reducer/gui/discovery.py +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/talks_reducer/gui/preferences.py +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/talks_reducer/gui/remote.py +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/talks_reducer/gui/theme.py +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/talks_reducer/models.py +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/talks_reducer/pipeline.py +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/talks_reducer/progress.py +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/talks_reducer/resources/__init__.py +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/talks_reducer/service_client.py +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/talks_reducer/version_utils.py +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/talks_reducer.egg-info/dependency_links.txt +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/talks_reducer.egg-info/entry_points.txt +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/talks_reducer.egg-info/requires.txt +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/talks_reducer.egg-info/top_level.txt +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/tests/test_audio.py +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/tests/test_cli.py +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/tests/test_discovery.py +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/tests/test_gui_preferences.py +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/tests/test_gui_remote.py +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/tests/test_gui_summary_parsing.py +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/tests/test_gui_theme.py +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/tests/test_pipeline_service.py +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/tests/test_server.py +0 -0
- {talks_reducer-0.7.1 → talks_reducer-0.7.2}/tests/test_service_client.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: talks-reducer
|
3
|
-
Version: 0.7.
|
3
|
+
Version: 0.7.2
|
4
4
|
Summary: CLI for speeding up long-form talks by removing silence
|
5
5
|
Author: Talks Reducer Maintainers
|
6
6
|
License-Expression: MIT
|
@@ -174,6 +174,28 @@ The helper wraps the Gradio API exposed by `server.py`, waits for processing to
|
|
174
174
|
path you provide. Pass `--small` to mirror the **Small video** checkbox or `--print-log` to stream the server log after the
|
175
175
|
download finishes.
|
176
176
|
|
177
|
+
## Faster PyInstaller builds
|
178
|
+
|
179
|
+
PyInstaller spends most of its time walking imports. To keep GUI builds snappy:
|
180
|
+
|
181
|
+
- Create a dedicated virtual environment for packaging the GUI and install only
|
182
|
+
the runtime dependencies you need (for example `pip install -r
|
183
|
+
requirements.txt -r scripts/requirements-pyinstaller.txt`). Avoid installing
|
184
|
+
heavy ML stacks such as Torch or TensorFlow in that environment so PyInstaller
|
185
|
+
never attempts to analyze them.
|
186
|
+
- Use the committed `talks-reducer.spec` file via `./scripts/build-gui.sh`.
|
187
|
+
The spec excludes Torch, TensorFlow, TensorBoard, torchvision/torchaudio,
|
188
|
+
Pandas, Qt bindings, setuptools' vendored helpers, and other bulky modules
|
189
|
+
that previously slowed the analysis stage. Set
|
190
|
+
`PYINSTALLER_EXTRA_EXCLUDES=module1,module2` if you need to drop additional
|
191
|
+
imports for an experimental build.
|
192
|
+
- Keep optional imports in the codebase lazy (wrapped in `try/except` or moved
|
193
|
+
inside functions) so the analyzer only sees the dependencies required for the
|
194
|
+
shipping GUI.
|
195
|
+
|
196
|
+
The script keeps incremental build artifacts in `build/` between runs. Pass
|
197
|
+
`--clean` to `scripts/build-gui.sh` when you want a full rebuild.
|
198
|
+
|
177
199
|
## Contributing
|
178
200
|
See `CONTRIBUTION.md` for development setup details and guidance on sharing improvements.
|
179
201
|
|
@@ -1,181 +1,175 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
server
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
the
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
## Contributing
|
178
|
-
See `CONTRIBUTION.md` for development setup details and guidance on sharing improvements.
|
179
|
-
|
180
|
-
## License
|
181
|
-
Talks Reducer is released under the MIT License. See `LICENSE` for the full text.
|
1
|
+
# Talks Reducer
|
2
|
+
|
3
|
+
Talks Reducer shortens long-form presentations by removing silent gaps and optionally re-encoding them to smaller files. The
|
4
|
+
project was renamed from **jumpcutter** to emphasize its focus on conference talks and screencasts.
|
5
|
+
|
6
|
+

|
7
|
+
|
8
|
+
## Example
|
9
|
+
- 1h 37m, 571 MB — Original OBS video recording
|
10
|
+
- 1h 19m, 751 MB — Talks Reducer
|
11
|
+
- 1h 19m, 171 MB — Talks Reducer `--small`
|
12
|
+
|
13
|
+
## Changelog
|
14
|
+
|
15
|
+
See [CHANGELOG.md](CHANGELOG.md).
|
16
|
+
|
17
|
+
## Install GUI (Windows, macOS)
|
18
|
+
Go to the [releases page](https://github.com/popstas/talks-reducer/releases) and download the appropriate artifact:
|
19
|
+
|
20
|
+
- **Windows** — `talks-reducer-windows-0.4.0.zip`
|
21
|
+
- **macOS** — `talks-reducer.app.zip`
|
22
|
+
|
23
|
+
> **Troubleshooting:** If launching the bundle (or running `python -m talks_reducer.gui`) 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.
|
24
|
+
|
25
|
+
When extracted on Windows the bundled `talks-reducer.exe` behaves like running
|
26
|
+
`python -m talks_reducer.gui`: double-clicking it launches the GUI
|
27
|
+
and passing a video file path (for example via *Open with…* or drag-and-drop
|
28
|
+
onto the executable) automatically queues that recording for processing.
|
29
|
+
|
30
|
+
## Install CLI (Linux, Windows, macOS)
|
31
|
+
```
|
32
|
+
pip install talks-reducer
|
33
|
+
```
|
34
|
+
|
35
|
+
**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.
|
36
|
+
|
37
|
+
The `--small` preset applies a 720p video scale and 128 kbps audio bitrate, making it useful for sharing talks over constrained
|
38
|
+
connections. Without `--small`, the script aims to preserve original quality while removing silence.
|
39
|
+
|
40
|
+
Example CLI usage:
|
41
|
+
|
42
|
+
```sh
|
43
|
+
talks-reducer --small input.mp4
|
44
|
+
```
|
45
|
+
|
46
|
+
Need to offload work to a remote Talks Reducer server? Pass `--url` with the
|
47
|
+
server address and the CLI will upload the input, wait for processing to finish,
|
48
|
+
and download the rendered video. You can also provide `--host` to expand to the
|
49
|
+
default Talks Reducer port (`http://<host>:9005`):
|
50
|
+
|
51
|
+
```sh
|
52
|
+
talks-reducer --url http://localhost:9005 demo.mp4
|
53
|
+
talks-reducer --host 192.168.1.42 demo.mp4
|
54
|
+
```
|
55
|
+
|
56
|
+
Remote jobs respect the same timing controls as the local CLI. Provide
|
57
|
+
`--silent-threshold`, `--sounded-speed`, or `--silent-speed` to tweak how the
|
58
|
+
server trims and accelerates segments without falling back to local mode.
|
59
|
+
|
60
|
+
Want to see progress as the remote server works? Add `--server-stream` so the
|
61
|
+
CLI prints live progress bars and log lines while you wait for the download.
|
62
|
+
|
63
|
+
### Speech detection
|
64
|
+
|
65
|
+
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.
|
66
|
+
|
67
|
+
When CUDA-capable hardware is available the pipeline leans on GPU encoders to keep export times low, but it still runs great on
|
68
|
+
CPUs.
|
69
|
+
|
70
|
+
## Simple web server
|
71
|
+
|
72
|
+
Prefer a lightweight browser interface? Launch the Gradio-powered simple mode with:
|
73
|
+
|
74
|
+
```sh
|
75
|
+
talks-reducer server
|
76
|
+
```
|
77
|
+
|
78
|
+
The browser UI mirrors the CLI timing controls with sliders for the silent
|
79
|
+
threshold and playback speeds, so you can tune exports without leaving the
|
80
|
+
remote workflow.
|
81
|
+
|
82
|
+
Want the server to live in your system tray instead of a terminal window? Use:
|
83
|
+
|
84
|
+
```sh
|
85
|
+
talks-reducer server-tray
|
86
|
+
```
|
87
|
+
|
88
|
+
Bundled Windows builds include the same behaviour: run
|
89
|
+
`talks-reducer.exe --server` to launch the tray-managed server directly from the
|
90
|
+
desktop shortcut without opening the GUI first.
|
91
|
+
|
92
|
+
Pass `--debug` to print verbose logs about the tray icon lifecycle, and
|
93
|
+
`--tray-mode pystray-detached` to try pystray's alternate detached runner. If
|
94
|
+
the icon backend refuses to appear, fall back to `--tray-mode headless` to keep
|
95
|
+
the web server running without a tray process. The tray menu highlights the
|
96
|
+
running Talks Reducer version and includes an **Open GUI**
|
97
|
+
item (also triggered by double-clicking the icon) that launches the desktop
|
98
|
+
Talks Reducer interface alongside an **Open WebUI** entry that opens the Gradio
|
99
|
+
page in your browser. Close the GUI window to return to the tray without
|
100
|
+
stopping the server. Launch the tray explicitly whenever you need it—either run
|
101
|
+
`talks-reducer server-tray` directly or start the GUI with
|
102
|
+
`python -m talks_reducer.gui --server` to boot the tray-managed server instead
|
103
|
+
of the desktop window. The GUI now runs standalone and no longer spawns the tray
|
104
|
+
automatically; the deprecated `--no-tray` flag is ignored for compatibility.
|
105
|
+
The tray command itself never launches the GUI automatically, so use the menu
|
106
|
+
item (or relaunch the GUI separately) whenever you want to reopen it. The tray
|
107
|
+
no longer opens a browser automatically—pass `--open-browser` if you prefer the
|
108
|
+
web page to launch as soon as the server is ready.
|
109
|
+
|
110
|
+
This opens a local web page featuring a drag-and-drop upload zone, a **Small video** checkbox that mirrors the CLI preset, a live
|
111
|
+
progress indicator, and automatic previews of the processed output. The page header and browser tab title include the current
|
112
|
+
Talks Reducer version so you can confirm which build the server is running. Once the job completes you can inspect the resulting
|
113
|
+
compression ratio and download the rendered video directly from the page.
|
114
|
+
|
115
|
+
The desktop GUI mirrors this behaviour. Open **Advanced** settings to provide a
|
116
|
+
server URL and click **Discover** to scan your local network for Talks Reducer
|
117
|
+
instances listening on port `9005`. The button now updates with the discovery
|
118
|
+
progress, showing the scanned/total host count as `scanned / total`. A new
|
119
|
+
**Processing mode** toggle lets you decide whether work stays local or uploads
|
120
|
+
to the configured server—the **Remote** option becomes available as soon as a
|
121
|
+
URL is supplied. Leave the toggle on **Local** to keep rendering on this
|
122
|
+
machine even if a server is saved; switch to **Remote** to hand jobs off while
|
123
|
+
the GUI downloads the finished files automatically.
|
124
|
+
|
125
|
+
### Uploading and retrieving a processed video
|
126
|
+
|
127
|
+
1. Open the printed `http://localhost:<port>` address (the default port is `9005`).
|
128
|
+
2. Drag a video onto the **Video file** drop zone or click to browse and select one from disk.
|
129
|
+
3. **Small video** starts enabled to apply the 720p/128 kbps preset. Clear the box before the upload finishes if you want to keep the original resolution and bitrate.
|
130
|
+
4. Wait for the progress bar and log to report completion—the interface queues work automatically after the file arrives.
|
131
|
+
5. Watch the processed preview in the **Processed video** player and click **Download processed file** to save the result locally.
|
132
|
+
|
133
|
+
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
|
134
|
+
different address.
|
135
|
+
|
136
|
+
### Automating uploads from the command line
|
137
|
+
|
138
|
+
Prefer to script uploads instead of using the browser UI? Start the server and use the bundled helper to submit a job and save
|
139
|
+
the processed video locally:
|
140
|
+
|
141
|
+
```sh
|
142
|
+
python -m talks_reducer.service_client --server http://127.0.0.1:9005/ --input demo.mp4 --output output/demo_processed.mp4
|
143
|
+
```
|
144
|
+
|
145
|
+
The helper wraps the Gradio API exposed by `server.py`, waits for processing to complete, then copies the rendered file to the
|
146
|
+
path you provide. Pass `--small` to mirror the **Small video** checkbox or `--print-log` to stream the server log after the
|
147
|
+
download finishes.
|
148
|
+
|
149
|
+
## Faster PyInstaller builds
|
150
|
+
|
151
|
+
PyInstaller spends most of its time walking imports. To keep GUI builds snappy:
|
152
|
+
|
153
|
+
- Create a dedicated virtual environment for packaging the GUI and install only
|
154
|
+
the runtime dependencies you need (for example `pip install -r
|
155
|
+
requirements.txt -r scripts/requirements-pyinstaller.txt`). Avoid installing
|
156
|
+
heavy ML stacks such as Torch or TensorFlow in that environment so PyInstaller
|
157
|
+
never attempts to analyze them.
|
158
|
+
- Use the committed `talks-reducer.spec` file via `./scripts/build-gui.sh`.
|
159
|
+
The spec excludes Torch, TensorFlow, TensorBoard, torchvision/torchaudio,
|
160
|
+
Pandas, Qt bindings, setuptools' vendored helpers, and other bulky modules
|
161
|
+
that previously slowed the analysis stage. Set
|
162
|
+
`PYINSTALLER_EXTRA_EXCLUDES=module1,module2` if you need to drop additional
|
163
|
+
imports for an experimental build.
|
164
|
+
- Keep optional imports in the codebase lazy (wrapped in `try/except` or moved
|
165
|
+
inside functions) so the analyzer only sees the dependencies required for the
|
166
|
+
shipping GUI.
|
167
|
+
|
168
|
+
The script keeps incremental build artifacts in `build/` between runs. Pass
|
169
|
+
`--clean` to `scripts/build-gui.sh` when you want a full rebuild.
|
170
|
+
|
171
|
+
## Contributing
|
172
|
+
See `CONTRIBUTION.md` for development setup details and guidance on sharing improvements.
|
173
|
+
|
174
|
+
## License
|
175
|
+
Talks Reducer is released under the MIT License. See `LICENSE` for the full text.
|
@@ -325,7 +325,7 @@ class TalksReducerGUI:
|
|
325
325
|
|
326
326
|
self._full_size = (1000, 800)
|
327
327
|
self._simple_size = (300, 270)
|
328
|
-
self.root.geometry(f"{self._full_size[0]}x{self._full_size[1]}")
|
328
|
+
# self.root.geometry(f"{self._full_size[0]}x{self._full_size[1]}")
|
329
329
|
self.style = self.ttk.Style(self.root)
|
330
330
|
|
331
331
|
self._processing_thread: Optional[threading.Thread] = None
|
@@ -3,9 +3,9 @@
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
5
|
import sys
|
6
|
-
from pathlib import Path
|
7
6
|
from typing import TYPE_CHECKING, Callable
|
8
7
|
|
8
|
+
from ..icons import find_icon_path
|
9
9
|
from ..models import default_temp_folder
|
10
10
|
|
11
11
|
if TYPE_CHECKING: # pragma: no cover - imported for type checking only
|
@@ -454,37 +454,24 @@ def reset_basic_defaults(gui: "TalksReducerGUI") -> None:
|
|
454
454
|
def apply_window_icon(gui: "TalksReducerGUI") -> None:
|
455
455
|
"""Configure the application icon when the asset is available."""
|
456
456
|
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
icon_candidates.append(
|
462
|
-
(
|
463
|
-
base_path / "talks_reducer" / "resources" / "icons" / "icon.ico",
|
464
|
-
"ico",
|
465
|
-
)
|
466
|
-
)
|
467
|
-
icon_candidates.append(
|
468
|
-
(
|
469
|
-
base_path / "talks_reducer" / "resources" / "icons" / "icon.png",
|
470
|
-
"png",
|
471
|
-
)
|
457
|
+
icon_filenames = (
|
458
|
+
("app.ico", "app.png")
|
459
|
+
if sys.platform.startswith("win")
|
460
|
+
else ("app.png", "app.ico")
|
472
461
|
)
|
462
|
+
icon_path = find_icon_path(filenames=icon_filenames)
|
463
|
+
if icon_path is None:
|
464
|
+
return
|
473
465
|
|
474
|
-
|
475
|
-
if
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
gui.root.iconphoto(False, gui.tk.PhotoImage(file=str(icon_path)))
|
484
|
-
return
|
485
|
-
except (gui.tk.TclError, Exception):
|
486
|
-
# Missing Tk image support or invalid icon format - try next candidate
|
487
|
-
continue
|
466
|
+
try:
|
467
|
+
if icon_path.suffix.lower() == ".ico" and sys.platform.startswith("win"):
|
468
|
+
# On Windows, iconbitmap works better without the 'default' parameter.
|
469
|
+
gui.root.iconbitmap(str(icon_path))
|
470
|
+
else:
|
471
|
+
gui.root.iconphoto(False, gui.tk.PhotoImage(file=str(icon_path)))
|
472
|
+
except (gui.tk.TclError, Exception):
|
473
|
+
# Missing Tk image support or invalid icon format - fail silently.
|
474
|
+
return
|
488
475
|
|
489
476
|
|
490
477
|
def apply_window_size(gui: "TalksReducerGUI", *, simple: bool) -> None:
|
@@ -0,0 +1,121 @@
|
|
1
|
+
"""Icon discovery helpers shared across Talks Reducer entry points."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
import logging
|
6
|
+
import sys
|
7
|
+
from contextlib import suppress
|
8
|
+
from pathlib import Path
|
9
|
+
from typing import Iterator, Optional, Sequence
|
10
|
+
|
11
|
+
LOGGER = logging.getLogger(__name__)
|
12
|
+
|
13
|
+
_ICON_RELATIVE_PATHS: Sequence[Path] = (
|
14
|
+
Path("talks_reducer") / "resources" / "icons",
|
15
|
+
Path("docs") / "assets",
|
16
|
+
)
|
17
|
+
_ICON_PATH_SUFFIXES: Sequence[Path] = (
|
18
|
+
Path(""),
|
19
|
+
Path("_internal"),
|
20
|
+
Path("Contents") / "Resources",
|
21
|
+
Path("Resources"),
|
22
|
+
)
|
23
|
+
|
24
|
+
|
25
|
+
def _iter_base_roots(module_file: Optional[Path | str] = None) -> Iterator[Path]:
|
26
|
+
"""Yield base directories where icon assets may live."""
|
27
|
+
|
28
|
+
module_path = Path(module_file or __file__).resolve()
|
29
|
+
package_root = module_path.parent
|
30
|
+
project_root = package_root.parent
|
31
|
+
|
32
|
+
seen: set[Path] = set()
|
33
|
+
|
34
|
+
def _yield(path: Optional[Path]) -> Iterator[Path]:
|
35
|
+
if path is None:
|
36
|
+
return iter(())
|
37
|
+
resolved = path.resolve()
|
38
|
+
if resolved in seen:
|
39
|
+
return iter(())
|
40
|
+
seen.add(resolved)
|
41
|
+
return iter((resolved,))
|
42
|
+
|
43
|
+
for root in (
|
44
|
+
package_root,
|
45
|
+
project_root,
|
46
|
+
):
|
47
|
+
yield from _yield(root)
|
48
|
+
|
49
|
+
frozen_root: Optional[Path] = None
|
50
|
+
frozen_value = getattr(sys, "_MEIPASS", None)
|
51
|
+
if frozen_value:
|
52
|
+
with suppress(Exception):
|
53
|
+
frozen_root = Path(str(frozen_value))
|
54
|
+
if frozen_root is not None:
|
55
|
+
yield from _yield(frozen_root)
|
56
|
+
|
57
|
+
with suppress(Exception):
|
58
|
+
executable_root = Path(sys.executable).resolve().parent
|
59
|
+
yield from _yield(executable_root)
|
60
|
+
|
61
|
+
with suppress(Exception):
|
62
|
+
launcher_root = Path(sys.argv[0]).resolve().parent
|
63
|
+
yield from _yield(launcher_root)
|
64
|
+
|
65
|
+
|
66
|
+
def iter_icon_candidates(
|
67
|
+
*,
|
68
|
+
filenames: Sequence[str],
|
69
|
+
relative_paths: Sequence[Path] | None = None,
|
70
|
+
module_file: Optional[Path | str] = None,
|
71
|
+
) -> Iterator[Path]:
|
72
|
+
"""Yield possible icon paths ordered from most to least specific."""
|
73
|
+
|
74
|
+
if relative_paths is None:
|
75
|
+
relative_paths = _ICON_RELATIVE_PATHS
|
76
|
+
|
77
|
+
seen: set[Path] = set()
|
78
|
+
for base_root in _iter_base_roots(module_file=module_file):
|
79
|
+
for suffix in _ICON_PATH_SUFFIXES:
|
80
|
+
candidate_root = (base_root / suffix).resolve()
|
81
|
+
if candidate_root in seen:
|
82
|
+
continue
|
83
|
+
seen.add(candidate_root)
|
84
|
+
LOGGER.debug("Considering icon root: %s", candidate_root)
|
85
|
+
|
86
|
+
if not candidate_root.exists():
|
87
|
+
LOGGER.debug("Skipping missing icon root: %s", candidate_root)
|
88
|
+
continue
|
89
|
+
|
90
|
+
for relative in relative_paths:
|
91
|
+
candidate_base = (candidate_root / relative).resolve()
|
92
|
+
if not candidate_base.exists():
|
93
|
+
LOGGER.debug("Skipping missing icon directory: %s", candidate_base)
|
94
|
+
continue
|
95
|
+
for name in filenames:
|
96
|
+
candidate = (candidate_base / name).resolve()
|
97
|
+
LOGGER.debug("Checking icon candidate: %s", candidate)
|
98
|
+
yield candidate
|
99
|
+
|
100
|
+
|
101
|
+
def find_icon_path(
|
102
|
+
*,
|
103
|
+
filenames: Sequence[str],
|
104
|
+
relative_paths: Sequence[Path] | None = None,
|
105
|
+
module_file: Optional[Path | str] = None,
|
106
|
+
) -> Optional[Path]:
|
107
|
+
"""Return the first existing icon path matching *filenames* or ``None``."""
|
108
|
+
|
109
|
+
for candidate in iter_icon_candidates(
|
110
|
+
filenames=filenames,
|
111
|
+
relative_paths=relative_paths,
|
112
|
+
module_file=module_file,
|
113
|
+
):
|
114
|
+
if candidate.is_file():
|
115
|
+
LOGGER.info("Found icon at %s", candidate)
|
116
|
+
return candidate
|
117
|
+
LOGGER.warning("Unable to locate Talks Reducer icon; checked %s", filenames)
|
118
|
+
return None
|
119
|
+
|
120
|
+
|
121
|
+
__all__ = ["find_icon_path", "iter_icon_candidates"]
|