talks-reducer 0.7.0__py3-none-any.whl → 0.7.2__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 +1 -1
- talks_reducer/gui/__init__.py +1 -1
- talks_reducer/gui/layout.py +17 -30
- talks_reducer/icons.py +121 -0
- talks_reducer/server.py +5 -6
- talks_reducer/server_tray.py +83 -204
- {talks_reducer-0.7.0.dist-info → talks_reducer-0.7.2.dist-info}/METADATA +23 -1
- {talks_reducer-0.7.0.dist-info → talks_reducer-0.7.2.dist-info}/RECORD +12 -11
- {talks_reducer-0.7.0.dist-info → talks_reducer-0.7.2.dist-info}/WHEEL +0 -0
- {talks_reducer-0.7.0.dist-info → talks_reducer-0.7.2.dist-info}/entry_points.txt +0 -0
- {talks_reducer-0.7.0.dist-info → talks_reducer-0.7.2.dist-info}/licenses/LICENSE +0 -0
- {talks_reducer-0.7.0.dist-info → talks_reducer-0.7.2.dist-info}/top_level.txt +0 -0
talks_reducer/__about__.py
CHANGED
talks_reducer/gui/__init__.py
CHANGED
@@ -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
|
talks_reducer/gui/layout.py
CHANGED
@@ -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:
|
talks_reducer/icons.py
ADDED
@@ -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"]
|
talks_reducer/server.py
CHANGED
@@ -6,6 +6,7 @@ import argparse
|
|
6
6
|
import atexit
|
7
7
|
import shutil
|
8
8
|
import socket
|
9
|
+
import sys
|
9
10
|
import tempfile
|
10
11
|
from contextlib import AbstractContextManager, suppress
|
11
12
|
from pathlib import Path
|
@@ -16,6 +17,7 @@ from typing import Callable, Iterator, Optional, Sequence, cast
|
|
16
17
|
import gradio as gr
|
17
18
|
|
18
19
|
from talks_reducer.ffmpeg import FFmpegNotFoundError
|
20
|
+
from talks_reducer.icons import find_icon_path
|
19
21
|
from talks_reducer.models import ProcessingOptions, ProcessingResult
|
20
22
|
from talks_reducer.pipeline import speed_up_video
|
21
23
|
from talks_reducer.progress import ProgressHandle, SignalProgressReporter
|
@@ -144,13 +146,10 @@ class GradioProgressReporter(SignalProgressReporter):
|
|
144
146
|
self._progress_callback(bounded_current, total_value, display_desc)
|
145
147
|
|
146
148
|
|
147
|
-
|
148
|
-
|
149
|
-
Path(__file__).resolve().parent.parent / "docs" / "assets" / "icon.ico",
|
150
|
-
)
|
151
|
-
_FAVICON_PATH: Optional[Path] = next(
|
152
|
-
(path for path in _FAVICON_CANDIDATES if path.exists()), None
|
149
|
+
_FAVICON_FILENAMES = (
|
150
|
+
("app.ico", "app.png") if sys.platform.startswith("win") else ("app.png", "app.ico")
|
153
151
|
)
|
152
|
+
_FAVICON_PATH = find_icon_path(filenames=_FAVICON_FILENAMES)
|
154
153
|
_FAVICON_PATH_STR = str(_FAVICON_PATH) if _FAVICON_PATH else None
|
155
154
|
_WORKSPACES: list[Path] = []
|
156
155
|
|
talks_reducer/server_tray.py
CHANGED
@@ -4,7 +4,6 @@ from __future__ import annotations
|
|
4
4
|
|
5
5
|
import argparse
|
6
6
|
import atexit
|
7
|
-
import base64
|
8
7
|
import logging
|
9
8
|
import subprocess
|
10
9
|
import sys
|
@@ -12,14 +11,13 @@ import threading
|
|
12
11
|
import time
|
13
12
|
import webbrowser
|
14
13
|
from contextlib import suppress
|
15
|
-
from importlib import resources
|
16
|
-
from io import BytesIO
|
17
14
|
from pathlib import Path
|
18
15
|
from typing import Any, Iterator, Optional, Sequence
|
19
16
|
from urllib.parse import urlsplit, urlunsplit
|
20
17
|
|
21
18
|
from PIL import Image
|
22
19
|
|
20
|
+
from .icons import iter_icon_candidates
|
23
21
|
from .server import build_interface
|
24
22
|
from .version_utils import resolve_version
|
25
23
|
|
@@ -78,208 +76,59 @@ def _normalize_local_url(url: str, host: Optional[str], port: int) -> str:
|
|
78
76
|
return url
|
79
77
|
|
80
78
|
|
79
|
+
if sys.platform.startswith("win"):
|
80
|
+
_TRAY_ICON_FILENAMES = ("icon.ico", "icon.png", "app.ico", "app.png", "app-256.png")
|
81
|
+
else:
|
82
|
+
_TRAY_ICON_FILENAMES = ("icon.png", "icon.ico", "app.png", "app.ico", "app-256.png")
|
83
|
+
_ICON_RELATIVE_PATHS = (
|
84
|
+
Path("talks_reducer") / "resources" / "icons",
|
85
|
+
Path("docs") / "assets",
|
86
|
+
)
|
87
|
+
|
88
|
+
|
81
89
|
def _iter_icon_candidates() -> Iterator[Path]:
|
82
90
|
"""Yield possible tray icon paths ordered from most to least specific."""
|
83
91
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
frozen_root: Optional[Path] = None
|
89
|
-
frozen_value = getattr(sys, "_MEIPASS", None)
|
90
|
-
if frozen_value:
|
91
|
-
with suppress(Exception):
|
92
|
-
frozen_root = Path(str(frozen_value)).resolve()
|
93
|
-
|
94
|
-
executable_root: Optional[Path] = None
|
95
|
-
with suppress(Exception):
|
96
|
-
executable_root = Path(sys.executable).resolve().parent
|
97
|
-
|
98
|
-
launcher_root: Optional[Path] = None
|
99
|
-
with suppress(Exception):
|
100
|
-
launcher_root = Path(sys.argv[0]).resolve().parent
|
101
|
-
|
102
|
-
base_roots: list[Path] = []
|
103
|
-
for candidate in (
|
104
|
-
package_root,
|
105
|
-
project_root,
|
106
|
-
frozen_root,
|
107
|
-
executable_root,
|
108
|
-
launcher_root,
|
109
|
-
):
|
110
|
-
if candidate and candidate not in base_roots:
|
111
|
-
base_roots.append(candidate)
|
112
|
-
|
113
|
-
expanded_roots: list[Path] = []
|
114
|
-
suffixes = (
|
115
|
-
Path(""),
|
116
|
-
Path("_internal"),
|
117
|
-
Path("Contents") / "Resources",
|
118
|
-
Path("Resources"),
|
92
|
+
yield from iter_icon_candidates(
|
93
|
+
filenames=_TRAY_ICON_FILENAMES,
|
94
|
+
relative_paths=_ICON_RELATIVE_PATHS,
|
95
|
+
module_file=Path(__file__),
|
119
96
|
)
|
120
|
-
for root in base_roots:
|
121
|
-
for suffix in suffixes:
|
122
|
-
candidate_root = (root / suffix).resolve()
|
123
|
-
if candidate_root not in expanded_roots:
|
124
|
-
expanded_roots.append(candidate_root)
|
125
|
-
|
126
|
-
icon_names = ("icon.ico", "icon.png") if sys.platform == "win32" else ("icon.png", "icon.ico")
|
127
|
-
relative_paths = (
|
128
|
-
Path("talks_reducer") / "resources" / "icons",
|
129
|
-
Path("talks_reducer") / "assets",
|
130
|
-
Path("docs") / "assets",
|
131
|
-
Path("assets"),
|
132
|
-
Path(""),
|
133
|
-
)
|
134
|
-
|
135
|
-
seen: set[Path] = set()
|
136
|
-
for root in expanded_roots:
|
137
|
-
if not root.exists():
|
138
|
-
continue
|
139
|
-
for relative in relative_paths:
|
140
|
-
for icon_name in icon_names:
|
141
|
-
candidate = (root / relative / icon_name).resolve()
|
142
|
-
if candidate in seen:
|
143
|
-
continue
|
144
|
-
seen.add(candidate)
|
145
|
-
yield candidate
|
146
97
|
|
147
98
|
|
148
|
-
def
|
149
|
-
"""
|
150
|
-
|
151
|
-
LOGGER.debug("Attempting to load tray icon image.")
|
99
|
+
def _generate_fallback_icon() -> Image.Image:
|
100
|
+
"""Return a simple multi-color square used when packaged icons are missing."""
|
152
101
|
|
153
|
-
for candidate in _iter_icon_candidates():
|
154
|
-
LOGGER.debug("Checking icon candidate at %s", candidate)
|
155
|
-
if candidate.exists():
|
156
|
-
try:
|
157
|
-
with Image.open(candidate) as image:
|
158
|
-
loaded = image.copy()
|
159
|
-
except Exception as exc: # pragma: no cover - diagnostic log
|
160
|
-
LOGGER.warning("Failed to load tray icon from %s: %s", candidate, exc)
|
161
|
-
else:
|
162
|
-
LOGGER.debug("Loaded tray icon from %s", candidate)
|
163
|
-
return loaded
|
164
|
-
|
165
|
-
LOGGER.warning("Falling back to generated tray icon; packaged image not found")
|
166
102
|
image = Image.new("RGBA", (64, 64), color=(37, 99, 235, 255))
|
103
|
+
for index in range(64):
|
104
|
+
image.putpixel((index, index), (17, 24, 39, 255))
|
105
|
+
image.putpixel((63 - index, index), (59, 130, 246, 255))
|
167
106
|
image.putpixel((0, 0), (255, 255, 255, 255))
|
168
|
-
image.putpixel((63, 63), (
|
107
|
+
image.putpixel((63, 63), (59, 130, 246, 255))
|
169
108
|
return image
|
170
109
|
|
171
110
|
|
172
|
-
_EMBEDDED_ICON_BASE64 = (
|
173
|
-
"iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAARnQU1BAACx"
|
174
|
-
"jwv8YQUAAAAJcEhZcwAADsIAAA7CARUoSoAAAA3MSURBVHhe5Zt7cB31dcc/Z/deyVfPK11JDsLW"
|
175
|
-
"xAEmMJDGk+mEFGza8oiZ8l+GSWiwMTBJQzDpA2xT4kcKpsWxoSl0SiCTEAPFDYWkEJtCeYSnG0oh"
|
176
|
-
"sQHbacCWbcmyZOlKV5L1vLt7+sdvf3d3rx6hnfwT6Tuzurvnd36Pc37nd37nd3YlzID65o+1OOn0"
|
177
|
-
"FSCXIXKeQJuq1pTz/RYggJYTZ8Fs/AIgIqOgHaq8B/p8UCzuHuzt7ilnxlaIo64+W+VU1a4Tx7lR"
|
178
|
-
"RFoiFgUx9xL2r/Hq4ZAibsMfqw2qhiMhgiJhu4qG3YQ9qGGSkF9j/YdFEcIyiOrFnntVg+/6Y6e2"
|
179
|
-
"DQ0MjMSLEgrILmz9pKRSO0Wcz5jCSNQ5AQ32Br6/stB9fL8llRRQv7D1HCeVfl7EWWQFNgqYI8Jb"
|
180
|
-
"qPYGXnFFoafrl1gF1DY21aUz1W8g8qnZFticgeoH/uT4Hwz29uQdgNSCzCZEPlXON2chcpaTrrwD"
|
181
|
-
"QLItpy1x0hX7xHFqAbTcgcwxxJz0WFAsLnXETX1BRGoteb5ARDLipq5yELkkuVfNbUQiCiJymSMi"
|
182
|
-
"54KAzjl//1Hwe46ii6AskJgnUKhzUJzygrkPG1GCMz9WfjlK8a3Ow9lPIlLAPLQDQCIFzD8fCAkL"
|
183
|
-
"mFcozXbSByjQ32mugYm5vDOW1rs4Vh0aKANdynVXCbetcThrkZLvUIK57RtUGlrbAnFd6e8IeOD2"
|
184
|
-
"FKsvqqTSFY4MeDz0UpE77/PBgVyrSY4oIPrRfEagSuH40RglRdPixQT/hwOXAPlhYLC85P+BZshV"
|
185
|
-
"hjKIEAQB0tDaFkyqKyNVAe3fyfDxOgd8wAVP4M3DRbb9uMiu3YrbDNlKCFSSy2MahfiBMth1lNu3"
|
186
|
-
"bOFz55/PyMgIj//rEzz+LzvJLV7ykU6drkBvN3zpIuGK3xcy6WBKtisOEUEVRMKUmU3JCRRGhEde"
|
187
|
-
"DXj9V9DUCEqkAB3zXSqaA97fmmFxjYv6CmHqjjQUxpWn3y5y7Q+K8CFkW8FxZjeB/s4j7Hj4YVat"
|
188
|
-
"WoUTaqswOMg99/w9d2654zcqwRHoy8NNK4QtqzNks6lo6QrJvJ/YSSgbU2liDEPHiUn+8sFxfrIP"
|
189
|
-
"cjVGAaETVPzA+AHbiW1TJyGbElYvq+TDuzN88xsOhS6l/4SajKFdFzEURsc545zzuHzFipLwANn6"
|
190
|
-
"ev761vVs3LSZfEd7oqwcgQKjyp/+cZpsQxr1FQ3HGN2Hl29+sWW+hrTk8+LWCq6/LAUDkf4cAClN"
|
191
|
-
"t4FVcImuQBHOyDr8zZUZXv1BJVd8Xsh3wtCETlF8UCzS1JiloqIiWQBUV1dz6/p13HzLWvo62n+D"
|
192
|
-
"JQmZSjuAj4AEW7KO7cW0FxFKZwHDbqi2qk1Tq4b3gZD2hIvOTPPoX2XYcVcKLwv5TjMjFvW11fzX"
|
193
|
-
"njc4fLg9IsZQU1PDxg3fZNU1q+k7Nr0SLOX1d4toMUBcQVwQR2KX2cfshVtGiz2TgsnxgBf3Gqdu"
|
194
|
-
"23cztfXf8lREquCmS9PUVzpGAyLGNsRah0SqCiDjCkuXpLh6mUtVLuDFnyljo1BVY+qMDRUYHhll"
|
195
|
-
"+bJlVFdXlwSzyGQynH/+Z/ng0GH2vvUm1dmGKQ6ushaeelNpqfapSgUMDHnkC2XXgE9+0Cdf8MkP"
|
196
|
-
"euQLPv3hc1/BPnt0nvR45MVJbn8kINdq5FNVJNvaFoz7jrg55cDWDG21rvEFYpIk0RqIz1LMXlJQ"
|
197
|
-
"BPZ8WGT7k0X+/Vkl1QwNGYfeY+2svGY1d2/fxsKWllj9CMeOHeOGG9fw7DO7aVq8ZMoWGSgUjjPF"
|
198
|
-
"pGdFfNUkVpCQM9kPsNvgjAqYUttsKeYpPhhBRCEF/ePKLrtbHILcIod8ZzurVl/L9m3fnlEJ7e3t"
|
199
|
-
"rLr2eva89kpJCdavWH14Cv4MOnDEKCo+TWp28lI7TshnYeOAcAk44lYpa2JLoOQAw0pJR2nM3FyG"
|
200
|
-
"oj5UucLSj7usXJ6iuinghZd8qGvk3bdeoftkL8suvGDa5dDQ0MCyCy/gtT0/p/1/DlBd32DaDIUa"
|
201
|
-
"6ISJYaU4zLTX5DQ0S5+YEGrDlG8cJmZQlWzr4mDcdyXdpLx/l7EAmx+0b4aMjFbS6W5DZ2mfXSgK"
|
202
|
-
"7PnAY9OOSd54T2DQWMLd27fR0txsx5HAgYMH+dKXV/L+3n00LW7DV2WgD+5a6XDZZ1wq09YijCkI"
|
203
|
-
"gtqNSs07RvuLwNCY8MRrHv/wZEDTonBrDSEIgfo2EHJIN4cKqDEKMN1Ys7d34fYYM6U4bGAj9k+F"
|
204
|
-
"sK/TY+nN49TiMNzVzvVf+Qrbtm4ll8uVVwdg//79fOGqq2k/0U0xn+HbX3O55coMbrr84BqXJu6v"
|
205
|
-
"YnDg1LDPhodGuW+30hjTu10CpVYdjU+pifvjwmu4HRIzzSSmUphUzlnocs2FDsMnlKa2T/DQ97/P"
|
206
|
-
"bRs2UChMH9yfe+65/Nl1qynmewDl4k+7uGkHDZ2AerFAxwuDIC8wv6Wgx1wUlZoalys+68LENOOz"
|
207
|
-
"gZACKRVzyFE7k/bNsFFCuXKtyVv+uPO2dIAJT+kfVqgKNQdkFmSmthdDKp0q3Re9qOHEBhENLjnz"
|
208
|
-
"oQzm1tDHi2U8MTix5qcecEqXjQHKoDrz22PHnCOee89j93NKrkHo62jn5rVr2WLH7WTr68trAHC4"
|
209
|
-
"vZ0dj+6E+maoEJ76T49Tw54Jglwb3IQBjpsMhMSRMGAKeVzoOenx2M98yJb3ZCDZ0AfUNSm/uCvD"
|
210
|
-
"ongcENOb2iURyitmfcRKwnsxscGJ4YB/frXI+ns86hc6DHa1c8vadWzatJH6urqQP4kjR4/y9TU3"
|
211
|
-
"8dwzu2luM4elvm746ueFS5c6VKQil5wwM2KOJ7ReERiZhCf3+Dz139DUFDlBK1mgJg7QMd+hvkl5"
|
212
|
-
"J6GAqP34FphQQJyA8f5jvvLCAY9NjxZ59y2l4XSHgePt3LJuHZs3bqRuBuGPHj3GV792Ay/8x7M0"
|
213
|
-
"tS0hCEcrQL4AnCIpcKk0jtI0ReUN0FST3AEgcoKSbW3TUd8h26S8szXDohq37Jha3okR2n7WUpp1"
|
214
|
-
"F97v9vmnXZM88GgAdUJzvdDb0c7adevZtHHDjMIf6+jghq+v4dlndpUCIRvcYKPByWnkp0zeEAvS"
|
215
|
-
"UOUmaeWIKyAY8x2ZsgRKnOGvxgQO6YIx997RgCd+XmTN/R70Co2LwBGz5tetv5VNGzdQO100AnR3"
|
216
|
-
"d3PjTd/g33785JRQ2BHomwR64IKlSk2FEtgNOWaKcatU4KVjgg5Ac8vM0aOJA2IKqG1SfmktILCC"
|
217
|
-
"x2rHFCCYGZ8EXv/A486dE7zyMmQWCpm04ervPMJNf/4X/O2WO2ac+ZMnT3Lz2nU89ugjCbO3GPTg"
|
218
|
-
"EzXw4A1plp6ZxgkFjH+4k7BPMX/6BpV7nxrnH3crueYpBgJJC1isY75LTU7ZuzVuAWaKBbPNRekm"
|
219
|
-
"M+sf5n2+99wk2x/0YQHkmqJtc6LoMdLTycGDv+Lssz9Z1rVBPp9n7br17PjhQ9MKT5ihfnxzii9e"
|
220
|
-
"XAWx4/a0a93+qDkWd5wosmLzKAcHoTGclDisBTgmxSmJg0KpQZsHCK1B0krBC3h4zwRnrR1j+4M+"
|
221
|
-
"DadDYy4yQ4CRoRGW/9HFnHbaxyJiDP39/WzYtHlW4Q1JOXuxOZtoEMsI2Webq1A1fstmtXxlYYPL"
|
222
|
-
"5860znMahCKWIsGE/Gh0qdljPQfeOORxzb1jXHtbEXcIcotKAXKiAbeqko6uE4yOjUXEEEb4TTz4"
|
223
|
-
"3ftnFJ5wPYPQ1RfmJ0uXIPYwFvqD0l1YjgPDoz4f9ACZ8paTcAQlUKXSSR4XCQchFXB0KGDLT8ZZ"
|
224
|
-
"ft0Eu55VcouE+srQ5EWSH0wC9ZkFHPn1QZ5++mk8zyvRe3t72bj5Wzxw//3G4c0gPHZCcvCdp4oc"
|
225
|
-
"PDTBZFHxvGCaK0YvKr6v9Pf77Hh+nDfegcYF5S0nIdnWNi2qw0ha+fC+DGdkXWN/Lpzy4Ll3i6zd"
|
226
|
-
"McnRfbFssF2CFmVLkdBvDBw/ym0bNvCHy5fTPzDAYzt/xDO7np515uNwBPpOQbYavrhUqF1gT6kR"
|
227
|
-
"yleuIvyiHV5+W8mVvnicikQc4Lou+Y6Av7vF5YbLF5BJCQe6PO796SSPPB5AA+Sqky9GSsdQylZA"
|
228
|
-
"QgkwcPxIRHCqaTq9ZUrWZzYIcMqHiUGTmJ0VttnsR5j5eBzguq4ISl+HcsmlQnOD8KOXFfqYVYu/"
|
229
|
-
"yzAK8CMFEJpt/xAwArUtUOHOTeGJWUBpF7Dhb2MdNJ4G6bksfOw+2gbLdoC5jGhi5+UXYnFIbAmU"
|
230
|
-
"7eXzBeWxz7xD6QuR+aSIhBMUkcnY8zyD4iAcM1vgXN30ZsWgg2r4D0TzaBFECd/3HFV9IaLPIyWY"
|
231
|
-
"4O9lRz3vp6hO/5pmDsJOsqoW1fefcAonT3SoBg9ZHzCnraAkmqJB8Hihp+s9B8AfH9uigf46wTwn"
|
232
|
-
"Ec5+oN3qFTdgzwJD/X0Dge9frar9EV/4eczvOkrfMYRvulXH1PdXFk6eOEb8MFTo7nxbPe9yDYJD"
|
233
|
-
"iS9BbTLOPMxymfIoUxc+xzqfymOo09RKtBmrHD7GCYYo4Thtf8lxm11eg6Az8Ip/MtDd+ZIlJ96f"
|
234
|
-
"jJ8a6qqoXLBTHLca4TxESgll03Akb7leTKchzSYvEwMPRUzwmMpRu3bwlseUSVxAq5C4kJYU664E"
|
235
|
-
"c8yf0CB4WIuTXy6cPPF+vHjaOgD1Laed6abSVyJyCfDpQLW57B2xTB89WXI4qhJCmrUutbRpEG/Z"
|
236
|
-
"tjEDa4ktORoRkR5F96P6ivr+k4WeroOl0hj+F2nUsotZ+OvIAAAAAElFTkSuQmCC"
|
237
|
-
)
|
238
|
-
|
239
|
-
|
240
|
-
def _load_embedded_icon() -> Image.Image:
|
241
|
-
"""Decode and return the embedded Talks Reducer tray icon."""
|
242
|
-
|
243
|
-
data = base64.b64decode(_EMBEDDED_ICON_BASE64)
|
244
|
-
with Image.open(BytesIO(data)) as image:
|
245
|
-
return image.copy()
|
246
|
-
|
247
|
-
|
248
111
|
def _load_icon() -> Image.Image:
|
249
|
-
"""Load the tray icon image, falling back to
|
112
|
+
"""Load the tray icon image, falling back to a generated placeholder."""
|
250
113
|
|
251
|
-
LOGGER.
|
114
|
+
LOGGER.info("Attempting to load tray icon image.")
|
252
115
|
|
253
116
|
for candidate in _iter_icon_candidates():
|
254
|
-
LOGGER.
|
255
|
-
if candidate.exists():
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
resource_icon = resources.files("talks_reducer") / "assets" / "icon.png"
|
267
|
-
if resource_icon.is_file():
|
268
|
-
LOGGER.debug("Loading tray icon from package resources")
|
269
|
-
with resource_icon.open("rb") as handle:
|
270
|
-
try:
|
271
|
-
with Image.open(handle) as image:
|
272
|
-
return image.copy()
|
273
|
-
except Exception as exc: # pragma: no cover - diagnostic log
|
274
|
-
LOGGER.warning(
|
275
|
-
"Failed to load tray icon from package resources: %s", exc
|
276
|
-
)
|
117
|
+
LOGGER.info("Checking icon candidate at %s", candidate)
|
118
|
+
if not candidate.exists():
|
119
|
+
continue
|
120
|
+
try:
|
121
|
+
with Image.open(candidate) as image:
|
122
|
+
loaded = image.copy()
|
123
|
+
except Exception as exc: # pragma: no cover - diagnostic log
|
124
|
+
LOGGER.warning("Failed to load tray icon from %s: %s", candidate, exc)
|
125
|
+
continue
|
126
|
+
|
127
|
+
LOGGER.info("Loaded tray icon from %s", candidate)
|
128
|
+
return loaded
|
277
129
|
|
278
130
|
LOGGER.warning("Falling back to generated tray icon; packaged image not found")
|
279
|
-
|
280
|
-
image.putpixel((0, 0), (255, 255, 255, 255))
|
281
|
-
image.putpixel((63, 63), (17, 24, 39, 255))
|
282
|
-
return image
|
131
|
+
return _generate_fallback_icon()
|
283
132
|
|
284
133
|
|
285
134
|
class _ServerTrayApplication:
|
@@ -309,6 +158,7 @@ class _ServerTrayApplication:
|
|
309
158
|
self._share_url: Optional[str] = None
|
310
159
|
self._icon: Optional[pystray.Icon] = None
|
311
160
|
self._gui_process: Optional[subprocess.Popen[Any]] = None
|
161
|
+
self._startup_error: Optional[BaseException] = None
|
312
162
|
|
313
163
|
# Server lifecycle -------------------------------------------------
|
314
164
|
|
@@ -362,7 +212,7 @@ class _ServerTrayApplication:
|
|
362
212
|
url = self._resolve_url()
|
363
213
|
if url:
|
364
214
|
webbrowser.open(url)
|
365
|
-
LOGGER.
|
215
|
+
LOGGER.info("Opened browser to %s", url)
|
366
216
|
else:
|
367
217
|
LOGGER.warning("Server URL not yet available; please try again.")
|
368
218
|
|
@@ -383,7 +233,7 @@ class _ServerTrayApplication:
|
|
383
233
|
try:
|
384
234
|
process.wait()
|
385
235
|
except Exception as exc: # pragma: no cover - best-effort cleanup
|
386
|
-
LOGGER.
|
236
|
+
LOGGER.info("GUI process monitor exited with %s", exc)
|
387
237
|
finally:
|
388
238
|
with self._gui_lock:
|
389
239
|
if self._gui_process is process:
|
@@ -406,9 +256,7 @@ class _ServerTrayApplication:
|
|
406
256
|
|
407
257
|
try:
|
408
258
|
LOGGER.info("Launching Talks Reducer GUI via %s", sys.executable)
|
409
|
-
process = subprocess.Popen(
|
410
|
-
[sys.executable, "-m", "talks_reducer.gui"]
|
411
|
-
)
|
259
|
+
process = subprocess.Popen([sys.executable, "-m", "talks_reducer.gui"])
|
412
260
|
except Exception as exc: # pragma: no cover - platform specific
|
413
261
|
LOGGER.error("Failed to launch Talks Reducer GUI: %s", exc)
|
414
262
|
self._gui_process = None
|
@@ -435,26 +283,45 @@ class _ServerTrayApplication:
|
|
435
283
|
|
436
284
|
# Public API -------------------------------------------------------
|
437
285
|
|
438
|
-
def
|
439
|
-
"""
|
286
|
+
def _await_server_start(self, icon: Optional[pystray.Icon]) -> None:
|
287
|
+
"""Wait for the server to signal readiness or trigger shutdown on failure."""
|
440
288
|
|
441
|
-
|
442
|
-
|
289
|
+
if self._ready_event.wait(timeout=30):
|
290
|
+
if self._open_browser_on_start and not self._stop_event.is_set():
|
291
|
+
self._handle_open_webui()
|
292
|
+
return
|
293
|
+
|
294
|
+
if self._stop_event.is_set():
|
295
|
+
return
|
296
|
+
|
297
|
+
error = RuntimeError(
|
298
|
+
"Timed out while waiting for the Talks Reducer server to start."
|
443
299
|
)
|
444
|
-
|
300
|
+
self._startup_error = error
|
301
|
+
LOGGER.error("%s", error)
|
445
302
|
|
446
|
-
if not
|
447
|
-
|
448
|
-
"
|
449
|
-
)
|
303
|
+
if icon is not None:
|
304
|
+
with suppress(Exception):
|
305
|
+
icon.notify("Talks Reducer server failed to start.")
|
450
306
|
|
451
|
-
|
452
|
-
|
307
|
+
self.stop()
|
308
|
+
|
309
|
+
def run(self) -> None:
|
310
|
+
"""Start the server and block until the tray icon exits."""
|
311
|
+
|
312
|
+
self._startup_error = None
|
313
|
+
|
314
|
+
threading.Thread(
|
315
|
+
target=self._launch_server, name="talks-reducer-server", daemon=True
|
316
|
+
).start()
|
453
317
|
|
454
318
|
if self._tray_mode == "headless":
|
455
319
|
LOGGER.warning(
|
456
320
|
"Tray icon disabled (tray_mode=headless); press Ctrl+C to stop the server."
|
457
321
|
)
|
322
|
+
self._await_server_start(None)
|
323
|
+
if self._startup_error is not None:
|
324
|
+
raise self._startup_error
|
458
325
|
try:
|
459
326
|
while not self._stop_event.wait(0.5):
|
460
327
|
pass
|
@@ -484,6 +351,14 @@ class _ServerTrayApplication:
|
|
484
351
|
menu=menu,
|
485
352
|
)
|
486
353
|
|
354
|
+
watcher = threading.Thread(
|
355
|
+
target=self._await_server_start,
|
356
|
+
args=(self._icon,),
|
357
|
+
name="talks-reducer-server-watcher",
|
358
|
+
daemon=True,
|
359
|
+
)
|
360
|
+
watcher.start()
|
361
|
+
|
487
362
|
if self._tray_mode == "pystray-detached":
|
488
363
|
LOGGER.info("Running tray icon in detached mode")
|
489
364
|
self._icon.run_detached()
|
@@ -492,10 +367,14 @@ class _ServerTrayApplication:
|
|
492
367
|
pass
|
493
368
|
finally:
|
494
369
|
self.stop()
|
370
|
+
if self._startup_error is not None:
|
371
|
+
raise self._startup_error
|
495
372
|
return
|
496
373
|
|
497
374
|
LOGGER.info("Running tray icon in blocking mode")
|
498
375
|
self._icon.run()
|
376
|
+
if self._startup_error is not None:
|
377
|
+
raise self._startup_error
|
499
378
|
|
500
379
|
def stop(self) -> None:
|
501
380
|
"""Stop the tray icon and shut down the Gradio server."""
|
@@ -535,7 +414,7 @@ class _ServerTrayApplication:
|
|
535
414
|
process.kill()
|
536
415
|
process.wait(timeout=5)
|
537
416
|
except Exception as exc: # pragma: no cover - defensive cleanup
|
538
|
-
LOGGER.
|
417
|
+
LOGGER.info("Error while terminating GUI process: %s", exc)
|
539
418
|
|
540
419
|
self._gui_process = None
|
541
420
|
|
@@ -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,4 +1,4 @@
|
|
1
|
-
talks_reducer/__about__.py,sha256=
|
1
|
+
talks_reducer/__about__.py,sha256=xjrdvQModGBv_84s8dFgz5jQVuohfdJ0TqtjSXpya4A,92
|
2
2
|
talks_reducer/__init__.py,sha256=Kzh1hXaw6Vq3DyTqrnJGOq8pn0P8lvaDcsg1bFUjFKk,208
|
3
3
|
talks_reducer/__main__.py,sha256=azR_vh8HFPLaOnh-L6gUFWsL67I6iHtbeH5rQhsipGY,299
|
4
4
|
talks_reducer/audio.py,sha256=sjHMeY0H9ESG-Gn5BX0wFRBX7sXjWwsgS8u9Vb0bJ88,4396
|
@@ -6,24 +6,25 @@ talks_reducer/chunks.py,sha256=IpdZxRFPURSG5wP-OQ_p09CVP8wcKwIFysV29zOTSWI,2959
|
|
6
6
|
talks_reducer/cli.py,sha256=MeL5feATcJc0bGPDuW_L3HgepKQUi335zs3HVg47WrE,16474
|
7
7
|
talks_reducer/discovery.py,sha256=BJ-iMir65cJMs0u-_EYdknBQT_grvCZaJNOx1xGi2PU,4590
|
8
8
|
talks_reducer/ffmpeg.py,sha256=dsHBOBcr5XCSg0q3xmzLOcibBiEdyrXdEQa-ze5vQsM,12551
|
9
|
+
talks_reducer/icons.py,sha256=Htkef_iyqVkQQ4R3Kh1Wfbkiq3-3aJosA_nTWlY0vDA,3769
|
9
10
|
talks_reducer/models.py,sha256=a1cHCVTNTJYh9I437CuANiaz5R_s-uECeGyK7WB67HQ,2018
|
10
11
|
talks_reducer/pipeline.py,sha256=OGZG_3G1fh6LFQw9NuhnLq7gwJ5YcJ6l76QNWJydD7c,13630
|
11
12
|
talks_reducer/progress.py,sha256=Mh43M6VWhjjUv9CI22xfD2EJ_7Aq3PCueqefQ9Bd5-o,4565
|
12
|
-
talks_reducer/server.py,sha256=
|
13
|
-
talks_reducer/server_tray.py,sha256=
|
13
|
+
talks_reducer/server.py,sha256=sgnUr0tDHXf1dZuYbu3Kkx0LXegHAn5GD2vP6KoRDFI,15732
|
14
|
+
talks_reducer/server_tray.py,sha256=Gy8RXhYm0v_Uu9rHTq8K7EJFfZJ3TW-aIwUbXfXqreA,16087
|
14
15
|
talks_reducer/service_client.py,sha256=8C2v2aNj8UAECfy1pw7oIzCK3Ktx5E6kZoNSYWH-8m8,12656
|
15
16
|
talks_reducer/version_utils.py,sha256=TkYrTznVb2JqxFXzVzPd6PEnYP2MH7dxKl1J4-3DjMA,755
|
16
|
-
talks_reducer/gui/__init__.py,sha256=
|
17
|
+
talks_reducer/gui/__init__.py,sha256=gL_epCXuf6c5yOdWwT9kmvmYta7rq5CbDyJBwFFwKwo,59872
|
17
18
|
talks_reducer/gui/__main__.py,sha256=9YWkGopLypanfMMq_RoQjjpPScTOxA7-biqMhQq-SSM,140
|
18
19
|
talks_reducer/gui/discovery.py,sha256=6AXPcFGXqHZNhSBE1O5PyoH_CEMCb0Jk-9JGFwyAuRk,4108
|
19
|
-
talks_reducer/gui/layout.py,sha256=
|
20
|
+
talks_reducer/gui/layout.py,sha256=ev6AbHOxxowhmZq8ZhT1C2-d88SzCMSdjBNkp8QgM7Q,16739
|
20
21
|
talks_reducer/gui/preferences.py,sha256=ahDLPNIzvL71sw8WvgY9-TV_kaWTl_JTkn1gf2Z1EaA,3531
|
21
22
|
talks_reducer/gui/remote.py,sha256=92HebrIo009GgRD7RBriw9yR8sbYHocsPzmjPe4ybhA,12071
|
22
23
|
talks_reducer/gui/theme.py,sha256=ueqpPVPOwnLkeHlOlWkUcdcoClJrAqz9LWT79p33Xic,7718
|
23
24
|
talks_reducer/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
24
|
-
talks_reducer-0.7.
|
25
|
-
talks_reducer-0.7.
|
26
|
-
talks_reducer-0.7.
|
27
|
-
talks_reducer-0.7.
|
28
|
-
talks_reducer-0.7.
|
29
|
-
talks_reducer-0.7.
|
25
|
+
talks_reducer-0.7.2.dist-info/licenses/LICENSE,sha256=jN17mHNR3e84awmH3AbpWBcBDBzPxEH0rcOFoj1s7sQ,1124
|
26
|
+
talks_reducer-0.7.2.dist-info/METADATA,sha256=BJnDrkCMyXiNm_ByyjbyQOE4J2Bbbzl29qJfvbiy5fA,9994
|
27
|
+
talks_reducer-0.7.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
28
|
+
talks_reducer-0.7.2.dist-info/entry_points.txt,sha256=X2pjoh2vWBXXExVWorv1mbA1aTEVP3fyuZH4AixqZK4,208
|
29
|
+
talks_reducer-0.7.2.dist-info/top_level.txt,sha256=pJWGcy__LR9JIEKH3QJyFmk9XrIsiFtqvuMNxFdIzDU,14
|
30
|
+
talks_reducer-0.7.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|