echoact 0.1.0__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.
Files changed (80) hide show
  1. echoact/__init__.py +3 -0
  2. echoact/__main__.py +117 -0
  3. echoact/app.py +315 -0
  4. echoact/audio/__init__.py +0 -0
  5. echoact/audio/devices.py +192 -0
  6. echoact/audio/player.py +611 -0
  7. echoact/audio/wav.py +854 -0
  8. echoact/config/__init__.py +0 -0
  9. echoact/config/budget.py +370 -0
  10. echoact/config/settings.py +1244 -0
  11. echoact/db/__init__.py +0 -0
  12. echoact/db/backup.py +2429 -0
  13. echoact/db/migrations.py +434 -0
  14. echoact/db/schema.sql +214 -0
  15. echoact/db/store.py +2062 -0
  16. echoact/diagnostics.py +902 -0
  17. echoact/domain.py +487 -0
  18. echoact/engine/__init__.py +0 -0
  19. echoact/engine/container.py +843 -0
  20. echoact/engine/protocol.py +241 -0
  21. echoact/engine/runtime.py +324 -0
  22. echoact/engine/supervisor.py +961 -0
  23. echoact/engine/worker.py +659 -0
  24. echoact/errors.py +281 -0
  25. echoact/instance.py +172 -0
  26. echoact/jobs/__init__.py +0 -0
  27. echoact/jobs/engine.py +776 -0
  28. echoact/jobs/request.py +300 -0
  29. echoact/mcp/__init__.py +0 -0
  30. echoact/mcp/__main__.py +50 -0
  31. echoact/mcp/client.py +202 -0
  32. echoact/mcp/config.py +112 -0
  33. echoact/mcp/server.py +340 -0
  34. echoact/models/__init__.py +0 -0
  35. echoact/models/catalog.py +273 -0
  36. echoact/models/manifest.py +278 -0
  37. echoact/models/registry.py +1551 -0
  38. echoact/paths.py +93 -0
  39. echoact/policy.py +189 -0
  40. echoact/security/__init__.py +0 -0
  41. echoact/security/credentials.py +930 -0
  42. echoact/security/ratelimit.py +534 -0
  43. echoact/service/__init__.py +20 -0
  44. echoact/service/app.py +182 -0
  45. echoact/service/deps.py +563 -0
  46. echoact/service/errors.py +241 -0
  47. echoact/service/routes.py +1125 -0
  48. echoact/service/schemas.py +509 -0
  49. echoact/service/server.py +270 -0
  50. echoact/text/__init__.py +0 -0
  51. echoact/text/language.py +44 -0
  52. echoact/text/loader.py +577 -0
  53. echoact/text/normalize.py +924 -0
  54. echoact/text/segment.py +499 -0
  55. echoact/text/sniff.py +1202 -0
  56. echoact/ui/__init__.py +0 -0
  57. echoact/ui/bridge.py +50 -0
  58. echoact/ui/controls.py +360 -0
  59. echoact/ui/credential_dialog.py +131 -0
  60. echoact/ui/fonts.py +94 -0
  61. echoact/ui/i18n.py +260 -0
  62. echoact/ui/icons.py +440 -0
  63. echoact/ui/library.py +1642 -0
  64. echoact/ui/licence.py +162 -0
  65. echoact/ui/main_window.py +1202 -0
  66. echoact/ui/mcp_setup.py +494 -0
  67. echoact/ui/models_view.py +1142 -0
  68. echoact/ui/notifications.py +202 -0
  69. echoact/ui/reading.py +494 -0
  70. echoact/ui/settings_view.py +2258 -0
  71. echoact/ui/status_view.py +1193 -0
  72. echoact/ui/theme.py +579 -0
  73. echoact/util/__init__.py +0 -0
  74. echoact/util/ids.py +62 -0
  75. echoact/util/logging.py +127 -0
  76. echoact-0.1.0.dist-info/METADATA +162 -0
  77. echoact-0.1.0.dist-info/RECORD +80 -0
  78. echoact-0.1.0.dist-info/WHEEL +4 -0
  79. echoact-0.1.0.dist-info/entry_points.txt +3 -0
  80. echoact-0.1.0.dist-info/licenses/LICENSE +21 -0
echoact/paths.py ADDED
@@ -0,0 +1,93 @@
1
+ """Where EchoAct keeps things on disk.
2
+
3
+ One module so that F-72's diagnostic export can state every location, F-73
4
+ can size each of them separately, and N-20 can keep the user's home path out
5
+ of logs by rendering paths relative to these roots.
6
+
7
+ An ``ECHOACT_DATA_DIR`` override exists for tests and for running two builds
8
+ side by side; nothing in the product writes outside the returned tree except
9
+ files the user explicitly chooses (exported WAV, backups).
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import os
15
+ import sys
16
+ from functools import lru_cache
17
+ from pathlib import Path
18
+
19
+ APP_NAME = "EchoAct"
20
+
21
+
22
+ @lru_cache(maxsize=1)
23
+ def data_dir() -> Path:
24
+ """Per-user application data: database, audio, settings, logs."""
25
+ override = os.environ.get("ECHOACT_DATA_DIR")
26
+ if override:
27
+ return Path(override).expanduser()
28
+ if sys.platform == "win32":
29
+ base = os.environ.get("LOCALAPPDATA") or (Path.home() / "AppData" / "Local")
30
+ return Path(base) / APP_NAME
31
+ if sys.platform == "darwin":
32
+ return Path.home() / "Library" / "Application Support" / APP_NAME
33
+ return Path(os.environ.get("XDG_DATA_HOME", Path.home() / ".local" / "share")) / APP_NAME
34
+
35
+
36
+ def db_path() -> Path:
37
+ return data_dir() / "echoact.sqlite3"
38
+
39
+
40
+ def audio_dir() -> Path:
41
+ """Retained job audio and full-result WAV files."""
42
+ return data_dir() / "audio"
43
+
44
+
45
+ def temp_dir() -> Path:
46
+ """Segment audio for in-flight jobs and one-off results.
47
+
48
+ N-02 requires whatever a forced termination leaves here to be cleaned up
49
+ on relaunch, so nothing durable may live in this tree.
50
+ """
51
+ return data_dir() / "temp"
52
+
53
+
54
+ def log_dir() -> Path:
55
+ return data_dir() / "logs"
56
+
57
+
58
+ def model_cache_dir() -> Path:
59
+ """Model weights. Kept apart from user data so F-73 can size it, F-65
60
+ can delete it, and F-76 can offer it as a separate deletion scope."""
61
+ override = os.environ.get("ECHOACT_MODEL_DIR")
62
+ if override:
63
+ return Path(override).expanduser()
64
+ return data_dir() / "models"
65
+
66
+
67
+ def settings_path() -> Path:
68
+ return data_dir() / "settings.json"
69
+
70
+
71
+ def lock_path() -> Path:
72
+ """F-85's single-instance marker."""
73
+ return data_dir() / "instance.lock"
74
+
75
+
76
+ def ensure_tree() -> None:
77
+ """Create every directory the app writes to. Safe to call repeatedly."""
78
+ for p in (data_dir(), audio_dir(), temp_dir(), log_dir(), model_cache_dir()):
79
+ p.mkdir(parents=True, exist_ok=True)
80
+
81
+
82
+ def redact(path: str | Path) -> str:
83
+ """Render a path for a log or a diagnostic export.
84
+
85
+ N-20 and F-72 exclude the user's home path. Anything under the data
86
+ directory becomes ``<data>/...``; anything else becomes its basename, so
87
+ a filename the user chose never leaks its directory.
88
+ """
89
+ p = Path(path)
90
+ try:
91
+ return "<data>/" + p.resolve().relative_to(data_dir().resolve()).as_posix()
92
+ except (ValueError, OSError):
93
+ return "<path>/" + p.name
echoact/policy.py ADDED
@@ -0,0 +1,189 @@
1
+ """Section 4's operating policy, as constants.
2
+
3
+ Every number the requirements fix lives here and nowhere else, so that a
4
+ policy change is one edit and a reviewer can check the document against one
5
+ file. Nothing here is read from disk: these are the *defaults* that
6
+ ``echoact.config.settings`` starts from and that safety limits clamp against.
7
+
8
+ Units follow Section 4.1's closing rule: memory in GiB (2**30), storage in
9
+ GB (10**9), transfer in bytes, CPU as a percentage of total logical capacity.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from typing import Final
15
+
16
+ GIB: Final = 1 << 30
17
+ GB: Final = 1_000_000_000
18
+
19
+ # ---------------------------------------------------------------- input ---
20
+ MAX_INPUT_CODEPOINTS: Final = 50_000 # F-03
21
+ MAX_IMPORT_FILE_BYTES: Final = 2_000_000 # F-03
22
+ MAX_REQUEST_BODY_BYTES: Final = 2_000_000 # 4.1, includes upload metadata
23
+
24
+ # ------------------------------------------------------------ segmenting ---
25
+ # F-81. A segment is capped by code points and by estimated audio seconds;
26
+ # the first segment of a job is capped harder so playback can start sooner.
27
+ SEGMENT_MAX_CODEPOINTS: Final = 200
28
+ SEGMENT_MAX_SECONDS: Final = 20.0
29
+ FIRST_SEGMENT_MAX_SECONDS: Final = 8.0
30
+ SEGMENT_MIN_CODEPOINTS: Final = 20
31
+
32
+ # Estimating audio length from source length.
33
+ #
34
+ # F-81 splits on an estimated duration and F-88 quotes one to a caller, so
35
+ # the numbers have to come from the engine rather than from intuition. These
36
+ # are measured against Supertonic 3 at tempo 1.00, two threads, voice F1:
37
+ # Korean 6.0 code points per second of audio (range 3.7 to 6.4), English 13.3
38
+ # (range 7.2 to 14.0). Short sentences sit at the low end because onset and
39
+ # trailing silence are a larger fraction of them, so using the median
40
+ # over-estimates a short segment's length -- which is the safe direction for
41
+ # a cap. Tempo divides duration almost exactly: 0.70x measured 1.43x the
42
+ # 1.00x length and 1.50x measured 0.67x, so ``seconds / tempo`` is the model.
43
+ ESTIMATE_CODEPOINTS_PER_SECOND_KO: Final = 6.0
44
+ ESTIMATE_CODEPOINTS_PER_SECOND_EN: Final = 13.3
45
+ #: Seconds of computation per second of audio, at the Section 8.2 baseline.
46
+ #: Median 0.24, worst case 0.34; the worst case is quoted so an estimate is
47
+ #: not optimistic about a machine that is busier than the one measured.
48
+ ESTIMATE_REAL_TIME_FACTOR: Final = 0.35
49
+
50
+ # ----------------------------------------------------------- engine call ---
51
+ # Fixed arguments for every synthesis call. Both exist to keep a segment's
52
+ # duration exactly known rather than inferred:
53
+ #
54
+ # * The engine re-chunks long text on its own (120 code points for Korean)
55
+ # and would then decide its own internal boundaries. A large limit makes
56
+ # one of our segments exactly one engine call.
57
+ # * The engine's own inter-chunk silence is inert once we chunk first, which
58
+ # A.5 measured, so F-82 makes inter-segment silence the app's to insert.
59
+ # Asking for zero states that intent rather than relying on the accident.
60
+ ENGINE_MAX_CHUNK_CODEPOINTS: Final = 100_000
61
+ ENGINE_SILENCE_DURATION_S: Final = 0.0
62
+ #: Diffusion steps. A.5 measured RTF 0.067 at the fastest setting and 0.207
63
+ #: at this one; the margin over playback is fivefold either way, so quality
64
+ #: wins.
65
+ ENGINE_TOTAL_STEPS: Final = 8
66
+
67
+ # F-08's speaking styles, as the preset the requirement describes: a tempo
68
+ # multiplier applied on top of the user's own tempo, and the inter-segment
69
+ # pause the app inserts per F-82. A style never changes which characters are
70
+ # spoken. The product of the two tempi is clamped to F-07's range, so a
71
+ # style can never take tempo outside what the user is told is possible.
72
+ STYLE_TEMPO_MULTIPLIER: Final = {
73
+ "natural": 1.00,
74
+ "calm": 0.92,
75
+ "bright": 1.08,
76
+ "narration": 0.97,
77
+ }
78
+ STYLE_SEGMENT_GAP_MS: Final = {
79
+ "natural": 250,
80
+ "calm": 400,
81
+ "bright": 180,
82
+ "narration": 320,
83
+ }
84
+ #: Extra pause where the source text itself had a paragraph break.
85
+ PARAGRAPH_EXTRA_GAP_MS: Final = 350
86
+
87
+ # ---------------------------------------------------------------- voice ---
88
+ TEMPO_MIN: Final = 0.70 # F-07
89
+ TEMPO_MAX: Final = 1.50
90
+ TEMPO_DEFAULT: Final = 1.00
91
+
92
+ # ------------------------------------------------------------- resources ---
93
+ CPU_PERCENT_MIN: Final = 10 # F-20
94
+ CPU_PERCENT_MAX: Final = 70
95
+ CPU_PERCENT_DEFAULT: Final = 20
96
+
97
+ MEMORY_DEFAULT_FRACTION: Final = 0.25 # F-21
98
+ MEMORY_DEFAULT_MIN_BYTES: Final = 2 * GIB
99
+ MEMORY_DEFAULT_MAX_BYTES: Final = 6 * GIB
100
+ MEMORY_CEILING_BYTES: Final = 32 * GIB
101
+ MEMORY_CEILING_FRACTION_OF_TOTAL: Final = 0.5
102
+ MEMORY_FLOOR_BYTES: Final = 2 * GIB # F-23: below this, do not load
103
+
104
+ HEADROOM_FRACTION: Final = 0.10 # N-04: greater of 10% of RAM or 1 GiB
105
+ HEADROOM_MIN_BYTES: Final = 1 * GIB
106
+ RESOURCE_SAMPLE_INTERVAL_S: Final = 0.5
107
+
108
+ # --------------------------------------------------------------- jobs -----
109
+ MAX_CONCURRENT_GENERATION: Final = 1 # F-47, whole app
110
+ WORKER_RELEASE_DEADLINE_S: Final = 5.0 # N-22
111
+ WORKER_TERMINATE_GRACE_S: Final = 1.0 # ask, then kill
112
+
113
+ # One-off results. 4.1: external jobs stay retrievable for one hour after a
114
+ # terminal state, or until the app exits, whichever comes first.
115
+ ONEOFF_RESULT_TTL_S: Final = 3600.0
116
+ IDEMPOTENCY_TTL_S: Final = 3600.0 # 4.1, survives a normal restart
117
+
118
+ # F-88 / 4.1 bounded wait.
119
+ BOUNDED_WAIT_DEFAULT_S: Final = 10.0
120
+ BOUNDED_WAIT_CEILING_S: Final = 60.0
121
+
122
+ # --------------------------------------------------------------- limits ---
123
+ #: What a caller is told to wait for a result that is not finished yet.
124
+ #: Section 4 fixes no figure, so this borrows the busy hint rather than
125
+ #: introducing a second unexplained number.
126
+ NOT_READY_RETRY_AFTER_S: Final = 3.0
127
+
128
+ # F-74's scheduled backup: once daily, made up only once when missed.
129
+ SCHEDULED_BACKUP_INTERVAL_S: Final = 86_400.0
130
+ #: How long a failed scheduled backup waits before trying again. Not a
131
+ #: requirement; without it a frequent tick would retry a failing backup
132
+ #: continuously, which N-28 would not forgive.
133
+ SCHEDULED_BACKUP_RETRY_S: Final = 3_600.0
134
+
135
+ RATE_GENERATION_PER_MIN: Final = 10 # 4.1, per client
136
+ RATE_OTHER_PER_MIN: Final = 120
137
+ AUTH_FAILURES_PER_MIN: Final = 10
138
+ AUTH_LOCKOUT_S: Final = 60.0
139
+
140
+ LIST_PAGE_DEFAULT: Final = 20
141
+ LIST_PAGE_MAX: Final = 100
142
+
143
+ # -------------------------------------------------------------- storage ---
144
+ RETENTION_DEFAULT_BYTES: Final = 5 * GB
145
+ RETENTION_MIN_BYTES: Final = 1 * GB
146
+ RETENTION_MAX_BYTES: Final = 100 * GB
147
+ LOW_SPACE_WARNING_BYTES: Final = 1 * GB
148
+
149
+ LOG_RETENTION_DAYS: Final = 7
150
+ LOG_MAX_BYTES: Final = 100 * 1_000_000
151
+
152
+ BACKUP_KEEP_SCHEDULED: Final = 7
153
+ BACKUP_RESTORE_MAX_BYTES: Final = 100 * GB
154
+ BACKUP_RESTORE_MAX_ITEMS: Final = 100_000
155
+
156
+ # ------------------------------------------------------------- service ---
157
+ REST_HOST: Final = "127.0.0.1" # N-17: loopback only, not configurable
158
+ REST_PORT_DEFAULT: Final = 8765
159
+ REST_ENABLED_DEFAULT: Final = True # F-46
160
+ MCP_ENABLED_DEFAULT: Final = False # F-46
161
+
162
+ CREDENTIAL_DAYS_DEFAULT: Final = 90 # 4.1
163
+ CREDENTIAL_DAYS_MIN: Final = 1
164
+ CREDENTIAL_DAYS_MAX: Final = 365
165
+ CREDENTIAL_EXPIRY_WARNING_DAYS: Final = 7
166
+
167
+ # Hosts a request may claim. N-17 evaluates this before authentication.
168
+ ALLOWED_HOSTS: Final = frozenset(
169
+ {"127.0.0.1", "localhost", "[::1]", "::1"}
170
+ )
171
+
172
+ # ------------------------------------------------------------ interface ---
173
+ API_VERSION: Final = "v1"
174
+ API_PREFIX: Final = "/api/v1"
175
+ # The MCP revision this build implements, per F-58.
176
+ MCP_PROTOCOL_REVISION: Final = "2026-07-28"
177
+
178
+ # ----------------------------------------------------------------- misc ---
179
+ VOICE_PRESET_MAX: Final = 100 # 4.1
180
+ PLAYBACK_VOLUME_DEFAULT: Final = 1.0
181
+ OS_NOTIFICATIONS_DEFAULT: Final = False
182
+ AUTOPLAY_DEFAULT: Final = True # F-83
183
+ FOLLOW_DEFAULT: Final = True # F-30
184
+ AUTOSAVE_DOCUMENTS_DEFAULT: Final = False # 4.1
185
+ RETAIN_HISTORY_DEFAULT: Final = False # 4.1
186
+ SCHEDULED_BACKUP_DEFAULT: Final = False # 4.1
187
+
188
+ # N-12: the on-screen highlight must track the audio clock this closely.
189
+ HIGHLIGHT_SYNC_BUDGET_MS: Final = 300
File without changes