fablecut 1.13.0 → 1.15.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fablecut",
3
- "version": "1.13.0",
3
+ "version": "1.15.0",
4
4
  "description": "Zero-dependency browser video editor that AI agents can drive — JSON timeline, MCP server, REST API, live-reloading UI.",
5
5
  "main": "server.js",
6
6
  "bin": {
package/paths.js CHANGED
@@ -32,8 +32,15 @@ const MEDIA_DIR = path.join(DATA_DIR, "media");
32
32
  const EXPORTS_DIR = path.join(DATA_DIR, "exports");
33
33
  const ANALYSIS_DIR = path.join(DATA_DIR, "analysis");
34
34
  const LIBRARY_DIR = path.join(DATA_DIR, "library");
35
+ /* Grabs of single frames / contact sheets (GET /api/frame). Disposable render
36
+ cache, so it lives inside analysis/ rather than becoming a fifth top-level
37
+ folder users have to think about. */
38
+ const FRAMES_DIR = path.join(ANALYSIS_DIR, "frames");
39
+ /* Proxies (light copies of heavy footage the editor plays while editing).
40
+ A regenerable cache too, so also inside analysis/. */
41
+ const PROXIES_DIR = path.join(ANALYSIS_DIR, "proxies");
35
42
  const PROJECT_FILE = path.join(DATA_DIR, "project.json");
36
- const LIBRARY_SUBDIRS = ["sfx", "elements", "svg", "fonts"];
43
+ const LIBRARY_SUBDIRS = ["sfx", "elements", "svg", "fonts", "presets", "templates"];
37
44
 
38
45
  /* The asset library ships with the repo but users also drop their own files in
39
46
  (library/sfx is gitignored precisely for that). When the data dir is split
@@ -92,15 +99,38 @@ function superseded(rel, file) {
92
99
  /* Create the writable tree. Safe to call from both servers; whoever runs first
93
100
  wins and the other no-ops. */
94
101
  function ensureDirs() {
95
- for (const d of [DATA_DIR, MEDIA_DIR, EXPORTS_DIR, ANALYSIS_DIR])
102
+ for (const d of [DATA_DIR, MEDIA_DIR, EXPORTS_DIR, ANALYSIS_DIR, FRAMES_DIR, PROXIES_DIR])
96
103
  fs.mkdirSync(d, { recursive: true });
97
104
  for (const d of LIBRARY_SUBDIRS)
98
105
  fs.mkdirSync(path.join(LIBRARY_DIR, d), { recursive: true });
99
106
  seedLibrary();
100
107
  }
101
108
 
109
+ /* Saved animation presets and templates: one JSON file each in library/presets/
110
+ and library/templates/ (templates.js validates them). Unreadable files are skipped. */
111
+ function readSaved(kind) {
112
+ const dir = path.join(LIBRARY_DIR, kind);
113
+ let names = [];
114
+ try { names = fs.readdirSync(dir).filter((f) => f.toLowerCase().endsWith(".json")).sort(); } catch { return []; }
115
+ const out = [];
116
+ for (const f of names) {
117
+ try { out.push({ file: f, data: JSON.parse(fs.readFileSync(path.join(dir, f), "utf8").replace(new RegExp("^\\uFEFF"), "")) }); } catch { }
118
+ }
119
+ return out;
120
+ }
121
+ function writeSaved(kind, id, data) {
122
+ const dir = path.join(LIBRARY_DIR, kind);
123
+ fs.mkdirSync(dir, { recursive: true });
124
+ const file = path.join(dir, String(id).replace(/[^a-z0-9-]+/gi, "-").slice(0, 60) + ".json");
125
+ // tmp + rename, like the project file: a crash mid-write never leaves half a preset
126
+ const tmp = `${file}.${crypto.randomUUID()}.tmp`; // unique: the editor and the MCP server may save at once
127
+ fs.writeFileSync(tmp, JSON.stringify(data, null, 2) + "\n");
128
+ fs.renameSync(tmp, file);
129
+ return file;
130
+ }
131
+
102
132
  module.exports = {
103
133
  APP_DIR, DATA_DIR, SPLIT,
104
134
  MEDIA_DIR, EXPORTS_DIR, ANALYSIS_DIR, LIBRARY_DIR, PROJECT_FILE,
105
- LIBRARY_SUBDIRS, ensureDirs,
135
+ LIBRARY_SUBDIRS, FRAMES_DIR, PROXIES_DIR, ensureDirs, readSaved, writeSaved,
106
136
  };