File size: 4,146 Bytes
4d1131c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
// The OGG Editor node: the editor core (editor_core.js) on a ComfyUI node. The node keeps the editor's settings
// and edits in its hidden "edits" widget and the names of its last take's preview files in its properties; the server
// plans edits at /ogg/editor/plan, and "Use these edits" queues the workflow, which renders the same plan.

import { EditorView, EditorWindow, TakePreview, injectStyle, loadTake } from "./editor_core.js";

const { app } = window.comfyAPI.app;
const { api } = window.comfyAPI.api;

function viewUrl(name, subfolder, type) {
  return api.apiURL(`/view?${new URLSearchParams({ filename: name, subfolder, type })}`);
}

function hideWidget(w) {
  if (!w) return;
  w.hidden = true;
  w.computeSize = () => [0, -4];
}

async function requestPlan(analysis, edits) {
  const response = await api.fetchApi("/ogg/editor/plan", {
    method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ analysis, edits }),
  });
  if (!response.ok) throw new Error(`plan: HTTP ${response.status}`);
  return response.json();
}

app.registerExtension({
  name: "Ogg.Editor",
  async beforeRegisterNodeDef(nodeType, nodeData) {
    if (nodeData.name !== "OggEditor") return;
    injectStyle();
    const onNodeCreated = nodeType.prototype.onNodeCreated;
    nodeType.prototype.onNodeCreated = function () {
      const r = onNodeCreated?.apply(this, arguments);
      const node = this;
      const editsW = this.widgets?.find((w) => w.name === "edits");
      hideWidget(editsW);
      const readEdits = () => { try { return JSON.parse(editsW?.value || "{}"); } catch { return {}; } };
      const writeEdits = (edits) => {
        if (!editsW) return;
        editsW.value = JSON.stringify(edits);
        editsW.callback?.(editsW.value);
        node.setDirtyCanvas?.(true, true);
      };
      const view = new EditorView({ readEdits, writeEdits, apply: () => app.queuePrompt(0, 1), plan: requestPlan });
      this.oggView = view;
      let window_ = null;
      const preview = new TakePreview(view, {
        emptyText: "Run the workflow to hear, read and edit the take here.",
        onOpen: () => { if (!window_) window_ = new EditorWindow(view, () => { window_ = null; view.emit(); }); },
      });
      for (const type of ["pointerdown", "wheel"]) preview.root.addEventListener(type, (e) => e.stopPropagation());
      const widget = this.addDOMWidget("ogg_editor", "div", preview.root, { serialize: false, getMinHeight: () => 190 });
      widget.serialize = false;
      if (!this.size || this.size[0] < 420 || this.size[1] < 300) this.setSize([Math.max(this.size?.[0] ?? 0, 460), Math.max(this.size?.[1] ?? 0, 320)]);

      let loaded = null;
      const load = async (entry) => {
        if (loaded === entry.audio + entry.analysis) { await view.replan(); return; }
        preview.setStatus("Loading the take...");
        try {
          await loadTake(view, viewUrl(entry.analysis, entry.subfolder, entry.type), viewUrl(entry.audio, entry.subfolder, entry.type));
          loaded = entry.audio + entry.analysis;
          preview.setStatus(null);
          window_?.renderAll();
        } catch (err) {
          preview.setStatus("The take's preview is gone (ComfyUI empties its temp folder on restart). Run the workflow again.");
          console.warn("OGG Editor:", err);
        }
      };

      const onExecuted = this.onExecuted;
      this.onExecuted = function (output) {
        onExecuted?.apply(this, arguments);
        const entry = output?.ogg_editor?.[0];
        if (!entry) return;
        this.properties = this.properties || {};
        this.properties.ogg_editor = entry;
        load(entry);
      };
      const onConfigure = this.onConfigure;
      this.onConfigure = function () {
        onConfigure?.apply(this, arguments);
        hideWidget(editsW);
        const entry = this.properties?.ogg_editor;
        if (entry) load(entry);
      };
      const onRemoved = this.onRemoved;
      this.onRemoved = function () {
        onRemoved?.apply(this, arguments);
        window_?.close();
        view.destroy();
      };
      return r;
    };
  },
});