srtmanager 0.1.0__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.
- srtmanager-0.1.0/PKG-INFO +613 -0
- srtmanager-0.1.0/README.md +607 -0
- srtmanager-0.1.0/pyproject.toml +10 -0
- srtmanager-0.1.0/src/srtmanager/__init__.py +350 -0
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: srtmanager
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Advanced, expressive, production-safe subtitle manager for `.srt` files.
|
|
5
|
+
Description-Content-Type: text/markdown
|
|
6
|
+
|
|
7
|
+
# 🎬 SRTManager
|
|
8
|
+
|
|
9
|
+
**Advanced, expressive, production-safe subtitle manager for `.srt` files.**
|
|
10
|
+
|
|
11
|
+
`SRTManager` is a high-level abstraction built on top of the `srt` Python library that enforces strict subtitle invariants while providing powerful transformation utilities like shifting, slicing, scaling, merging, gap compression, content mapping, and more.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## ✨ Features
|
|
16
|
+
|
|
17
|
+
* ✅ Automatic sorting & reindexing
|
|
18
|
+
* ✅ Overlap detection
|
|
19
|
+
* ✅ Timestamp validation (no negative times)
|
|
20
|
+
* ✅ Immutable-style transformations
|
|
21
|
+
* ✅ Shift subtitles forward/backward
|
|
22
|
+
* ✅ Merge subtitle files safely
|
|
23
|
+
* ✅ Clip/slice by time range
|
|
24
|
+
* ✅ Search by text
|
|
25
|
+
* ✅ Split & join subtitle segments
|
|
26
|
+
* ✅ Gap compression
|
|
27
|
+
* ✅ Duration scaling
|
|
28
|
+
* ✅ Functional content transformations
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 📦 Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install srtmanager
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Then place `SRTManager` in your project.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 🧠 Core Invariants
|
|
43
|
+
|
|
44
|
+
Every `SRTManager` instance guarantees:
|
|
45
|
+
|
|
46
|
+
1. Subtitles are sorted by start time
|
|
47
|
+
2. Subtitles are sequentially indexed from `1`
|
|
48
|
+
3. No overlapping subtitles
|
|
49
|
+
4. All timestamps are ≥ 0
|
|
50
|
+
|
|
51
|
+
If any invariant is violated:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
SRTValidationError
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
is raised.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 🚀 Quick Start
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from srtmanager import SRTManager
|
|
65
|
+
|
|
66
|
+
# Load from file
|
|
67
|
+
subs = SRTManager.from_file("input.srt")
|
|
68
|
+
|
|
69
|
+
# Shift forward by 2 seconds
|
|
70
|
+
shifted = subs >> 2
|
|
71
|
+
|
|
72
|
+
# Search for text
|
|
73
|
+
hello_lines = subs["hello"]
|
|
74
|
+
|
|
75
|
+
# Slice between 10s and 30s
|
|
76
|
+
clip = subs[10:30]
|
|
77
|
+
|
|
78
|
+
# Save result
|
|
79
|
+
shifted.save("output.srt")
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
# 📚 API Documentation
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 🔹 Constructors
|
|
89
|
+
|
|
90
|
+
### `SRTManager.from_file(path: str)`
|
|
91
|
+
|
|
92
|
+
Load subtitles from `.srt` file.
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
subs = SRTManager.from_file("movie.srt")
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
### `SRTManager.from_string(raw: str)`
|
|
101
|
+
|
|
102
|
+
Parse subtitles from raw string.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## 🔹 Basic Properties
|
|
107
|
+
|
|
108
|
+
### `len(manager)`
|
|
109
|
+
|
|
110
|
+
Returns number of subtitles.
|
|
111
|
+
|
|
112
|
+
### `manager.start`
|
|
113
|
+
|
|
114
|
+
Start timestamp of first subtitle.
|
|
115
|
+
|
|
116
|
+
### `manager.end`
|
|
117
|
+
|
|
118
|
+
End timestamp of last subtitle.
|
|
119
|
+
|
|
120
|
+
### `manager.duration`
|
|
121
|
+
|
|
122
|
+
Total duration (`end - start`).
|
|
123
|
+
|
|
124
|
+
You can also **scale duration**:
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
subs.duration = 120 # scale entire timeline to 120 seconds
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## 🔹 Time Shifting
|
|
133
|
+
|
|
134
|
+
### `shift(seconds: float) -> SRTManager`
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
new_subs = subs.shift(2.5)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Operator Overloads
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
subs >> 2 # shift forward 2 seconds
|
|
144
|
+
subs << 1 # shift backward 1 second
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## 🔹 Merge
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
combined = subs1 + subs2
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
* Automatically shifts second file to avoid overlap.
|
|
156
|
+
* Maintains invariants.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## 🔹 Slice (Time Clipping)
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
clip = subs.slice(10, 30)
|
|
164
|
+
# or
|
|
165
|
+
clip = subs[10:30]
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
* Clips subtitles within range
|
|
169
|
+
* Adjusts start/end if partially overlapping
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## 🔹 Find (Search)
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
results = subs.find("hello")
|
|
177
|
+
results = subs.find("HELLO", case_sensitive=True)
|
|
178
|
+
|
|
179
|
+
# Shortcut
|
|
180
|
+
results = subs["hello"]
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Returns new `SRTManager` containing matches.
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## 🔹 Split
|
|
188
|
+
|
|
189
|
+
Split subtitles using delimiter:
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
parts = subs.split("<line>")
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Returns list of `SRTManager` instances.
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## 🔹 Join as Single Subtitle
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
single = subs.join_as_single()
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Returns one merged `srt.Subtitle`.
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## 🔹 Gap Compression
|
|
210
|
+
|
|
211
|
+
Removes silent gaps between subtitles while preserving durations:
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
tight = subs.compress_gaps()
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## 🔹 Content Transformation
|
|
220
|
+
|
|
221
|
+
Functional style mapping:
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
upper = subs.map_content(lambda text: text.upper())
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## 🔹 Plain Text Export
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
text = subs.to_plain_text()
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## 🔹 Save
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
subs.save("output.srt")
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## 🔹 Add Raw Subtitles (Validated)
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
subs.add_raw(new_subtitles)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Re-validates:
|
|
252
|
+
|
|
253
|
+
* Sorting
|
|
254
|
+
* Indexing
|
|
255
|
+
* Overlaps
|
|
256
|
+
|
|
257
|
+
Raises `SRTValidationError` if invalid.
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
# 🛡 Error Handling
|
|
262
|
+
|
|
263
|
+
### `SRTValidationError`
|
|
264
|
+
|
|
265
|
+
Raised when:
|
|
266
|
+
|
|
267
|
+
* Negative timestamps
|
|
268
|
+
* End before start
|
|
269
|
+
* Overlapping subtitles
|
|
270
|
+
|
|
271
|
+
Example:
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
try:
|
|
275
|
+
subs = SRTManager(invalid_subtitles)
|
|
276
|
+
except SRTValidationError as e:
|
|
277
|
+
print("Invalid SRT:", e)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
# 🧩 Design Philosophy
|
|
283
|
+
|
|
284
|
+
This library follows:
|
|
285
|
+
|
|
286
|
+
* **Immutability-first transformations**
|
|
287
|
+
* **Strong invariants**
|
|
288
|
+
* **Operator overloading for expressiveness**
|
|
289
|
+
* **Functional programming patterns**
|
|
290
|
+
* **Production-safe validation**
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
# 🔮 Example Workflow
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
subs = (
|
|
298
|
+
SRTManager.from_file("raw.srt")
|
|
299
|
+
.shift(1.2)
|
|
300
|
+
.map_content(str.strip)
|
|
301
|
+
.compress_gaps()
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
subs.save("cleaned.srt")
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
# 📌 Ideal Use Cases
|
|
310
|
+
|
|
311
|
+
* Subtitle synchronization
|
|
312
|
+
* Post-processing AI-generated subtitles
|
|
313
|
+
* Timeline alignment
|
|
314
|
+
* Subtitle merging pipelines
|
|
315
|
+
* Research projects
|
|
316
|
+
* Video automation tools
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
# 🧠 Author Notes
|
|
321
|
+
|
|
322
|
+
Built for developers who want:
|
|
323
|
+
|
|
324
|
+
* Clean abstraction
|
|
325
|
+
* No silent timeline corruption
|
|
326
|
+
* Predictable transformations
|
|
327
|
+
* Composable subtitle workflows
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
Below are **advanced, production-level usage patterns** for your `SRTManager` implementation
|
|
333
|
+
|
|
334
|
+
These go beyond basic examples and show how to build real subtitle pipelines.
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
# 🔥 Advanced Usage Examples
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
# 1️⃣ Auto-Sync Two Subtitle Files
|
|
343
|
+
|
|
344
|
+
### 🎯 Problem
|
|
345
|
+
|
|
346
|
+
You have:
|
|
347
|
+
|
|
348
|
+
* `dialogue.srt`
|
|
349
|
+
* `translated.srt` (starts earlier)
|
|
350
|
+
|
|
351
|
+
You want to align them safely and merge.
|
|
352
|
+
|
|
353
|
+
```python
|
|
354
|
+
from srtmanager import SRTManager
|
|
355
|
+
|
|
356
|
+
dialogue = SRTManager.from_file("dialogue.srt")
|
|
357
|
+
translated = SRTManager.from_file("translated.srt")
|
|
358
|
+
|
|
359
|
+
# Align translated to dialogue start
|
|
360
|
+
offset = (dialogue.start - translated.start).total_seconds()
|
|
361
|
+
translated_aligned = translated.shift(offset)
|
|
362
|
+
|
|
363
|
+
# Merge safely (no overlap corruption)
|
|
364
|
+
final = dialogue + translated_aligned
|
|
365
|
+
|
|
366
|
+
final.save("merged.srt")
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
✔ Automatically prevents overlap
|
|
370
|
+
✔ Keeps indexes clean
|
|
371
|
+
✔ Guarantees invariants
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
# 2️⃣ Normalize AI-Generated Subtitles
|
|
376
|
+
|
|
377
|
+
### 🎯 Problem
|
|
378
|
+
|
|
379
|
+
AI subtitles often contain:
|
|
380
|
+
|
|
381
|
+
* Extra spaces
|
|
382
|
+
* Broken casing
|
|
383
|
+
* Random gaps
|
|
384
|
+
|
|
385
|
+
### 🧠 Clean Pipeline
|
|
386
|
+
|
|
387
|
+
```python
|
|
388
|
+
def clean_text(text: str) -> str:
|
|
389
|
+
return " ".join(text.strip().split()).capitalize()
|
|
390
|
+
|
|
391
|
+
subs = (
|
|
392
|
+
SRTManager.from_file("ai_output.srt")
|
|
393
|
+
.map_content(clean_text)
|
|
394
|
+
.compress_gaps()
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
subs.save("clean_ai_output.srt")
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
✔ Removes weird spacing
|
|
401
|
+
✔ Fixes formatting
|
|
402
|
+
✔ Eliminates timing gaps
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
# 3️⃣ Clip Scene + Retime to New Duration
|
|
407
|
+
|
|
408
|
+
### 🎯 Problem
|
|
409
|
+
|
|
410
|
+
You want only the segment from 60s–120s
|
|
411
|
+
Then stretch it to exactly 30 seconds.
|
|
412
|
+
|
|
413
|
+
```python
|
|
414
|
+
clip = SRTManager.from_file("movie.srt")[60:120]
|
|
415
|
+
|
|
416
|
+
# Scale duration to 30 seconds
|
|
417
|
+
clip.duration = 30
|
|
418
|
+
|
|
419
|
+
clip.save("scene_shortened.srt")
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
✔ Automatically scales proportionally
|
|
423
|
+
✔ Keeps relative timing intact
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
# 4️⃣ Subtitle Chunking (Episode Segmentation)
|
|
428
|
+
|
|
429
|
+
### 🎯 Problem
|
|
430
|
+
|
|
431
|
+
You use `<line>` as delimiter to mark chapter breaks.
|
|
432
|
+
|
|
433
|
+
```python
|
|
434
|
+
subs = SRTManager.from_file("lecture.srt")
|
|
435
|
+
|
|
436
|
+
parts = subs.split("<line>")
|
|
437
|
+
|
|
438
|
+
for i, part in enumerate(parts, 1):
|
|
439
|
+
part.save(f"chapter_{i}.srt")
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
✔ Clean segmentation
|
|
443
|
+
✔ Maintains proper indexing
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
# 5️⃣ Build a Search Engine on Subtitles
|
|
448
|
+
|
|
449
|
+
### 🎯 Problem
|
|
450
|
+
|
|
451
|
+
Extract all dialogue containing a keyword.
|
|
452
|
+
|
|
453
|
+
```python
|
|
454
|
+
subs = SRTManager.from_file("podcast.srt")
|
|
455
|
+
|
|
456
|
+
crypto_mentions = subs["blockchain"]
|
|
457
|
+
|
|
458
|
+
print(crypto_mentions.to_plain_text())
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
✔ Returns only matching subtitles
|
|
462
|
+
✔ Keeps original timestamps
|
|
463
|
+
|
|
464
|
+
---
|
|
465
|
+
|
|
466
|
+
# 6️⃣ Remove Silence but Keep Flow
|
|
467
|
+
|
|
468
|
+
### 🎯 Problem
|
|
469
|
+
|
|
470
|
+
You want subtitles to play continuously without silent gaps.
|
|
471
|
+
|
|
472
|
+
```python
|
|
473
|
+
tight = (
|
|
474
|
+
SRTManager.from_file("documentary.srt")
|
|
475
|
+
.compress_gaps()
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
tight.save("no_silence.srt")
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
Original:
|
|
482
|
+
|
|
483
|
+
```
|
|
484
|
+
00:00:01 → 00:00:03
|
|
485
|
+
00:00:10 → 00:00:12
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
Compressed:
|
|
489
|
+
|
|
490
|
+
```
|
|
491
|
+
00:00:01 → 00:00:03
|
|
492
|
+
00:00:03 → 00:00:05
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
✔ Keeps duration of each subtitle
|
|
496
|
+
✔ Removes dead air
|
|
497
|
+
|
|
498
|
+
---
|
|
499
|
+
|
|
500
|
+
# 7️⃣ Subtitle Stitching (Multiple Episodes → Movie Cut)
|
|
501
|
+
|
|
502
|
+
### 🎯 Problem
|
|
503
|
+
|
|
504
|
+
Combine multiple SRT files into one continuous timeline.
|
|
505
|
+
|
|
506
|
+
```python
|
|
507
|
+
ep1 = SRTManager.from_file("ep1.srt")
|
|
508
|
+
ep2 = SRTManager.from_file("ep2.srt")
|
|
509
|
+
ep3 = SRTManager.from_file("ep3.srt")
|
|
510
|
+
|
|
511
|
+
movie_cut = ep1 + ep2 + ep3
|
|
512
|
+
|
|
513
|
+
movie_cut.save("full_movie.srt")
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
✔ Automatically offsets later files
|
|
517
|
+
✔ No overlap errors
|
|
518
|
+
|
|
519
|
+
---
|
|
520
|
+
|
|
521
|
+
# 8️⃣ Build a Subtitle Analytics Tool
|
|
522
|
+
|
|
523
|
+
### 🎯 Word Frequency Counter
|
|
524
|
+
|
|
525
|
+
```python
|
|
526
|
+
from collections import Counter
|
|
527
|
+
|
|
528
|
+
subs = SRTManager.from_file("debate.srt")
|
|
529
|
+
|
|
530
|
+
words = subs.to_plain_text().lower().split()
|
|
531
|
+
freq = Counter(words)
|
|
532
|
+
|
|
533
|
+
print(freq.most_common(20))
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
✔ Works perfectly because `to_plain_text()` strips formatting
|
|
537
|
+
|
|
538
|
+
---
|
|
539
|
+
|
|
540
|
+
# 9️⃣ Convert Subtitles to Voice Script
|
|
541
|
+
|
|
542
|
+
### 🎯 Create Narration Script
|
|
543
|
+
|
|
544
|
+
```python
|
|
545
|
+
subs = SRTManager.from_file("course.srt")
|
|
546
|
+
|
|
547
|
+
script = subs.join_as_single(sep=" ")
|
|
548
|
+
|
|
549
|
+
with open("script.txt", "w") as f:
|
|
550
|
+
f.write(script.content)
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
✔ Preserves full timeline span
|
|
554
|
+
✔ Merges content cleanly
|
|
555
|
+
|
|
556
|
+
---
|
|
557
|
+
|
|
558
|
+
# 🔟 Real Production Pipeline Example
|
|
559
|
+
|
|
560
|
+
```python
|
|
561
|
+
def production_pipeline(path_in: str, path_out: str):
|
|
562
|
+
subs = (
|
|
563
|
+
SRTManager.from_file(path_in)
|
|
564
|
+
.shift(0.5)
|
|
565
|
+
.map_content(lambda t: t.replace("uh", "").strip())
|
|
566
|
+
.compress_gaps()
|
|
567
|
+
)
|
|
568
|
+
|
|
569
|
+
subs.save(path_out)
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
production_pipeline("raw.srt", "final.srt")
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
✔ Sync correction
|
|
576
|
+
✔ Filler word removal
|
|
577
|
+
✔ Timeline tightening
|
|
578
|
+
✔ Safe save
|
|
579
|
+
|
|
580
|
+
---
|
|
581
|
+
|
|
582
|
+
# ⚙️ Advanced Pattern: Functional Chaining
|
|
583
|
+
|
|
584
|
+
Because transformations return new `SRTManager` instances:
|
|
585
|
+
|
|
586
|
+
```python
|
|
587
|
+
final = (
|
|
588
|
+
SRTManager.from_file("input.srt")
|
|
589
|
+
>> 1.2
|
|
590
|
+
.map_content(str.upper)
|
|
591
|
+
.slice(30, 90)
|
|
592
|
+
.compress_gaps()
|
|
593
|
+
)
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
This enables:
|
|
597
|
+
|
|
598
|
+
* Declarative pipelines
|
|
599
|
+
* Immutable-style transformations
|
|
600
|
+
* Predictable behavior
|
|
601
|
+
|
|
602
|
+
---
|
|
603
|
+
|
|
604
|
+
# 🏗 Design Strength Shown in Advanced Use
|
|
605
|
+
|
|
606
|
+
Your architecture supports:
|
|
607
|
+
|
|
608
|
+
* Deterministic transformations
|
|
609
|
+
* Strong validation
|
|
610
|
+
* Clean composition
|
|
611
|
+
* Safe timeline arithmetic
|
|
612
|
+
* Operator expressiveness
|
|
613
|
+
|