orator-conversion 1.0.8 → 1.0.10
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.
- package/docs/_cover.md +1 -1
- package/docs/_version.json +7 -0
- package/docs/css/docuserve.css +277 -23
- package/docs/index.html +2 -2
- package/docs/retold-catalog.json +13 -1
- package/docs/retold-keyword-index.json +1 -1
- package/package.json +52 -4
- package/source/Conversion-Core.js +110 -0
- package/source/Orator-Conversion-BeaconProvider.js +151 -1
package/docs/_cover.md
CHANGED
package/docs/css/docuserve.css
CHANGED
|
@@ -1,73 +1,327 @@
|
|
|
1
1
|
/* ============================================================================
|
|
2
|
-
Pict Docuserve - Base Styles
|
|
2
|
+
Pict Docuserve - Base Styles & Theme Variables
|
|
3
3
|
============================================================================ */
|
|
4
4
|
|
|
5
|
-
/*
|
|
6
|
-
|
|
5
|
+
/* ----------------------------------------------------------------------------
|
|
6
|
+
Theme variables — light defaults on :root.
|
|
7
|
+
Dark mode applies when either:
|
|
8
|
+
(a) the user explicitly selected dark via <html data-theme="dark">
|
|
9
|
+
(b) the user hasn't chosen anything AND the system prefers dark
|
|
10
|
+
An explicit <html data-theme="light"> pins the light palette regardless.
|
|
11
|
+
---------------------------------------------------------------------------- */
|
|
12
|
+
|
|
13
|
+
:root
|
|
14
|
+
{
|
|
15
|
+
/* Surfaces */
|
|
16
|
+
--docuserve-bg: #FDFBF7;
|
|
17
|
+
--docuserve-bg-elevated: #FFFFFF;
|
|
18
|
+
--docuserve-border: #DDD6CA;
|
|
19
|
+
--docuserve-border-soft: #EAE3D8;
|
|
20
|
+
|
|
21
|
+
/* Text */
|
|
22
|
+
--docuserve-text: #2A241E;
|
|
23
|
+
--docuserve-text-strong: #3D3229;
|
|
24
|
+
--docuserve-text-muted: #5E5549;
|
|
25
|
+
--docuserve-text-dim: #8A7F72;
|
|
26
|
+
|
|
27
|
+
/* Accent / links */
|
|
28
|
+
--docuserve-accent: #2E7D74;
|
|
29
|
+
--docuserve-accent-hover: #236660;
|
|
30
|
+
|
|
31
|
+
/* Top bar */
|
|
32
|
+
--docuserve-topbar-bg: #3D3229;
|
|
33
|
+
--docuserve-topbar-text: #E8E0D4;
|
|
34
|
+
--docuserve-topbar-text-muted: #B5AA9A;
|
|
35
|
+
--docuserve-topbar-text-dim: #8A7F72;
|
|
36
|
+
--docuserve-topbar-hover-bg: #524438;
|
|
37
|
+
--docuserve-topbar-version-bg: rgba(255, 255, 255, 0.06);
|
|
38
|
+
--docuserve-topbar-version-border: rgba(255, 255, 255, 0.08);
|
|
39
|
+
--docuserve-topbar-version-text: #B5AA9A;
|
|
40
|
+
|
|
41
|
+
/* Sidebar */
|
|
42
|
+
--docuserve-sidebar-bg: #FAF7F1;
|
|
43
|
+
--docuserve-sidebar-border: #DDD6CA;
|
|
44
|
+
--docuserve-sidebar-border-soft: #E5DED1;
|
|
45
|
+
--docuserve-sidebar-text: #423D37;
|
|
46
|
+
--docuserve-sidebar-group-title: #3D3229;
|
|
47
|
+
--docuserve-sidebar-module-text: #5E5549;
|
|
48
|
+
--docuserve-sidebar-hover-bg: #EAE3D8;
|
|
49
|
+
--docuserve-sidebar-hover-text: #2E7D74;
|
|
50
|
+
--docuserve-sidebar-active-bg: #E5DED1;
|
|
51
|
+
--docuserve-sidebar-active-text: #2E7D74;
|
|
52
|
+
--docuserve-sidebar-search-bg: #FFFFFF;
|
|
53
|
+
--docuserve-sidebar-search-border: #DDD6CA;
|
|
54
|
+
|
|
55
|
+
/* Inline code */
|
|
56
|
+
--docuserve-inline-code-bg: #F0ECE4;
|
|
57
|
+
--docuserve-inline-code-text: #9E3A50;
|
|
58
|
+
|
|
59
|
+
/* Code block panel */
|
|
60
|
+
--docuserve-code-bg: #F6F3EE;
|
|
61
|
+
--docuserve-code-border: #E5DED1;
|
|
62
|
+
--docuserve-code-gutter-bg: #EFEAE0;
|
|
63
|
+
--docuserve-code-gutter-border: #DDD6CA;
|
|
64
|
+
--docuserve-code-gutter-text: #A59986;
|
|
65
|
+
--docuserve-code-text: #2A241E;
|
|
66
|
+
|
|
67
|
+
/* Syntax tokens — low-chroma dark-on-light palette */
|
|
68
|
+
--docuserve-tok-keyword: #A03472;
|
|
69
|
+
--docuserve-tok-string: #1A6640;
|
|
70
|
+
--docuserve-tok-number: #B25A00;
|
|
71
|
+
--docuserve-tok-comment: #8A7F72;
|
|
72
|
+
--docuserve-tok-operator: #2E7D74;
|
|
73
|
+
--docuserve-tok-punctuation: #2A241E;
|
|
74
|
+
--docuserve-tok-function: #2A5DB0;
|
|
75
|
+
--docuserve-tok-property: #9E3A50;
|
|
76
|
+
--docuserve-tok-tag: #9E3A50;
|
|
77
|
+
--docuserve-tok-attr-name: #B25A00;
|
|
78
|
+
--docuserve-tok-attr-value: #1A6640;
|
|
79
|
+
|
|
80
|
+
/* Tables, blockquotes, mermaid */
|
|
81
|
+
--docuserve-table-header-bg: #F5F0E8;
|
|
82
|
+
--docuserve-table-row-alt-bg: #F9F6F0;
|
|
83
|
+
--docuserve-blockquote-bg: #F7F5F0;
|
|
84
|
+
--docuserve-blockquote-border: #2E7D74;
|
|
85
|
+
--docuserve-blockquote-text: #5E5549;
|
|
86
|
+
--docuserve-mermaid-bg: #FFFFFF;
|
|
87
|
+
|
|
88
|
+
/* Scrollbars */
|
|
89
|
+
--docuserve-scrollbar-track: #F5F0E8;
|
|
90
|
+
--docuserve-scrollbar-thumb: #D4CCBE;
|
|
91
|
+
--docuserve-scrollbar-thumb-hover: #B5AA9A;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@media (prefers-color-scheme: dark)
|
|
95
|
+
{
|
|
96
|
+
:root:not([data-theme="light"])
|
|
97
|
+
{
|
|
98
|
+
--docuserve-bg: #15120F;
|
|
99
|
+
--docuserve-bg-elevated: #1B1814;
|
|
100
|
+
--docuserve-border: #2F2823;
|
|
101
|
+
--docuserve-border-soft: #26211C;
|
|
102
|
+
|
|
103
|
+
--docuserve-text: #E8E0D4;
|
|
104
|
+
--docuserve-text-strong: #F2ECE0;
|
|
105
|
+
--docuserve-text-muted: #B5AA9A;
|
|
106
|
+
--docuserve-text-dim: #7A6F62;
|
|
107
|
+
|
|
108
|
+
--docuserve-accent: #5DB8A8;
|
|
109
|
+
--docuserve-accent-hover: #7FCCB8;
|
|
110
|
+
|
|
111
|
+
--docuserve-topbar-bg: #1A1612;
|
|
112
|
+
--docuserve-topbar-text: #E8E0D4;
|
|
113
|
+
--docuserve-topbar-text-muted: #B5AA9A;
|
|
114
|
+
--docuserve-topbar-text-dim: #7A6F62;
|
|
115
|
+
--docuserve-topbar-hover-bg: #2A241E;
|
|
116
|
+
--docuserve-topbar-version-bg: rgba(255, 255, 255, 0.05);
|
|
117
|
+
--docuserve-topbar-version-border: rgba(255, 255, 255, 0.09);
|
|
118
|
+
--docuserve-topbar-version-text: #B5AA9A;
|
|
119
|
+
|
|
120
|
+
--docuserve-sidebar-bg: #1B1814;
|
|
121
|
+
--docuserve-sidebar-border: #2F2823;
|
|
122
|
+
--docuserve-sidebar-border-soft: #26211C;
|
|
123
|
+
--docuserve-sidebar-text: #C9C0B3;
|
|
124
|
+
--docuserve-sidebar-group-title: #F2ECE0;
|
|
125
|
+
--docuserve-sidebar-module-text: #B5AA9A;
|
|
126
|
+
--docuserve-sidebar-hover-bg: #2A241E;
|
|
127
|
+
--docuserve-sidebar-hover-text: #7FCCB8;
|
|
128
|
+
--docuserve-sidebar-active-bg: #2F2823;
|
|
129
|
+
--docuserve-sidebar-active-text: #7FCCB8;
|
|
130
|
+
--docuserve-sidebar-search-bg: #26211C;
|
|
131
|
+
--docuserve-sidebar-search-border: #2F2823;
|
|
132
|
+
|
|
133
|
+
--docuserve-inline-code-bg: #2A241E;
|
|
134
|
+
--docuserve-inline-code-text: #E8B07A;
|
|
135
|
+
|
|
136
|
+
--docuserve-code-bg: #1E1A17;
|
|
137
|
+
--docuserve-code-border: #2F2823;
|
|
138
|
+
--docuserve-code-gutter-bg: #17130F;
|
|
139
|
+
--docuserve-code-gutter-border: #2F2823;
|
|
140
|
+
--docuserve-code-gutter-text: #6A6052;
|
|
141
|
+
--docuserve-code-text: #E8E0D4;
|
|
142
|
+
|
|
143
|
+
--docuserve-tok-keyword: #C678DD;
|
|
144
|
+
--docuserve-tok-string: #98C379;
|
|
145
|
+
--docuserve-tok-number: #D19A66;
|
|
146
|
+
--docuserve-tok-comment: #7F848E;
|
|
147
|
+
--docuserve-tok-operator: #56B6C2;
|
|
148
|
+
--docuserve-tok-punctuation: #E8E0D4;
|
|
149
|
+
--docuserve-tok-function: #61AFEF;
|
|
150
|
+
--docuserve-tok-property: #E06C75;
|
|
151
|
+
--docuserve-tok-tag: #E06C75;
|
|
152
|
+
--docuserve-tok-attr-name: #D19A66;
|
|
153
|
+
--docuserve-tok-attr-value: #98C379;
|
|
154
|
+
|
|
155
|
+
--docuserve-table-header-bg: #26211C;
|
|
156
|
+
--docuserve-table-row-alt-bg: #1F1B17;
|
|
157
|
+
--docuserve-blockquote-bg: #1F1B17;
|
|
158
|
+
--docuserve-blockquote-border: #5DB8A8;
|
|
159
|
+
--docuserve-blockquote-text: #C9C0B3;
|
|
160
|
+
--docuserve-mermaid-bg: #E8E0D4;
|
|
161
|
+
|
|
162
|
+
--docuserve-scrollbar-track: #1B1814;
|
|
163
|
+
--docuserve-scrollbar-thumb: #3A322B;
|
|
164
|
+
--docuserve-scrollbar-thumb-hover: #524438;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
:root[data-theme="dark"]
|
|
169
|
+
{
|
|
170
|
+
--docuserve-bg: #15120F;
|
|
171
|
+
--docuserve-bg-elevated: #1B1814;
|
|
172
|
+
--docuserve-border: #2F2823;
|
|
173
|
+
--docuserve-border-soft: #26211C;
|
|
174
|
+
|
|
175
|
+
--docuserve-text: #E8E0D4;
|
|
176
|
+
--docuserve-text-strong: #F2ECE0;
|
|
177
|
+
--docuserve-text-muted: #B5AA9A;
|
|
178
|
+
--docuserve-text-dim: #7A6F62;
|
|
179
|
+
|
|
180
|
+
--docuserve-accent: #5DB8A8;
|
|
181
|
+
--docuserve-accent-hover: #7FCCB8;
|
|
182
|
+
|
|
183
|
+
--docuserve-topbar-bg: #1A1612;
|
|
184
|
+
--docuserve-topbar-text: #E8E0D4;
|
|
185
|
+
--docuserve-topbar-text-muted: #B5AA9A;
|
|
186
|
+
--docuserve-topbar-text-dim: #7A6F62;
|
|
187
|
+
--docuserve-topbar-hover-bg: #2A241E;
|
|
188
|
+
--docuserve-topbar-version-bg: rgba(255, 255, 255, 0.05);
|
|
189
|
+
--docuserve-topbar-version-border: rgba(255, 255, 255, 0.09);
|
|
190
|
+
--docuserve-topbar-version-text: #B5AA9A;
|
|
191
|
+
|
|
192
|
+
--docuserve-sidebar-bg: #1B1814;
|
|
193
|
+
--docuserve-sidebar-border: #2F2823;
|
|
194
|
+
--docuserve-sidebar-border-soft: #26211C;
|
|
195
|
+
--docuserve-sidebar-text: #C9C0B3;
|
|
196
|
+
--docuserve-sidebar-group-title: #F2ECE0;
|
|
197
|
+
--docuserve-sidebar-module-text: #B5AA9A;
|
|
198
|
+
--docuserve-sidebar-hover-bg: #2A241E;
|
|
199
|
+
--docuserve-sidebar-hover-text: #7FCCB8;
|
|
200
|
+
--docuserve-sidebar-active-bg: #2F2823;
|
|
201
|
+
--docuserve-sidebar-active-text: #7FCCB8;
|
|
202
|
+
--docuserve-sidebar-search-bg: #26211C;
|
|
203
|
+
--docuserve-sidebar-search-border: #2F2823;
|
|
204
|
+
|
|
205
|
+
--docuserve-inline-code-bg: #2A241E;
|
|
206
|
+
--docuserve-inline-code-text: #E8B07A;
|
|
207
|
+
|
|
208
|
+
--docuserve-code-bg: #1E1A17;
|
|
209
|
+
--docuserve-code-border: #2F2823;
|
|
210
|
+
--docuserve-code-gutter-bg: #17130F;
|
|
211
|
+
--docuserve-code-gutter-border: #2F2823;
|
|
212
|
+
--docuserve-code-gutter-text: #6A6052;
|
|
213
|
+
--docuserve-code-text: #E8E0D4;
|
|
214
|
+
|
|
215
|
+
--docuserve-tok-keyword: #C678DD;
|
|
216
|
+
--docuserve-tok-string: #98C379;
|
|
217
|
+
--docuserve-tok-number: #D19A66;
|
|
218
|
+
--docuserve-tok-comment: #7F848E;
|
|
219
|
+
--docuserve-tok-operator: #56B6C2;
|
|
220
|
+
--docuserve-tok-punctuation: #E8E0D4;
|
|
221
|
+
--docuserve-tok-function: #61AFEF;
|
|
222
|
+
--docuserve-tok-property: #E06C75;
|
|
223
|
+
--docuserve-tok-tag: #E06C75;
|
|
224
|
+
--docuserve-tok-attr-name: #D19A66;
|
|
225
|
+
--docuserve-tok-attr-value: #98C379;
|
|
226
|
+
|
|
227
|
+
--docuserve-table-header-bg: #26211C;
|
|
228
|
+
--docuserve-table-row-alt-bg: #1F1B17;
|
|
229
|
+
--docuserve-blockquote-bg: #1F1B17;
|
|
230
|
+
--docuserve-blockquote-border: #5DB8A8;
|
|
231
|
+
--docuserve-blockquote-text: #C9C0B3;
|
|
232
|
+
--docuserve-mermaid-bg: #E8E0D4;
|
|
233
|
+
|
|
234
|
+
--docuserve-scrollbar-track: #1B1814;
|
|
235
|
+
--docuserve-scrollbar-thumb: #3A322B;
|
|
236
|
+
--docuserve-scrollbar-thumb-hover: #524438;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/* ----------------------------------------------------------------------------
|
|
240
|
+
Reset and base
|
|
241
|
+
---------------------------------------------------------------------------- */
|
|
242
|
+
|
|
243
|
+
*, *::before, *::after
|
|
244
|
+
{
|
|
7
245
|
box-sizing: border-box;
|
|
8
246
|
}
|
|
9
247
|
|
|
10
|
-
html, body
|
|
248
|
+
html, body
|
|
249
|
+
{
|
|
11
250
|
margin: 0;
|
|
12
251
|
padding: 0;
|
|
13
252
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
14
253
|
font-size: 16px;
|
|
15
254
|
line-height: 1.5;
|
|
16
|
-
color:
|
|
17
|
-
background-color:
|
|
255
|
+
color: var(--docuserve-text);
|
|
256
|
+
background-color: var(--docuserve-bg);
|
|
18
257
|
-webkit-font-smoothing: antialiased;
|
|
19
258
|
-moz-osx-font-smoothing: grayscale;
|
|
259
|
+
transition: background-color 0.15s ease, color 0.15s ease;
|
|
20
260
|
}
|
|
21
261
|
|
|
22
262
|
/* Typography */
|
|
23
|
-
h1, h2, h3, h4, h5, h6
|
|
263
|
+
h1, h2, h3, h4, h5, h6
|
|
264
|
+
{
|
|
24
265
|
margin-top: 0;
|
|
25
266
|
line-height: 1.3;
|
|
267
|
+
color: var(--docuserve-text-strong);
|
|
26
268
|
}
|
|
27
269
|
|
|
28
|
-
a
|
|
29
|
-
|
|
270
|
+
a
|
|
271
|
+
{
|
|
272
|
+
color: var(--docuserve-accent);
|
|
30
273
|
text-decoration: none;
|
|
31
274
|
}
|
|
32
275
|
|
|
33
|
-
a:hover
|
|
34
|
-
|
|
276
|
+
a:hover
|
|
277
|
+
{
|
|
278
|
+
color: var(--docuserve-accent-hover);
|
|
35
279
|
}
|
|
36
280
|
|
|
37
281
|
/* Application container */
|
|
38
|
-
#Docuserve-Application-Container
|
|
282
|
+
#Docuserve-Application-Container
|
|
283
|
+
{
|
|
39
284
|
min-height: 100vh;
|
|
40
285
|
}
|
|
41
286
|
|
|
42
287
|
/* Utility: scrollbar styling */
|
|
43
|
-
::-webkit-scrollbar
|
|
288
|
+
::-webkit-scrollbar
|
|
289
|
+
{
|
|
44
290
|
width: 8px;
|
|
291
|
+
height: 8px;
|
|
45
292
|
}
|
|
46
293
|
|
|
47
|
-
::-webkit-scrollbar-track
|
|
48
|
-
|
|
294
|
+
::-webkit-scrollbar-track
|
|
295
|
+
{
|
|
296
|
+
background: var(--docuserve-scrollbar-track);
|
|
49
297
|
}
|
|
50
298
|
|
|
51
|
-
::-webkit-scrollbar-thumb
|
|
52
|
-
|
|
299
|
+
::-webkit-scrollbar-thumb
|
|
300
|
+
{
|
|
301
|
+
background: var(--docuserve-scrollbar-thumb);
|
|
53
302
|
border-radius: 4px;
|
|
54
303
|
}
|
|
55
304
|
|
|
56
|
-
::-webkit-scrollbar-thumb:hover
|
|
57
|
-
|
|
305
|
+
::-webkit-scrollbar-thumb:hover
|
|
306
|
+
{
|
|
307
|
+
background: var(--docuserve-scrollbar-thumb-hover);
|
|
58
308
|
}
|
|
59
309
|
|
|
60
310
|
/* Responsive adjustments */
|
|
61
|
-
@media (max-width: 768px)
|
|
62
|
-
|
|
311
|
+
@media (max-width: 768px)
|
|
312
|
+
{
|
|
313
|
+
html
|
|
314
|
+
{
|
|
63
315
|
font-size: 14px;
|
|
64
316
|
}
|
|
65
317
|
|
|
66
|
-
#Docuserve-Sidebar-Container
|
|
318
|
+
#Docuserve-Sidebar-Container
|
|
319
|
+
{
|
|
67
320
|
display: none;
|
|
68
321
|
}
|
|
69
322
|
|
|
70
|
-
.docuserve-body
|
|
323
|
+
.docuserve-body
|
|
324
|
+
{
|
|
71
325
|
flex-direction: column;
|
|
72
326
|
}
|
|
73
327
|
}
|
package/docs/index.html
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
|
7
|
-
<meta name="description" content="Documentation
|
|
7
|
+
<meta name="description" content="Orator File Translation v1.0.9 Documentation — File format conversion endpoints for Orator service servers.">
|
|
8
8
|
|
|
9
|
-
<title>Documentation</title>
|
|
9
|
+
<title>Orator File Translation v1.0.9 Documentation</title>
|
|
10
10
|
|
|
11
11
|
<!-- Application Stylesheet -->
|
|
12
12
|
<link href="css/docuserve.css" rel="stylesheet">
|
package/docs/retold-catalog.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"Generated": "2026-
|
|
2
|
+
"Generated": "2026-04-10T17:22:48.550Z",
|
|
3
3
|
"GitHubOrg": "stevenvelozo",
|
|
4
4
|
"DefaultBranch": "master",
|
|
5
5
|
"Groups": [
|
|
@@ -25,6 +25,18 @@
|
|
|
25
25
|
"Key": "docs",
|
|
26
26
|
"Description": "",
|
|
27
27
|
"Modules": [
|
|
28
|
+
{
|
|
29
|
+
"Name": "css",
|
|
30
|
+
"Repo": "css",
|
|
31
|
+
"Group": "docs",
|
|
32
|
+
"Branch": "master",
|
|
33
|
+
"HasDocs": true,
|
|
34
|
+
"HasCover": false,
|
|
35
|
+
"Sidebar": [],
|
|
36
|
+
"DocFiles": [
|
|
37
|
+
"css/docuserve.css"
|
|
38
|
+
]
|
|
39
|
+
},
|
|
28
40
|
{
|
|
29
41
|
"Name": "endpoints",
|
|
30
42
|
"Repo": "endpoints",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orator-conversion",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"description": "File format conversion endpoints for Orator service servers.",
|
|
5
5
|
"main": "source/Orator-File-Translation.js",
|
|
6
6
|
"scripts": {
|
|
@@ -40,16 +40,64 @@
|
|
|
40
40
|
"url": "https://github.com/stevenvelozo/orator-conversion/issues"
|
|
41
41
|
},
|
|
42
42
|
"homepage": "https://github.com/stevenvelozo/orator-conversion#readme",
|
|
43
|
+
"retoldBeacon": {
|
|
44
|
+
"displayName": "Orator Media Conversion",
|
|
45
|
+
"description": "Image/PDF/video/audio conversion actions exposed as an ultravisor-beacon capability.",
|
|
46
|
+
"category": "media",
|
|
47
|
+
"mode": "capability-provider",
|
|
48
|
+
"providerPath": "./source/Orator-Conversion-BeaconProvider.js",
|
|
49
|
+
"capability": "MediaConversion",
|
|
50
|
+
"healthCheck": {
|
|
51
|
+
"path": "/"
|
|
52
|
+
},
|
|
53
|
+
"defaultPort": 54500,
|
|
54
|
+
"requiresUltravisor": true,
|
|
55
|
+
"configForm": {
|
|
56
|
+
"Fields": [
|
|
57
|
+
{
|
|
58
|
+
"Name": "PdftkPath",
|
|
59
|
+
"Label": "pdftk binary",
|
|
60
|
+
"Type": "text",
|
|
61
|
+
"Default": "pdftk"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"Name": "PdftoppmPath",
|
|
65
|
+
"Label": "pdftoppm binary",
|
|
66
|
+
"Type": "text",
|
|
67
|
+
"Default": "pdftoppm"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"Name": "FfmpegPath",
|
|
71
|
+
"Label": "ffmpeg binary",
|
|
72
|
+
"Type": "text",
|
|
73
|
+
"Default": "ffmpeg"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"Name": "FfprobePath",
|
|
77
|
+
"Label": "ffprobe binary",
|
|
78
|
+
"Type": "text",
|
|
79
|
+
"Default": "ffprobe"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"Name": "MaxFileSizeBytes",
|
|
83
|
+
"Label": "Max input file size",
|
|
84
|
+
"Type": "number",
|
|
85
|
+
"Default": 104857600
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
},
|
|
43
90
|
"dependencies": {
|
|
44
91
|
"fable-serviceproviderbase": "^3.0.19",
|
|
45
92
|
"sharp": "^0.34.5",
|
|
46
|
-
"ultravisor-beacon": "^0.0.
|
|
93
|
+
"ultravisor-beacon": "^0.0.9",
|
|
47
94
|
"ws": "^8.20.0"
|
|
48
95
|
},
|
|
49
96
|
"devDependencies": {
|
|
50
97
|
"fable": "^3.1.67",
|
|
51
98
|
"orator": "^6.0.4",
|
|
52
|
-
"orator-serviceserver-restify": "^2.0.
|
|
53
|
-
"
|
|
99
|
+
"orator-serviceserver-restify": "^2.0.10",
|
|
100
|
+
"pict-docuserve": "^0.1.5",
|
|
101
|
+
"quackage": "^1.1.0"
|
|
54
102
|
}
|
|
55
103
|
}
|
|
@@ -767,6 +767,116 @@ class ConversionCore
|
|
|
767
767
|
});
|
|
768
768
|
}
|
|
769
769
|
|
|
770
|
+
/**
|
|
771
|
+
* Extract MULTIPLE frames from a video file in a single work item.
|
|
772
|
+
*
|
|
773
|
+
* This is the batch counterpart to videoExtractFrame. It exists so the
|
|
774
|
+
* dispatcher can request all N frames the video explorer wants in one
|
|
775
|
+
* trip — instead of triggering an entire operation graph 20 times — which
|
|
776
|
+
* means one address resolve, one file-transfer / shared-fs check, one
|
|
777
|
+
* probe (optional), and one ffmpeg invocation per frame all served from
|
|
778
|
+
* the same on-disk file.
|
|
779
|
+
*
|
|
780
|
+
* Each frame extraction is a separate ffmpeg call internally because
|
|
781
|
+
* ffmpeg's `-ss` before `-i` is fast (uses container index), and chaining
|
|
782
|
+
* `select=eq(t,X)+eq(t,Y)+...` filters is brittle and produces wrong-sized
|
|
783
|
+
* outputs for variable-bitrate streams. Sequential single-frame extracts
|
|
784
|
+
* give us the same correctness as videoExtractFrame, just amortized.
|
|
785
|
+
*
|
|
786
|
+
* @param {string} pInputPath - Path to the input video file.
|
|
787
|
+
* @param {Array} pFrameSpecs - Frame specs:
|
|
788
|
+
* [
|
|
789
|
+
* { Timestamp: '00:00:05.000', OutputPath: '/abs/path/frame_0000.jpg' },
|
|
790
|
+
* { Timestamp: '00:00:10.000', OutputPath: '/abs/path/frame_0001.jpg' },
|
|
791
|
+
* ...
|
|
792
|
+
* ]
|
|
793
|
+
* @param {object} pOptions - Shared extract options applied to every frame:
|
|
794
|
+
* { Width, Height }
|
|
795
|
+
* @param {Function} fCallback - Called with (pError, pResult) where
|
|
796
|
+
* pResult is { Frames: [{ Index, Timestamp, OutputPath, Size, Success, Error? }, ...] }
|
|
797
|
+
*/
|
|
798
|
+
videoExtractFramesBatch(pInputPath, pFrameSpecs, pOptions, fCallback)
|
|
799
|
+
{
|
|
800
|
+
let tmpSelf = this;
|
|
801
|
+
|
|
802
|
+
if (!Array.isArray(pFrameSpecs) || pFrameSpecs.length === 0)
|
|
803
|
+
{
|
|
804
|
+
return fCallback(new Error('videoExtractFramesBatch: Frames array is required and must be non-empty.'));
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// Quick existence check on the input video — fail fast rather than per-frame
|
|
808
|
+
if (!libFS.existsSync(pInputPath))
|
|
809
|
+
{
|
|
810
|
+
return fCallback(new Error(`videoExtractFramesBatch: input video not found: ${pInputPath}`));
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
let tmpResults = [];
|
|
814
|
+
let tmpIndex = 0;
|
|
815
|
+
|
|
816
|
+
let _extractNext = () =>
|
|
817
|
+
{
|
|
818
|
+
if (tmpIndex >= pFrameSpecs.length)
|
|
819
|
+
{
|
|
820
|
+
return fCallback(null, { Frames: tmpResults });
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
let tmpI = tmpIndex;
|
|
824
|
+
let tmpSpec = pFrameSpecs[tmpI];
|
|
825
|
+
tmpIndex++;
|
|
826
|
+
|
|
827
|
+
if (!tmpSpec || !tmpSpec.OutputPath || !tmpSpec.Timestamp)
|
|
828
|
+
{
|
|
829
|
+
tmpResults.push(
|
|
830
|
+
{
|
|
831
|
+
Index: tmpI,
|
|
832
|
+
Timestamp: tmpSpec ? tmpSpec.Timestamp : null,
|
|
833
|
+
OutputPath: tmpSpec ? tmpSpec.OutputPath : null,
|
|
834
|
+
Size: 0,
|
|
835
|
+
Success: false,
|
|
836
|
+
Error: 'Missing Timestamp or OutputPath'
|
|
837
|
+
});
|
|
838
|
+
return _extractNext();
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
tmpSelf.videoExtractFrame(pInputPath, tmpSpec.OutputPath,
|
|
842
|
+
{
|
|
843
|
+
Timestamp: tmpSpec.Timestamp,
|
|
844
|
+
Width: pOptions ? pOptions.Width : undefined,
|
|
845
|
+
Height: pOptions ? pOptions.Height : undefined
|
|
846
|
+
},
|
|
847
|
+
(pError, pResultPath) =>
|
|
848
|
+
{
|
|
849
|
+
if (pError)
|
|
850
|
+
{
|
|
851
|
+
tmpResults.push(
|
|
852
|
+
{
|
|
853
|
+
Index: tmpI,
|
|
854
|
+
Timestamp: tmpSpec.Timestamp,
|
|
855
|
+
OutputPath: tmpSpec.OutputPath,
|
|
856
|
+
Size: 0,
|
|
857
|
+
Success: false,
|
|
858
|
+
Error: pError.message
|
|
859
|
+
});
|
|
860
|
+
return _extractNext();
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
let tmpSize = 0;
|
|
864
|
+
try { tmpSize = libFS.statSync(pResultPath).size; } catch (pIgnore) { /* ignore */ }
|
|
865
|
+
tmpResults.push(
|
|
866
|
+
{
|
|
867
|
+
Index: tmpI,
|
|
868
|
+
Timestamp: tmpSpec.Timestamp,
|
|
869
|
+
OutputPath: pResultPath,
|
|
870
|
+
Size: tmpSize,
|
|
871
|
+
Success: true
|
|
872
|
+
});
|
|
873
|
+
return _extractNext();
|
|
874
|
+
});
|
|
875
|
+
};
|
|
876
|
+
|
|
877
|
+
_extractNext();
|
|
878
|
+
}
|
|
879
|
+
|
|
770
880
|
/**
|
|
771
881
|
* Generate a thumbnail from a video file.
|
|
772
882
|
*
|
|
@@ -192,6 +192,18 @@ class OratorConversionBeaconProvider extends libBeaconCapabilityProvider
|
|
|
192
192
|
{ Name: 'Height', DataType: 'Number', Required: false, Description: 'Scale height (width auto)' }
|
|
193
193
|
]
|
|
194
194
|
},
|
|
195
|
+
'VideoExtractFrames':
|
|
196
|
+
{
|
|
197
|
+
Description: 'Extract MULTIPLE frames from a video file in a single work item. Each frame spec includes a timestamp and an output filename. All frames are written to the same OutputDir, which must be writable from this beacon (typically via shared filesystem with the dispatcher). Returns a JSON manifest with per-frame results.',
|
|
198
|
+
SettingsSchema:
|
|
199
|
+
[
|
|
200
|
+
{ Name: 'InputFile', DataType: 'String', Required: true, Description: 'Path to input video file' },
|
|
201
|
+
{ Name: 'OutputDir', DataType: 'String', Required: true, Description: 'Absolute directory where frames should be written. Must be writable from this beacon.' },
|
|
202
|
+
{ Name: 'Frames', DataType: 'String', Required: true, Description: 'JSON-encoded array of {Timestamp, Filename} pairs, e.g. [{"Timestamp":"00:00:05","Filename":"frame_0000.jpg"}, ...]' },
|
|
203
|
+
{ Name: 'Width', DataType: 'Number', Required: false, Description: 'Scale width applied to every frame' },
|
|
204
|
+
{ Name: 'Height', DataType: 'Number', Required: false, Description: 'Scale height applied to every frame' }
|
|
205
|
+
]
|
|
206
|
+
},
|
|
195
207
|
'VideoThumbnail':
|
|
196
208
|
{
|
|
197
209
|
Description: 'Generate a thumbnail from a video file.',
|
|
@@ -325,7 +337,7 @@ class OratorConversionBeaconProvider extends libBeaconCapabilityProvider
|
|
|
325
337
|
tmpLog.info(`[OratorConversion] Input file OK: ${tmpInputPath} (${libFS.statSync(tmpInputPath).size} bytes)`);
|
|
326
338
|
|
|
327
339
|
// File-path actions skip the buffer read — Sharp and ffmpeg handle files directly
|
|
328
|
-
let tmpFilePathActions = { 'ImageResize': true, 'ImageConvert': true, 'MediaProbe': true, 'VideoExtractFrame': true, 'VideoThumbnail': true, 'AudioExtractSegment': true, 'AudioWaveform': true };
|
|
340
|
+
let tmpFilePathActions = { 'ImageResize': true, 'ImageConvert': true, 'MediaProbe': true, 'VideoExtractFrame': true, 'VideoExtractFrames': true, 'VideoThumbnail': true, 'AudioExtractSegment': true, 'AudioWaveform': true };
|
|
329
341
|
if (tmpFilePathActions[pAction])
|
|
330
342
|
{
|
|
331
343
|
return this._executeFilePathAction(pAction, tmpSettings, tmpInputPath, tmpOutputPath, fCallback, fReportProgress);
|
|
@@ -605,6 +617,144 @@ class OratorConversionBeaconProvider extends libBeaconCapabilityProvider
|
|
|
605
617
|
break;
|
|
606
618
|
}
|
|
607
619
|
|
|
620
|
+
case 'VideoExtractFrames':
|
|
621
|
+
{
|
|
622
|
+
// Batch frame extraction. Reads InputFile once, extracts N frames
|
|
623
|
+
// to OutputDir, returns a JSON manifest. Used by the video explorer
|
|
624
|
+
// to avoid 20 separate work-item dispatches per video.
|
|
625
|
+
if (!this._FfmpegAvailable)
|
|
626
|
+
{
|
|
627
|
+
return fCallback(null, {
|
|
628
|
+
Outputs: { StdOut: 'ffmpeg not available on this beacon.', ExitCode: -1, Result: '' },
|
|
629
|
+
Log: ['OratorConversion: ffmpeg required for VideoExtractFrames but not found.']
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
let tmpOutputDir = pSettings.OutputDir;
|
|
634
|
+
if (!tmpOutputDir)
|
|
635
|
+
{
|
|
636
|
+
return fCallback(null, {
|
|
637
|
+
Outputs: { StdOut: 'No OutputDir specified.', ExitCode: -1, Result: '' },
|
|
638
|
+
Log: ['OratorConversion VideoExtractFrames: OutputDir is required.']
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
let tmpFrameSpecs;
|
|
643
|
+
try
|
|
644
|
+
{
|
|
645
|
+
tmpFrameSpecs = JSON.parse(pSettings.Frames || '[]');
|
|
646
|
+
}
|
|
647
|
+
catch (pParseError)
|
|
648
|
+
{
|
|
649
|
+
return fCallback(null, {
|
|
650
|
+
Outputs: { StdOut: `Invalid Frames JSON: ${pParseError.message}`, ExitCode: -1, Result: '' },
|
|
651
|
+
Log: [`OratorConversion VideoExtractFrames: failed to parse Frames JSON: ${pParseError.message}`]
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
if (!Array.isArray(tmpFrameSpecs) || tmpFrameSpecs.length === 0)
|
|
656
|
+
{
|
|
657
|
+
return fCallback(null, {
|
|
658
|
+
Outputs: { StdOut: 'Frames array is empty.', ExitCode: -1, Result: '' },
|
|
659
|
+
Log: ['OratorConversion VideoExtractFrames: Frames must be a non-empty array.']
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Ensure OutputDir exists. With shared-fs the dispatcher already
|
|
664
|
+
// created it; mkdir -p is a no-op in that case but needed when
|
|
665
|
+
// the dispatcher is on a different host.
|
|
666
|
+
try
|
|
667
|
+
{
|
|
668
|
+
libFS.mkdirSync(tmpOutputDir, { recursive: true });
|
|
669
|
+
}
|
|
670
|
+
catch (pMkdirError)
|
|
671
|
+
{
|
|
672
|
+
return fCallback(null, {
|
|
673
|
+
Outputs: { StdOut: `Could not create OutputDir: ${pMkdirError.message}`, ExitCode: -1, Result: '' },
|
|
674
|
+
Log: [`OratorConversion VideoExtractFrames: mkdir failed for ${tmpOutputDir}: ${pMkdirError.message}`]
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Resolve absolute output paths from { Timestamp, Filename } pairs
|
|
679
|
+
let tmpResolvedSpecs = [];
|
|
680
|
+
for (let i = 0; i < tmpFrameSpecs.length; i++)
|
|
681
|
+
{
|
|
682
|
+
let tmpEntry = tmpFrameSpecs[i];
|
|
683
|
+
if (!tmpEntry || !tmpEntry.Timestamp || !tmpEntry.Filename)
|
|
684
|
+
{
|
|
685
|
+
tmpResolvedSpecs.push(tmpEntry);
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
// Reject filenames with path separators or traversal — the
|
|
689
|
+
// caller is meant to give us flat names like frame_0000.jpg
|
|
690
|
+
if (tmpEntry.Filename.indexOf('/') !== -1 ||
|
|
691
|
+
tmpEntry.Filename.indexOf('\\') !== -1 ||
|
|
692
|
+
tmpEntry.Filename.indexOf('..') !== -1)
|
|
693
|
+
{
|
|
694
|
+
return fCallback(null, {
|
|
695
|
+
Outputs: { StdOut: `Invalid filename in Frames: ${tmpEntry.Filename}`, ExitCode: -1, Result: '' },
|
|
696
|
+
Log: [`OratorConversion VideoExtractFrames: refusing filename with path separators: ${tmpEntry.Filename}`]
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
tmpResolvedSpecs.push(
|
|
700
|
+
{
|
|
701
|
+
Timestamp: tmpEntry.Timestamp,
|
|
702
|
+
OutputPath: libPath.join(tmpOutputDir, tmpEntry.Filename)
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
if (fReportProgress) fReportProgress({ Percent: 5, Message: `Extracting ${tmpResolvedSpecs.length} frames...` });
|
|
707
|
+
|
|
708
|
+
this._Core.videoExtractFramesBatch(pInputPath, tmpResolvedSpecs,
|
|
709
|
+
{
|
|
710
|
+
Width: pSettings.Width,
|
|
711
|
+
Height: pSettings.Height
|
|
712
|
+
},
|
|
713
|
+
(pError, pBatchResult) =>
|
|
714
|
+
{
|
|
715
|
+
if (pError)
|
|
716
|
+
{
|
|
717
|
+
return fCallback(null, {
|
|
718
|
+
Outputs: { StdOut: `Batch frame extraction failed: ${pError.message}`, ExitCode: 1, Result: '' },
|
|
719
|
+
Log: [`OratorConversion VideoExtractFrames error: ${pError.message}`]
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// Re-attach the original Filename field to each result so
|
|
724
|
+
// the dispatcher can correlate against its own naming.
|
|
725
|
+
let tmpFrames = pBatchResult.Frames || [];
|
|
726
|
+
for (let i = 0; i < tmpFrames.length; i++)
|
|
727
|
+
{
|
|
728
|
+
let tmpOriginal = tmpFrameSpecs[i];
|
|
729
|
+
if (tmpOriginal && tmpOriginal.Filename)
|
|
730
|
+
{
|
|
731
|
+
tmpFrames[i].Filename = tmpOriginal.Filename;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
let tmpSuccessCount = tmpFrames.filter((f) => f.Success).length;
|
|
736
|
+
let tmpManifest =
|
|
737
|
+
{
|
|
738
|
+
FrameCount: tmpFrames.length,
|
|
739
|
+
SuccessCount: tmpSuccessCount,
|
|
740
|
+
OutputDir: tmpOutputDir,
|
|
741
|
+
Frames: tmpFrames
|
|
742
|
+
};
|
|
743
|
+
|
|
744
|
+
return fCallback(null, {
|
|
745
|
+
Outputs:
|
|
746
|
+
{
|
|
747
|
+
StdOut: `Extracted ${tmpSuccessCount}/${tmpFrames.length} frames from ${pSettings.InputFile} → ${tmpOutputDir}`,
|
|
748
|
+
ExitCode: tmpSuccessCount === tmpFrames.length ? 0 : 1,
|
|
749
|
+
Result: JSON.stringify(tmpManifest),
|
|
750
|
+
ContentType: 'application/json'
|
|
751
|
+
},
|
|
752
|
+
Log: [`OratorConversion VideoExtractFrames: ${tmpSuccessCount}/${tmpFrames.length} frames written to ${tmpOutputDir}`]
|
|
753
|
+
});
|
|
754
|
+
});
|
|
755
|
+
break;
|
|
756
|
+
}
|
|
757
|
+
|
|
608
758
|
case 'VideoThumbnail':
|
|
609
759
|
{
|
|
610
760
|
if (!this._FfmpegAvailable)
|