@aicut/core 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.
@@ -0,0 +1,120 @@
1
+ /**
2
+ * UI strings the editor paints into the DOM (toolbar tooltips, the
3
+ * fullscreen exit button) and onto the timeline canvas (phantom new-
4
+ * track label, track header labels). Every user-visible literal in
5
+ * `@aicut/core` flows through this interface — there are no hidden
6
+ * hard-coded translations elsewhere in the library.
7
+ *
8
+ * Defaults to English. Hosts that want Chinese (or any other locale)
9
+ * pass `locale: localeZh` to `Editor.create` / `Timeline.create`, or
10
+ * override individual keys with `locale: { undo: "撤销" }`.
11
+ */
12
+ interface Locale {
13
+ undo: string;
14
+ redo: string;
15
+ split: string;
16
+ trimLeft: string;
17
+ trimRight: string;
18
+ speedComingSoon: string;
19
+ playPause: string;
20
+ fullscreen: string;
21
+ snap: string;
22
+ /** Title shown on the snap button when snap is ON (clicking turns OFF). */
23
+ snapOnTitle: string;
24
+ /** Title shown when snap is OFF (clicking turns ON). */
25
+ snapOffTitle: string;
26
+ zoomOut: string;
27
+ zoomIn: string;
28
+ reset: string;
29
+ exitFullscreen: string;
30
+ exitFullscreenTitle: string;
31
+ /** Phantom row that appears under the last track during a drag. */
32
+ newTrack: string;
33
+ /** Track header — `{n}` is replaced with the 1-based track index. */
34
+ videoTrackLabel: string;
35
+ /** Same template format as videoTrackLabel. */
36
+ audioTrackLabel: string;
37
+ }
38
+ /** English. The library default — chosen over Chinese as the OSS norm. */
39
+ declare const localeEn: Locale;
40
+ /** Simplified Chinese. */
41
+ declare const localeZh: Locale;
42
+ /** Spread defaults under host overrides — host can supply a partial. */
43
+ declare function mergeLocale(partial: Partial<Locale> | undefined): Locale;
44
+ /**
45
+ * Replace `{key}` placeholders in a template. We only need `{n}`
46
+ * substitution today; the implementation is generic so additional
47
+ * keys (e.g. `{name}`) won't need a second pass.
48
+ */
49
+ declare function formatLabel(template: string, vars: Record<string, string | number>): string;
50
+
51
+ /**
52
+ * Milliseconds. All timing in the project is expressed as integer ms to
53
+ * keep JSON serialization unambiguous (no frame-rate coupling in the
54
+ * data model — the renderer can present time as frames if it wants).
55
+ */
56
+ type Ms = number;
57
+ interface MediaSource {
58
+ id: string;
59
+ url: string;
60
+ kind: "video" | "audio";
61
+ /** Optional — probed lazily from the <video> element if absent. */
62
+ duration?: Ms;
63
+ name?: string;
64
+ }
65
+ interface Clip {
66
+ id: string;
67
+ sourceId: string;
68
+ /** Window into the source — `in` inclusive, `out` exclusive. */
69
+ in: Ms;
70
+ out: Ms;
71
+ /** Position on the timeline. */
72
+ start: Ms;
73
+ /**
74
+ * Playback rate. 1 = normal, 2 = 2× speed. Default 1.
75
+ * Persisted in the project JSON so a host can restore exactly.
76
+ */
77
+ speed?: number;
78
+ }
79
+ interface Track {
80
+ id: string;
81
+ kind: "video" | "audio";
82
+ /** Clips on this track. Must be kept sorted by `start` and non-overlapping. */
83
+ clips: Clip[];
84
+ }
85
+ interface Project {
86
+ /** Schema version — bump when breaking the JSON shape. */
87
+ version: 1;
88
+ sources: MediaSource[];
89
+ tracks: Track[];
90
+ }
91
+ /**
92
+ * Subset of CSS variables the editor honors. Pass any custom values
93
+ * via `Editor` options; everything is forwarded as `--aicut-*` on the
94
+ * editor's root container, so a host can also override via plain CSS.
95
+ */
96
+ interface Theme {
97
+ brand?: string;
98
+ secondary?: string;
99
+ surface?: string;
100
+ dark?: string;
101
+ muted?: string;
102
+ card?: string;
103
+ success?: string;
104
+ warning?: string;
105
+ info?: string;
106
+ error?: string;
107
+ /** Toolbar / ruler chrome. Background of the editor frame. */
108
+ controlsBg?: string;
109
+ controlsBorder?: string;
110
+ controlsText?: string;
111
+ controlsHover?: string;
112
+ controlsActive?: string;
113
+ /** Letterbox color around the preview video. Defaults to black. */
114
+ previewBg?: string;
115
+ radiusSm?: string;
116
+ radiusMd?: string;
117
+ radiusLg?: string;
118
+ }
119
+
120
+ export { type Clip as C, type Locale as L, type Ms as M, type Project as P, type Track as T, type MediaSource as a, type Theme as b, localeZh as c, formatLabel as f, localeEn as l, mergeLocale as m };
@@ -0,0 +1,120 @@
1
+ /**
2
+ * UI strings the editor paints into the DOM (toolbar tooltips, the
3
+ * fullscreen exit button) and onto the timeline canvas (phantom new-
4
+ * track label, track header labels). Every user-visible literal in
5
+ * `@aicut/core` flows through this interface — there are no hidden
6
+ * hard-coded translations elsewhere in the library.
7
+ *
8
+ * Defaults to English. Hosts that want Chinese (or any other locale)
9
+ * pass `locale: localeZh` to `Editor.create` / `Timeline.create`, or
10
+ * override individual keys with `locale: { undo: "撤销" }`.
11
+ */
12
+ interface Locale {
13
+ undo: string;
14
+ redo: string;
15
+ split: string;
16
+ trimLeft: string;
17
+ trimRight: string;
18
+ speedComingSoon: string;
19
+ playPause: string;
20
+ fullscreen: string;
21
+ snap: string;
22
+ /** Title shown on the snap button when snap is ON (clicking turns OFF). */
23
+ snapOnTitle: string;
24
+ /** Title shown when snap is OFF (clicking turns ON). */
25
+ snapOffTitle: string;
26
+ zoomOut: string;
27
+ zoomIn: string;
28
+ reset: string;
29
+ exitFullscreen: string;
30
+ exitFullscreenTitle: string;
31
+ /** Phantom row that appears under the last track during a drag. */
32
+ newTrack: string;
33
+ /** Track header — `{n}` is replaced with the 1-based track index. */
34
+ videoTrackLabel: string;
35
+ /** Same template format as videoTrackLabel. */
36
+ audioTrackLabel: string;
37
+ }
38
+ /** English. The library default — chosen over Chinese as the OSS norm. */
39
+ declare const localeEn: Locale;
40
+ /** Simplified Chinese. */
41
+ declare const localeZh: Locale;
42
+ /** Spread defaults under host overrides — host can supply a partial. */
43
+ declare function mergeLocale(partial: Partial<Locale> | undefined): Locale;
44
+ /**
45
+ * Replace `{key}` placeholders in a template. We only need `{n}`
46
+ * substitution today; the implementation is generic so additional
47
+ * keys (e.g. `{name}`) won't need a second pass.
48
+ */
49
+ declare function formatLabel(template: string, vars: Record<string, string | number>): string;
50
+
51
+ /**
52
+ * Milliseconds. All timing in the project is expressed as integer ms to
53
+ * keep JSON serialization unambiguous (no frame-rate coupling in the
54
+ * data model — the renderer can present time as frames if it wants).
55
+ */
56
+ type Ms = number;
57
+ interface MediaSource {
58
+ id: string;
59
+ url: string;
60
+ kind: "video" | "audio";
61
+ /** Optional — probed lazily from the <video> element if absent. */
62
+ duration?: Ms;
63
+ name?: string;
64
+ }
65
+ interface Clip {
66
+ id: string;
67
+ sourceId: string;
68
+ /** Window into the source — `in` inclusive, `out` exclusive. */
69
+ in: Ms;
70
+ out: Ms;
71
+ /** Position on the timeline. */
72
+ start: Ms;
73
+ /**
74
+ * Playback rate. 1 = normal, 2 = 2× speed. Default 1.
75
+ * Persisted in the project JSON so a host can restore exactly.
76
+ */
77
+ speed?: number;
78
+ }
79
+ interface Track {
80
+ id: string;
81
+ kind: "video" | "audio";
82
+ /** Clips on this track. Must be kept sorted by `start` and non-overlapping. */
83
+ clips: Clip[];
84
+ }
85
+ interface Project {
86
+ /** Schema version — bump when breaking the JSON shape. */
87
+ version: 1;
88
+ sources: MediaSource[];
89
+ tracks: Track[];
90
+ }
91
+ /**
92
+ * Subset of CSS variables the editor honors. Pass any custom values
93
+ * via `Editor` options; everything is forwarded as `--aicut-*` on the
94
+ * editor's root container, so a host can also override via plain CSS.
95
+ */
96
+ interface Theme {
97
+ brand?: string;
98
+ secondary?: string;
99
+ surface?: string;
100
+ dark?: string;
101
+ muted?: string;
102
+ card?: string;
103
+ success?: string;
104
+ warning?: string;
105
+ info?: string;
106
+ error?: string;
107
+ /** Toolbar / ruler chrome. Background of the editor frame. */
108
+ controlsBg?: string;
109
+ controlsBorder?: string;
110
+ controlsText?: string;
111
+ controlsHover?: string;
112
+ controlsActive?: string;
113
+ /** Letterbox color around the preview video. Defaults to black. */
114
+ previewBg?: string;
115
+ radiusSm?: string;
116
+ radiusMd?: string;
117
+ radiusLg?: string;
118
+ }
119
+
120
+ export { type Clip as C, type Locale as L, type Ms as M, type Project as P, type Track as T, type MediaSource as a, type Theme as b, localeZh as c, formatLabel as f, localeEn as l, mergeLocale as m };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@aicut/core",
3
- "version": "0.1.1",
4
- "description": "Framework-agnostic core for the AiCut video editor — canvas timeline, data model, HTML5 playback engine.",
3
+ "version": "0.3.0",
4
+ "description": "Framework-agnostic core for the AiCut video editor — canvas timeline, data model, HTML5 playback engine, plus an opt-in 3D lighting picker.",
5
5
  "license": "MIT",
6
6
  "author": "ziqiang <ziqiangytu@gmail.com>",
7
7
  "homepage": "https://github.com/ziqiangai/AiCut#readme",
@@ -34,7 +34,10 @@
34
34
  "davinci",
35
35
  "imovie",
36
36
  "veed",
37
- "filmora"
37
+ "filmora",
38
+ "lighting",
39
+ "three.js",
40
+ "ai-relighting"
38
41
  ],
39
42
  "type": "module",
40
43
  "sideEffects": [
@@ -49,6 +52,11 @@
49
52
  "import": "./dist/index.js",
50
53
  "require": "./dist/index.cjs"
51
54
  },
55
+ "./lighting": {
56
+ "types": "./dist/lighting/index.d.ts",
57
+ "import": "./dist/lighting/index.js",
58
+ "require": "./dist/lighting/index.cjs"
59
+ },
52
60
  "./styles.css": "./styles/theme.css"
53
61
  },
54
62
  "files": [
@@ -56,7 +64,11 @@
56
64
  "styles",
57
65
  "README.md"
58
66
  ],
67
+ "dependencies": {
68
+ "three": "^0.159.0"
69
+ },
59
70
  "devDependencies": {
71
+ "@types/three": "^0.159.0",
60
72
  "tsup": "^8.3.5",
61
73
  "typescript": "^5.7.2"
62
74
  },
package/styles/theme.css CHANGED
@@ -412,3 +412,242 @@
412
412
  .aicut-timeline-toolbar-right {
413
413
  margin-left: auto;
414
414
  }
415
+
416
+ /* ===== Lighting editor (opt-in component) ===== */
417
+
418
+ /* IMPORTANT: every `var(--aicut-controls-*)` reference below carries
419
+ an explicit fallback (the same value the .aicut-root rule would
420
+ compute via its self-reference pattern). Chrome treats the
421
+ self-reference `--foo: var(--foo, x)` on .aicut-root as a cycle
422
+ and skips it when no parent + no inline override defines `--foo`
423
+ — which means the lighting component must NOT depend on those
424
+ inherited custom properties succeeding. The video editor sidesteps
425
+ the issue because applyTheme() always writes the values inline.
426
+ Hosts that pass a `theme` prop or set the --aicut-controls-* vars
427
+ themselves still override these fallbacks; it's pure belt-and-
428
+ braces. */
429
+
430
+ .aicut-lighting-editor {
431
+ --aicut-lighting-canvas-bg: color-mix(in srgb, var(--aicut-controls-text, rgba(255, 255, 255, 0.85)) 4%, transparent);
432
+ --aicut-lighting-section-gap: 14px;
433
+ --aicut-lighting-control-w: 220px;
434
+ display: block;
435
+ }
436
+
437
+ .aicut-lighting-body {
438
+ display: grid;
439
+ /* Just two columns: scene 240 + controls 220. Hosts that want to
440
+ render an adjacent panel (AI smart mode, prompt, presets, …) put
441
+ their own DOM beside <LightingEditor> in their own layout. */
442
+ grid-template-columns: 240px 220px;
443
+ gap: 16px;
444
+ align-items: start;
445
+ padding: 16px;
446
+ background: var(--aicut-controls-bg, #1f1f22);
447
+ color: var(--aicut-controls-text, rgba(255, 255, 255, 0.85));
448
+ border-radius: var(--aicut-radius-md, 12px);
449
+ }
450
+
451
+ /* --- Scene column --- */
452
+ .aicut-lighting-scene-col {
453
+ display: flex;
454
+ flex-direction: column;
455
+ align-items: center;
456
+ gap: 8px;
457
+ /* Self-center vertically so the sphere stays at the visual midpoint
458
+ of the row even when the smart drawer stretches the row taller
459
+ than the scene column's own content. */
460
+ align-self: center;
461
+ }
462
+
463
+ .aicut-lighting-view-toggle {
464
+ display: inline-flex;
465
+ padding: 2px;
466
+ border-radius: 999px;
467
+ background: var(--aicut-controls-active, rgba(255, 255, 255, 0.12));
468
+ position: relative;
469
+ }
470
+ .aicut-lighting-view-opt {
471
+ height: 24px;
472
+ padding: 0 12px;
473
+ border: 0;
474
+ background: transparent;
475
+ font: inherit;
476
+ font-size: 12px;
477
+ color: var(--aicut-controls-text, rgba(255, 255, 255, 0.85));
478
+ opacity: 0.6;
479
+ border-radius: 999px;
480
+ cursor: pointer;
481
+ transition: opacity 120ms ease, background-color 120ms ease;
482
+ }
483
+ .aicut-lighting-view-opt.active {
484
+ background: var(--aicut-controls-bg, #1f1f22);
485
+ opacity: 1;
486
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
487
+ }
488
+
489
+ .aicut-lighting-scene-viewport {
490
+ width: 100%;
491
+ max-width: 240px;
492
+ aspect-ratio: 1;
493
+ border-radius: 50%;
494
+ overflow: hidden;
495
+ background: var(--aicut-lighting-canvas-bg);
496
+ position: relative;
497
+ /* Keep the canvas filling the viewport even before ResizeObserver
498
+ fires the first measurement. */
499
+ display: flex;
500
+ align-items: center;
501
+ justify-content: center;
502
+ }
503
+
504
+ /* --- Controls column --- */
505
+ .aicut-lighting-controls {
506
+ display: flex;
507
+ flex-direction: column;
508
+ gap: var(--aicut-lighting-section-gap);
509
+ min-width: 0;
510
+ }
511
+
512
+ .aicut-lighting-controls-header {
513
+ display: flex;
514
+ align-items: center;
515
+ justify-content: space-between;
516
+ gap: 8px;
517
+ }
518
+
519
+ .aicut-lighting-controls-title {
520
+ font-size: 13px;
521
+ font-weight: 600;
522
+ color: var(--aicut-controls-text, rgba(255, 255, 255, 0.85));
523
+ }
524
+
525
+ .aicut-lighting-controls-header-slot {
526
+ display: flex;
527
+ align-items: center;
528
+ gap: 8px;
529
+ margin-left: auto;
530
+ }
531
+
532
+ .aicut-lighting-section {
533
+ display: flex;
534
+ flex-direction: column;
535
+ gap: 6px;
536
+ }
537
+ .aicut-lighting-section-row {
538
+ flex-direction: row;
539
+ align-items: center;
540
+ justify-content: space-between;
541
+ }
542
+
543
+ .aicut-lighting-label {
544
+ font-size: 12px;
545
+ color: color-mix(in srgb, var(--aicut-controls-text, rgba(255, 255, 255, 0.85)) 70%, transparent);
546
+ }
547
+
548
+ .aicut-lighting-range {
549
+ appearance: none;
550
+ -webkit-appearance: none;
551
+ width: 100%;
552
+ height: 4px;
553
+ border-radius: 999px;
554
+ background: color-mix(in srgb, var(--aicut-controls-text, rgba(255, 255, 255, 0.85)) 18%, transparent);
555
+ outline: none;
556
+ }
557
+ .aicut-lighting-range::-webkit-slider-thumb {
558
+ appearance: none;
559
+ -webkit-appearance: none;
560
+ width: 14px;
561
+ height: 14px;
562
+ border-radius: 50%;
563
+ background: var(--aicut-controls-text, rgba(255, 255, 255, 0.85));
564
+ cursor: pointer;
565
+ }
566
+ .aicut-lighting-range::-moz-range-thumb {
567
+ width: 14px;
568
+ height: 14px;
569
+ border: 0;
570
+ border-radius: 50%;
571
+ background: var(--aicut-controls-text, rgba(255, 255, 255, 0.85));
572
+ cursor: pointer;
573
+ }
574
+
575
+ .aicut-lighting-color {
576
+ width: 44px;
577
+ height: 24px;
578
+ padding: 0;
579
+ border: 1px solid var(--aicut-controls-border, rgba(255, 255, 255, 0.08));
580
+ border-radius: 6px;
581
+ background: transparent;
582
+ cursor: pointer;
583
+ }
584
+
585
+ .aicut-lighting-dir-grid {
586
+ display: grid;
587
+ grid-template-columns: repeat(3, 1fr);
588
+ gap: 6px;
589
+ }
590
+ .aicut-lighting-dir-btn {
591
+ height: 28px;
592
+ padding: 0 4px;
593
+ border: 1px solid var(--aicut-controls-border, rgba(255, 255, 255, 0.08));
594
+ border-radius: 6px;
595
+ background: transparent;
596
+ color: var(--aicut-controls-text, rgba(255, 255, 255, 0.85));
597
+ font: inherit;
598
+ font-size: 12px;
599
+ cursor: pointer;
600
+ transition: background-color 120ms ease, border-color 120ms ease;
601
+ }
602
+ .aicut-lighting-dir-btn:hover {
603
+ background: var(--aicut-controls-hover, rgba(255, 255, 255, 0.08));
604
+ }
605
+ .aicut-lighting-dir-btn.active {
606
+ background: color-mix(in srgb, var(--color-brand, #ff3386) 18%, transparent);
607
+ border-color: var(--color-brand, #ff3386);
608
+ color: var(--color-brand, #ff3386);
609
+ }
610
+
611
+ .aicut-lighting-toggle {
612
+ width: 32px;
613
+ height: 18px;
614
+ border-radius: 999px;
615
+ background: color-mix(in srgb, var(--aicut-controls-text, rgba(255, 255, 255, 0.85)) 18%, transparent);
616
+ position: relative;
617
+ cursor: pointer;
618
+ transition: background-color 120ms ease;
619
+ }
620
+ .aicut-lighting-toggle.active {
621
+ background: var(--color-brand, #ff3386);
622
+ }
623
+ .aicut-lighting-toggle-thumb {
624
+ width: 14px;
625
+ height: 14px;
626
+ border-radius: 50%;
627
+ background: #fff;
628
+ position: absolute;
629
+ top: 2px;
630
+ left: 2px;
631
+ transition: left 120ms ease;
632
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
633
+ }
634
+ .aicut-lighting-toggle.active .aicut-lighting-toggle-thumb {
635
+ left: 16px;
636
+ }
637
+
638
+ .aicut-lighting-reset {
639
+ margin-left: auto;
640
+ height: 28px;
641
+ padding: 0 12px;
642
+ border: 1px solid var(--aicut-controls-border, rgba(255, 255, 255, 0.08));
643
+ border-radius: 6px;
644
+ background: transparent;
645
+ color: var(--aicut-controls-text, rgba(255, 255, 255, 0.85));
646
+ font: inherit;
647
+ font-size: 12px;
648
+ cursor: pointer;
649
+ }
650
+ .aicut-lighting-reset:hover {
651
+ background: var(--aicut-controls-hover, rgba(255, 255, 255, 0.08));
652
+ }
653
+