disaster-broadcast-studio 0.1.1 → 0.3.0

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 (69) hide show
  1. package/README.md +19 -7
  2. package/assets/README.md +1 -1
  3. package/assets/audio/README.md +16 -4
  4. package/assets/audio/alert-critical.mjs +4 -0
  5. package/assets/audio/alert-critical.wav +0 -0
  6. package/assets/audio/alert-critical.wav.json +15 -0
  7. package/assets/audio/alert-urgent.mjs +4 -0
  8. package/assets/audio/alert-urgent.wav +0 -0
  9. package/assets/audio/alert-urgent.wav.json +15 -0
  10. package/assets/audio/bulletin.mjs +9 -0
  11. package/assets/audio/bulletin.wav +0 -0
  12. package/assets/audio/bulletin.wav.json +15 -0
  13. package/assets/audio/news.mjs +4 -0
  14. package/assets/audio/news.wav +0 -0
  15. package/assets/audio/news.wav.json +15 -0
  16. package/assets/audio/notification.mjs +24 -9
  17. package/assets/audio/notification.wav +0 -0
  18. package/assets/audio/notification.wav.json +15 -0
  19. package/assets/audio/status.mjs +5 -0
  20. package/assets/audio/status.wav +0 -0
  21. package/assets/audio/status.wav.json +15 -0
  22. package/assets/audio/tones.mjs +17 -0
  23. package/assets/workspace/README.md +3 -0
  24. package/dist/cli.js +20 -1
  25. package/dist/credits.js +2 -2
  26. package/dist/load.js +8 -7
  27. package/dist/notifications.d.ts +40 -3
  28. package/dist/notifications.js +34 -6
  29. package/dist/preview/browser.d.ts +1 -0
  30. package/dist/preview/browser.js +87 -0
  31. package/dist/preview/http.d.ts +3 -0
  32. package/dist/preview/http.js +78 -0
  33. package/dist/preview/page.d.ts +1 -0
  34. package/dist/preview/page.js +30 -0
  35. package/dist/preview/project.d.ts +22 -0
  36. package/dist/preview/project.js +81 -0
  37. package/dist/preview/server.d.ts +6 -0
  38. package/dist/preview/server.js +57 -0
  39. package/dist/render.js +4 -40
  40. package/dist/scenario.d.ts +32 -4
  41. package/dist/scenario.js +59 -50
  42. package/dist/timeline.d.ts +68 -5
  43. package/dist/timeline.js +29 -9
  44. package/dist/video/Alerts.js +49 -49
  45. package/dist/video/Broadcast.js +82 -96
  46. package/dist/video/Bulletin.d.ts +2 -2
  47. package/dist/video/Bulletin.js +13 -18
  48. package/dist/video/Chat.d.ts +9 -0
  49. package/dist/video/Chat.js +34 -0
  50. package/dist/video/Information.d.ts +10 -0
  51. package/dist/video/Information.js +100 -0
  52. package/dist/video/Map.js +28 -12
  53. package/dist/video/TimeJump.d.ts +8 -0
  54. package/dist/video/TimeJump.js +42 -0
  55. package/dist/video/layout.d.ts +5 -0
  56. package/dist/video/layout.js +2 -0
  57. package/dist/video/prepare.d.ts +4 -0
  58. package/dist/video/prepare.js +47 -0
  59. package/dist/video/transitions.d.ts +609 -0
  60. package/dist/video/transitions.js +22 -0
  61. package/dist/video/useBundledFonts.js +16 -7
  62. package/package.json +2 -1
  63. package/skills/scenario-writing/SKILL.md +12 -5
  64. package/skills/scenario-writing/references/README.md +4 -4
  65. package/skills/scenario-writing/references/audio.md +7 -1
  66. package/skills/scenario-writing/references/direction.md +31 -7
  67. package/skills/scenario-writing/references/format.md +63 -12
  68. package/skills/scenario-writing/references/news-writing.md +10 -0
  69. package/skills/scenario-writing/references/status-systems.md +4 -2
@@ -0,0 +1,609 @@
1
+ import { type CompiledEvent } from '../scenario.js';
2
+ export declare const changeFrames: number;
3
+ export declare function changeProgress(frame: number, start: number, frames?: number): number;
4
+ export declare function transitionAt<T extends CompiledEvent['type']>(events: CompiledEvent[], type: T, frame: number): {
5
+ current: Extract<{
6
+ type: "alert";
7
+ sender: string;
8
+ text: string;
9
+ level: "안전안내" | "긴급재난" | "위급재난";
10
+ regions: string[];
11
+ mapHighlight: false | {
12
+ duration: number;
13
+ color: string;
14
+ };
15
+ sound: false | "default" | {
16
+ asset: string;
17
+ duration: number;
18
+ volume: number;
19
+ };
20
+ id: string;
21
+ cut: string;
22
+ at: number;
23
+ occurredAt?: string | undefined;
24
+ publishedAt?: string | undefined;
25
+ } & {
26
+ start: number;
27
+ end: number;
28
+ order: number;
29
+ }, {
30
+ type: T;
31
+ }> | Extract<{
32
+ type: "news";
33
+ sound: false | "default" | {
34
+ asset: string;
35
+ duration: number;
36
+ volume: number;
37
+ };
38
+ source: string;
39
+ headline: string;
40
+ duration: number;
41
+ carry: boolean;
42
+ id: string;
43
+ cut: string;
44
+ at: number;
45
+ publishedAt?: string | undefined;
46
+ } & {
47
+ start: number;
48
+ end: number;
49
+ order: number;
50
+ }, {
51
+ type: T;
52
+ }> | Extract<{
53
+ type: "status";
54
+ sound: false | "default" | {
55
+ asset: string;
56
+ duration: number;
57
+ volume: number;
58
+ };
59
+ items: {
60
+ text: string;
61
+ color: string;
62
+ }[];
63
+ duration: number;
64
+ carry: boolean;
65
+ id: string;
66
+ cut: string;
67
+ at: number;
68
+ } & {
69
+ start: number;
70
+ end: number;
71
+ order: number;
72
+ }, {
73
+ type: T;
74
+ }> | Extract<{
75
+ type: "bulletin";
76
+ sound: false | "default" | {
77
+ asset: string;
78
+ duration: number;
79
+ volume: number;
80
+ };
81
+ source: string;
82
+ heading: string;
83
+ text: string;
84
+ color: string;
85
+ duration: number;
86
+ carry: boolean;
87
+ id: string;
88
+ cut: string;
89
+ at: number;
90
+ } & {
91
+ start: number;
92
+ end: number;
93
+ order: number;
94
+ }, {
95
+ type: T;
96
+ }> | Extract<{
97
+ type: "chat";
98
+ title: string;
99
+ duration: number;
100
+ carry: boolean;
101
+ id: string;
102
+ cut: string;
103
+ at: number;
104
+ } & {
105
+ start: number;
106
+ end: number;
107
+ order: number;
108
+ }, {
109
+ type: T;
110
+ }> | Extract<{
111
+ type: "chat-message";
112
+ chat: string;
113
+ author: string;
114
+ text: string;
115
+ color: string;
116
+ id: string;
117
+ cut: string;
118
+ at: number;
119
+ } & {
120
+ start: number;
121
+ end: number;
122
+ order: number;
123
+ }, {
124
+ type: T;
125
+ }> | Extract<{
126
+ type: "zone";
127
+ points: [number, number][];
128
+ color: string;
129
+ opacity: number;
130
+ duration: number;
131
+ carry: boolean;
132
+ id: string;
133
+ cut: string;
134
+ at: number;
135
+ } & {
136
+ start: number;
137
+ end: number;
138
+ order: number;
139
+ }, {
140
+ type: T;
141
+ }> | Extract<{
142
+ type: "line";
143
+ points: [number, number][];
144
+ color: string;
145
+ width: number;
146
+ drawSeconds: number;
147
+ dashed: boolean;
148
+ duration: number;
149
+ carry: boolean;
150
+ id: string;
151
+ cut: string;
152
+ at: number;
153
+ } & {
154
+ start: number;
155
+ end: number;
156
+ order: number;
157
+ }, {
158
+ type: T;
159
+ }> | Extract<{
160
+ type: "region";
161
+ regions: string[];
162
+ color: string;
163
+ fillOpacity: number;
164
+ strokeOpacity: number;
165
+ strokeWidth: number;
166
+ fadeIn: number;
167
+ fadeOut: number;
168
+ pulse: boolean;
169
+ duration: number;
170
+ carry: boolean;
171
+ id: string;
172
+ cut: string;
173
+ at: number;
174
+ strokeColor?: string | undefined;
175
+ label?: string | undefined;
176
+ } & {
177
+ start: number;
178
+ end: number;
179
+ order: number;
180
+ }, {
181
+ type: T;
182
+ }> | Extract<{
183
+ type: "legend";
184
+ items: {
185
+ text: string;
186
+ color: string;
187
+ symbol: "line" | "fill" | "outline";
188
+ }[];
189
+ duration: number;
190
+ carry: boolean;
191
+ id: string;
192
+ cut: string;
193
+ at: number;
194
+ } & {
195
+ start: number;
196
+ end: number;
197
+ order: number;
198
+ }, {
199
+ type: T;
200
+ }> | Extract<{
201
+ type: "marker";
202
+ position: [number, number];
203
+ color: string;
204
+ effect: "point" | "ring";
205
+ duration: number;
206
+ carry: boolean;
207
+ id: string;
208
+ cut: string;
209
+ at: number;
210
+ label?: string | undefined;
211
+ } & {
212
+ start: number;
213
+ end: number;
214
+ order: number;
215
+ }, {
216
+ type: T;
217
+ }> | Extract<{
218
+ type: "camera";
219
+ to: {
220
+ center: [number, number];
221
+ zoom: number;
222
+ bearing: number;
223
+ pitch: number;
224
+ };
225
+ ease: "linear" | "smooth";
226
+ duration: number;
227
+ carry: boolean;
228
+ id: string;
229
+ cut: string;
230
+ at: number;
231
+ } & {
232
+ start: number;
233
+ end: number;
234
+ order: number;
235
+ }, {
236
+ type: T;
237
+ }> | Extract<{
238
+ type: "inset";
239
+ camera: {
240
+ center: [number, number];
241
+ zoom: number;
242
+ bearing: number;
243
+ pitch: number;
244
+ };
245
+ title: string;
246
+ duration: number;
247
+ carry: boolean;
248
+ id: string;
249
+ cut: string;
250
+ at: number;
251
+ } & {
252
+ start: number;
253
+ end: number;
254
+ order: number;
255
+ }, {
256
+ type: T;
257
+ }> | Extract<{
258
+ type: "audio";
259
+ asset: string;
260
+ offset: number;
261
+ volume: number;
262
+ fadeIn: number;
263
+ fadeOut: number;
264
+ duration: number;
265
+ carry: boolean;
266
+ id: string;
267
+ cut: string;
268
+ at: number;
269
+ } & {
270
+ start: number;
271
+ end: number;
272
+ order: number;
273
+ }, {
274
+ type: T;
275
+ }> | Extract<{
276
+ type: "silence";
277
+ duration: number;
278
+ carry: boolean;
279
+ id: string;
280
+ cut: string;
281
+ at: number;
282
+ } & {
283
+ start: number;
284
+ end: number;
285
+ order: number;
286
+ }, {
287
+ type: T;
288
+ }> | Extract<{
289
+ type: "screen";
290
+ effect: "blackout" | "flash" | "interference" | "noise" | "scanlines" | "chromatic" | "dropout" | "shutdown";
291
+ intensity: number;
292
+ fadeIn: number;
293
+ fadeOut: number;
294
+ duration: number;
295
+ carry: boolean;
296
+ id: string;
297
+ cut: string;
298
+ at: number;
299
+ } & {
300
+ start: number;
301
+ end: number;
302
+ order: number;
303
+ }, {
304
+ type: T;
305
+ }> | undefined;
306
+ previous: Extract<{
307
+ type: "alert";
308
+ sender: string;
309
+ text: string;
310
+ level: "안전안내" | "긴급재난" | "위급재난";
311
+ regions: string[];
312
+ mapHighlight: false | {
313
+ duration: number;
314
+ color: string;
315
+ };
316
+ sound: false | "default" | {
317
+ asset: string;
318
+ duration: number;
319
+ volume: number;
320
+ };
321
+ id: string;
322
+ cut: string;
323
+ at: number;
324
+ occurredAt?: string | undefined;
325
+ publishedAt?: string | undefined;
326
+ } & {
327
+ start: number;
328
+ end: number;
329
+ order: number;
330
+ }, {
331
+ type: T;
332
+ }> | Extract<{
333
+ type: "news";
334
+ sound: false | "default" | {
335
+ asset: string;
336
+ duration: number;
337
+ volume: number;
338
+ };
339
+ source: string;
340
+ headline: string;
341
+ duration: number;
342
+ carry: boolean;
343
+ id: string;
344
+ cut: string;
345
+ at: number;
346
+ publishedAt?: string | undefined;
347
+ } & {
348
+ start: number;
349
+ end: number;
350
+ order: number;
351
+ }, {
352
+ type: T;
353
+ }> | Extract<{
354
+ type: "status";
355
+ sound: false | "default" | {
356
+ asset: string;
357
+ duration: number;
358
+ volume: number;
359
+ };
360
+ items: {
361
+ text: string;
362
+ color: string;
363
+ }[];
364
+ duration: number;
365
+ carry: boolean;
366
+ id: string;
367
+ cut: string;
368
+ at: number;
369
+ } & {
370
+ start: number;
371
+ end: number;
372
+ order: number;
373
+ }, {
374
+ type: T;
375
+ }> | Extract<{
376
+ type: "bulletin";
377
+ sound: false | "default" | {
378
+ asset: string;
379
+ duration: number;
380
+ volume: number;
381
+ };
382
+ source: string;
383
+ heading: string;
384
+ text: string;
385
+ color: string;
386
+ duration: number;
387
+ carry: boolean;
388
+ id: string;
389
+ cut: string;
390
+ at: number;
391
+ } & {
392
+ start: number;
393
+ end: number;
394
+ order: number;
395
+ }, {
396
+ type: T;
397
+ }> | Extract<{
398
+ type: "chat";
399
+ title: string;
400
+ duration: number;
401
+ carry: boolean;
402
+ id: string;
403
+ cut: string;
404
+ at: number;
405
+ } & {
406
+ start: number;
407
+ end: number;
408
+ order: number;
409
+ }, {
410
+ type: T;
411
+ }> | Extract<{
412
+ type: "chat-message";
413
+ chat: string;
414
+ author: string;
415
+ text: string;
416
+ color: string;
417
+ id: string;
418
+ cut: string;
419
+ at: number;
420
+ } & {
421
+ start: number;
422
+ end: number;
423
+ order: number;
424
+ }, {
425
+ type: T;
426
+ }> | Extract<{
427
+ type: "zone";
428
+ points: [number, number][];
429
+ color: string;
430
+ opacity: number;
431
+ duration: number;
432
+ carry: boolean;
433
+ id: string;
434
+ cut: string;
435
+ at: number;
436
+ } & {
437
+ start: number;
438
+ end: number;
439
+ order: number;
440
+ }, {
441
+ type: T;
442
+ }> | Extract<{
443
+ type: "line";
444
+ points: [number, number][];
445
+ color: string;
446
+ width: number;
447
+ drawSeconds: number;
448
+ dashed: boolean;
449
+ duration: number;
450
+ carry: boolean;
451
+ id: string;
452
+ cut: string;
453
+ at: number;
454
+ } & {
455
+ start: number;
456
+ end: number;
457
+ order: number;
458
+ }, {
459
+ type: T;
460
+ }> | Extract<{
461
+ type: "region";
462
+ regions: string[];
463
+ color: string;
464
+ fillOpacity: number;
465
+ strokeOpacity: number;
466
+ strokeWidth: number;
467
+ fadeIn: number;
468
+ fadeOut: number;
469
+ pulse: boolean;
470
+ duration: number;
471
+ carry: boolean;
472
+ id: string;
473
+ cut: string;
474
+ at: number;
475
+ strokeColor?: string | undefined;
476
+ label?: string | undefined;
477
+ } & {
478
+ start: number;
479
+ end: number;
480
+ order: number;
481
+ }, {
482
+ type: T;
483
+ }> | Extract<{
484
+ type: "legend";
485
+ items: {
486
+ text: string;
487
+ color: string;
488
+ symbol: "line" | "fill" | "outline";
489
+ }[];
490
+ duration: number;
491
+ carry: boolean;
492
+ id: string;
493
+ cut: string;
494
+ at: number;
495
+ } & {
496
+ start: number;
497
+ end: number;
498
+ order: number;
499
+ }, {
500
+ type: T;
501
+ }> | Extract<{
502
+ type: "marker";
503
+ position: [number, number];
504
+ color: string;
505
+ effect: "point" | "ring";
506
+ duration: number;
507
+ carry: boolean;
508
+ id: string;
509
+ cut: string;
510
+ at: number;
511
+ label?: string | undefined;
512
+ } & {
513
+ start: number;
514
+ end: number;
515
+ order: number;
516
+ }, {
517
+ type: T;
518
+ }> | Extract<{
519
+ type: "camera";
520
+ to: {
521
+ center: [number, number];
522
+ zoom: number;
523
+ bearing: number;
524
+ pitch: number;
525
+ };
526
+ ease: "linear" | "smooth";
527
+ duration: number;
528
+ carry: boolean;
529
+ id: string;
530
+ cut: string;
531
+ at: number;
532
+ } & {
533
+ start: number;
534
+ end: number;
535
+ order: number;
536
+ }, {
537
+ type: T;
538
+ }> | Extract<{
539
+ type: "inset";
540
+ camera: {
541
+ center: [number, number];
542
+ zoom: number;
543
+ bearing: number;
544
+ pitch: number;
545
+ };
546
+ title: string;
547
+ duration: number;
548
+ carry: boolean;
549
+ id: string;
550
+ cut: string;
551
+ at: number;
552
+ } & {
553
+ start: number;
554
+ end: number;
555
+ order: number;
556
+ }, {
557
+ type: T;
558
+ }> | Extract<{
559
+ type: "audio";
560
+ asset: string;
561
+ offset: number;
562
+ volume: number;
563
+ fadeIn: number;
564
+ fadeOut: number;
565
+ duration: number;
566
+ carry: boolean;
567
+ id: string;
568
+ cut: string;
569
+ at: number;
570
+ } & {
571
+ start: number;
572
+ end: number;
573
+ order: number;
574
+ }, {
575
+ type: T;
576
+ }> | Extract<{
577
+ type: "silence";
578
+ duration: number;
579
+ carry: boolean;
580
+ id: string;
581
+ cut: string;
582
+ at: number;
583
+ } & {
584
+ start: number;
585
+ end: number;
586
+ order: number;
587
+ }, {
588
+ type: T;
589
+ }> | Extract<{
590
+ type: "screen";
591
+ effect: "blackout" | "flash" | "interference" | "noise" | "scanlines" | "chromatic" | "dropout" | "shutdown";
592
+ intensity: number;
593
+ fadeIn: number;
594
+ fadeOut: number;
595
+ duration: number;
596
+ carry: boolean;
597
+ id: string;
598
+ cut: string;
599
+ at: number;
600
+ } & {
601
+ start: number;
602
+ end: number;
603
+ order: number;
604
+ }, {
605
+ type: T;
606
+ }> | undefined;
607
+ progress: number;
608
+ start: number;
609
+ };
@@ -0,0 +1,22 @@
1
+ import { FPS } from '../scenario.js';
2
+ // Frame-derived motion: seeking and parallel renders never depend on DOM history.
3
+ export const changeFrames = Math.round(0.45 * FPS);
4
+ export function changeProgress(frame, start, frames = changeFrames) {
5
+ const p = Math.max(0, Math.min(1, (frame - start) / frames));
6
+ return 1 - (1 - p) ** 3;
7
+ }
8
+ export function transitionAt(events, type, frame) {
9
+ const candidates = events.filter((e) => e.type === type && e.start <= frame);
10
+ const current = candidates.find((e) => frame < e.end);
11
+ const previous = current
12
+ ? candidates.find((e) => e !== current && e.end === current.start)
13
+ : candidates.reduce((last, e) => (!last || e.end > last.end ? e : last), undefined);
14
+ const start = current?.start ?? previous?.end ?? frame;
15
+ const progress = changeProgress(frame, start);
16
+ return {
17
+ current,
18
+ previous: progress < 1 ? previous : undefined,
19
+ progress,
20
+ start,
21
+ };
22
+ }
@@ -1,10 +1,13 @@
1
1
  import { useEffect, useState } from 'react';
2
- import { staticFile, useDelayRender, cancelRender } from 'remotion';
2
+ import { staticFile, useBufferState, useDelayRender } from 'remotion';
3
3
  import { fonts } from './fonts.js';
4
4
  export function useBundledFonts(text) {
5
- const { delayRender, continueRender } = useDelayRender();
5
+ const { delayRender, continueRender, cancelRender } = useDelayRender();
6
+ const buffer = useBufferState();
6
7
  const [fontHandle] = useState(() => delayRender('Loading bundled Korean font'));
7
8
  useEffect(() => {
9
+ const playback = buffer.delayPlayback();
10
+ let cancelled = false;
8
11
  const links = fonts.flatMap((font) => font.weights.map((weight) => {
9
12
  const link = document.createElement('link');
10
13
  link.rel = 'stylesheet';
@@ -18,10 +21,16 @@ export function useBundledFonts(text) {
18
21
  })))
19
22
  .then(() => Promise.all(fonts.flatMap((font) => font.weights.map((weight) => document.fonts.load(`${weight} 28px "${font.family}"`, font.id === 'mono'
20
23
  ? '0123456789:.- /+'
21
- : text + ' 재난문자 수신 이력 발생 발표 이전 정정 안전안내 긴급재난 위급재난')))))
24
+ : text + ' 재난문자 수신 이력 발생 발표 안전안내 긴급재난 위급재난')))))
22
25
  .then(() => document.fonts.ready)
23
- .then(() => continueRender(fontHandle))
24
- .catch(cancelRender);
25
- return () => links.forEach((link) => link.remove());
26
- }, [text, fontHandle, delayRender, continueRender]);
26
+ .then(() => { if (!cancelled) {
27
+ continueRender(fontHandle);
28
+ playback.unblock();
29
+ } })
30
+ .catch(error => { if (!cancelled) {
31
+ playback.unblock();
32
+ cancelRender(error);
33
+ } });
34
+ return () => { cancelled = true; playback.unblock(); continueRender(fontHandle); links.forEach((link) => link.remove()); };
35
+ }, [text, fontHandle, buffer, delayRender, continueRender, cancelRender]);
27
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "disaster-broadcast-studio",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "Render text-authored Korean disaster broadcast fiction into deterministic videos.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,6 +27,7 @@
27
27
  "@fontsource/ibm-plex-mono": "5.3.0",
28
28
  "@fontsource/ibm-plex-sans-kr": "5.3.0",
29
29
  "@remotion/bundler": "4.0.543",
30
+ "@remotion/player": "4.0.543",
30
31
  "@remotion/renderer": "4.0.543",
31
32
  "commander": "^14.0.0",
32
33
  "maplibre-gl": "6.18.0",