Skip to content

Commit ecc9e22

Browse files
committed
refactor: Extract per-filetype logic from app.ts
1 parent d8c2954 commit ecc9e22

File tree

4 files changed

+140
-207
lines changed

4 files changed

+140
-207
lines changed

src/scripts/app.ts

Lines changed: 34 additions & 205 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,6 @@ import {
4242
isComboInputSpecV2
4343
} from '@/schemas/nodeDefSchema'
4444
import { type BaseDOMWidget, DOMWidgetImpl } from '@/scripts/domWidget'
45-
import { getFromWebmFile } from '@/scripts/metadata/ebml'
46-
import { getGltfBinaryMetadata } from '@/scripts/metadata/gltf'
47-
import { getFromIsobmffFile } from '@/scripts/metadata/isobmff'
48-
import { getMp3Metadata } from '@/scripts/metadata/mp3'
49-
import { getOggMetadata } from '@/scripts/metadata/ogg'
50-
import { getSvgMetadata } from '@/scripts/metadata/svg'
5145
import { useDialogService } from '@/services/dialogService'
5246
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
5347
import { useExtensionService } from '@/services/extensionService'
@@ -89,20 +83,14 @@ import { deserialiseAndCreate } from '@/utils/vintageClipboard'
8983

9084
import { type ComfyApi, PromptExecutionError, api } from './api'
9185
import { defaultGraph } from './defaultGraph'
92-
import {
93-
getAvifMetadata,
94-
getFlacMetadata,
95-
getLatentMetadata,
96-
getPngMetadata,
97-
getWebpMetadata,
98-
importA1111
99-
} from './pnginfo'
86+
import { importA1111 } from './pnginfo'
10087
import { $el, ComfyUI } from './ui'
10188
import { ComfyAppMenu } from './ui/menu/index'
10289
import { clone } from './utils'
10390
import { type ComfyWidgetConstructor } from './widgets'
10491
import { ensureCorrectLayoutScale } from '@/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale'
10592
import { extractFileFromDragEvent } from '@/utils/eventUtils'
93+
import { getWorkflowDataFromFile } from '@/scripts/metadata/parser'
10694

10795
export const ANIM_PREVIEW_WIDGET = '$$comfy_animation_preview'
10896

@@ -1396,208 +1384,49 @@ export class ComfyApp {
13961384
*/
13971385
async handleFile(file: File, openSource?: WorkflowOpenSource) {
13981386
const fileName = file.name.replace(/\.\w+$/, '') // Strip file extension
1399-
if (file.type === 'image/png') {
1400-
const pngInfo = await getPngMetadata(file)
1401-
if (pngInfo?.workflow) {
1402-
await this.loadGraphData(
1403-
JSON.parse(pngInfo.workflow),
1404-
true,
1405-
true,
1406-
fileName,
1407-
{ openSource }
1408-
)
1409-
return
1410-
}
1411-
if (pngInfo?.prompt) {
1412-
this.loadApiJson(JSON.parse(pngInfo.prompt), fileName)
1413-
return
1414-
}
1415-
if (pngInfo?.parameters) {
1416-
// Note: Not putting this in `importA1111` as it is mostly not used
1417-
// by external callers, and `importA1111` has no access to `app`.
1418-
useWorkflowService().beforeLoadNewGraph()
1419-
importA1111(this.graph, pngInfo.parameters)
1420-
useWorkflowService().afterLoadNewGraph(
1421-
fileName,
1422-
this.graph.serialize() as unknown as ComfyWorkflowJSON
1423-
)
1424-
return
1425-
}
1387+
const workflowData = await getWorkflowDataFromFile(file)
1388+
if (!workflowData) {
1389+
this.showErrorOnFileLoad(file)
1390+
return
14261391
}
1427-
if (file.type === 'image/avif') {
1428-
const { workflow, prompt } = await getAvifMetadata(file)
14291392

1430-
if (workflow) {
1431-
this.loadGraphData(JSON.parse(workflow), true, true, fileName, {
1432-
openSource
1433-
})
1434-
return
1435-
}
1436-
if (prompt) {
1437-
this.loadApiJson(JSON.parse(prompt), fileName)
1438-
return
1439-
}
1440-
}
1441-
if (file.type === 'image/webp') {
1442-
const pngInfo = await getWebpMetadata(file)
1443-
// Support loading workflows from that webp custom node.
1444-
const workflow = pngInfo?.workflow || pngInfo?.Workflow
1445-
const prompt = pngInfo?.prompt || pngInfo?.Prompt
1446-
1447-
if (workflow) {
1448-
this.loadGraphData(JSON.parse(workflow), true, true, fileName, {
1449-
openSource
1450-
})
1451-
return
1452-
}
1453-
if (prompt) {
1454-
this.loadApiJson(JSON.parse(prompt), fileName)
1455-
return
1456-
}
1457-
}
1458-
if (file.type === 'audio/mpeg') {
1459-
const { workflow, prompt } = await getMp3Metadata(file)
1460-
if (workflow) {
1461-
this.loadGraphData(workflow, true, true, fileName)
1462-
return
1463-
}
1464-
if (prompt) {
1465-
this.loadApiJson(prompt, fileName)
1466-
return
1467-
}
1468-
}
1469-
if (file.type === 'audio/ogg') {
1470-
const { workflow, prompt } = await getOggMetadata(file)
1471-
if (workflow) {
1472-
this.loadGraphData(workflow, true, true, fileName, { openSource })
1473-
return
1474-
}
1475-
if (prompt) {
1476-
this.loadApiJson(prompt, fileName)
1477-
return
1478-
}
1479-
}
1480-
if (file.type === 'audio/flac' || file.type === 'audio/x-flac') {
1481-
const pngInfo = await getFlacMetadata(file)
1482-
const workflow = pngInfo?.workflow || pngInfo?.Workflow
1483-
const prompt = pngInfo?.prompt || pngInfo?.Prompt
1484-
1485-
if (workflow) {
1486-
this.loadGraphData(JSON.parse(workflow), true, true, fileName, {
1487-
openSource
1488-
})
1489-
return
1490-
}
1491-
if (prompt) {
1492-
this.loadApiJson(JSON.parse(prompt), fileName)
1493-
return
1494-
}
1495-
}
1496-
if (file.type === 'video/webm') {
1497-
const webmInfo = await getFromWebmFile(file)
1498-
if (webmInfo.workflow) {
1499-
this.loadGraphData(webmInfo.workflow, true, true, fileName, {
1500-
openSource
1501-
})
1502-
return
1503-
}
1504-
if (webmInfo.prompt) {
1505-
this.loadApiJson(webmInfo.prompt, fileName)
1506-
return
1507-
}
1508-
}
1509-
if (
1510-
file.type === 'video/mp4' ||
1511-
file.name?.endsWith('.mp4') ||
1512-
file.name?.endsWith('.mov') ||
1513-
file.name?.endsWith('.m4v') ||
1514-
file.type === 'video/quicktime' ||
1515-
file.type === 'video/x-m4v'
1516-
) {
1517-
const mp4Info = await getFromIsobmffFile(file)
1518-
if (mp4Info.workflow) {
1519-
this.loadGraphData(mp4Info.workflow, true, true, fileName, {
1520-
openSource
1521-
})
1522-
return
1523-
}
1524-
if (mp4Info.prompt) {
1525-
this.loadApiJson(mp4Info.prompt, fileName)
1526-
return
1527-
}
1528-
}
1529-
if (file.type === 'image/svg+xml' || file.name?.endsWith('.svg')) {
1530-
const svgInfo = await getSvgMetadata(file)
1531-
if (svgInfo.workflow) {
1532-
this.loadGraphData(svgInfo.workflow, true, true, fileName, {
1533-
openSource
1534-
})
1535-
return
1536-
}
1537-
if (svgInfo.prompt) {
1538-
this.loadApiJson(svgInfo.prompt, fileName)
1539-
return
1540-
}
1393+
const { workflow, prompt, parameters, templates } = workflowData
1394+
1395+
if (templates) {
1396+
this.loadTemplateData({ templates })
15411397
}
1542-
if (file.type === 'model/gltf-binary' || file.name?.endsWith('.glb')) {
1543-
const gltfInfo = await getGltfBinaryMetadata(file)
1544-
if (gltfInfo.workflow) {
1545-
this.loadGraphData(gltfInfo.workflow, true, true, fileName, {
1546-
openSource
1547-
})
1548-
return
1549-
}
1550-
if (gltfInfo.prompt) {
1551-
this.loadApiJson(gltfInfo.prompt, fileName)
1552-
return
1553-
}
1398+
1399+
if (parameters) {
1400+
// Note: Not putting this in `importA1111` as it is mostly not used
1401+
// by external callers, and `importA1111` has no access to `app`.
1402+
useWorkflowService().beforeLoadNewGraph()
1403+
importA1111(this.graph, parameters)
1404+
useWorkflowService().afterLoadNewGraph(
1405+
fileName,
1406+
this.graph.serialize() as unknown as ComfyWorkflowJSON
1407+
)
1408+
return
15541409
}
1555-
if (file.type === 'application/json' || file.name?.endsWith('.json')) {
1556-
const reader = new FileReader()
1557-
reader.onload = async () => {
1558-
const readerResult = reader.result as string
1559-
const jsonContent = JSON.parse(readerResult)
1560-
if (jsonContent?.templates) {
1561-
this.loadTemplateData(jsonContent)
1562-
return
1563-
}
1564-
if (this.isApiJson(jsonContent)) {
1565-
this.loadApiJson(jsonContent, fileName)
1566-
return
1567-
}
1568-
await this.loadGraphData(
1569-
JSON.parse(readerResult),
1570-
true,
1571-
true,
1572-
fileName,
1573-
{ openSource }
1574-
)
1575-
}
1576-
reader.readAsText(file)
1410+
1411+
if (workflow) {
1412+
const workflowObj =
1413+
typeof workflow === 'string' ? JSON.parse(workflow) : workflow
1414+
await this.loadGraphData(workflowObj, true, true, fileName, {
1415+
openSource
1416+
})
15771417
return
15781418
}
1579-
if (file.name?.endsWith('.latent') || file.name?.endsWith('.safetensors')) {
1580-
const info = await getLatentMetadata(file)
1581-
// TODO define schema to LatentMetadata
1582-
if (info.workflow) {
1583-
await this.loadGraphData(
1584-
JSON.parse(info.workflow),
1585-
true,
1586-
true,
1587-
fileName,
1588-
{ openSource }
1589-
)
1590-
return
1591-
}
1592-
if (info.prompt) {
1593-
this.loadApiJson(JSON.parse(info.prompt), fileName)
1594-
return
1595-
}
1419+
1420+
if (prompt) {
1421+
const promptObj = typeof prompt === 'string' ? JSON.parse(prompt) : prompt
1422+
this.loadApiJson(promptObj, fileName)
1423+
return
15961424
}
15971425

15981426
this.showErrorOnFileLoad(file)
15991427
}
16001428

1429+
// @deprecated
16011430
isApiJson(data: unknown) {
16021431
return _.isObject(data) && Object.values(data).every((v) => v.class_type)
16031432
}

src/scripts/metadata/json.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { isObject } from 'es-toolkit/compat'
2+
3+
export function getDataFromJSON(
4+
file: File
5+
): Promise<Record<string, object> | undefined> {
6+
return new Promise<Record<string, object> | undefined>((resolve) => {
7+
const reader = new FileReader()
8+
reader.onload = async () => {
9+
const readerResult = reader.result as string
10+
const jsonContent = JSON.parse(readerResult)
11+
if (jsonContent?.templates) {
12+
resolve({ templates: jsonContent.templates })
13+
return
14+
}
15+
if (isApiJson(jsonContent)) {
16+
resolve({ prompt: jsonContent })
17+
return
18+
}
19+
resolve({ workflow: jsonContent })
20+
return
21+
}
22+
reader.readAsText(file)
23+
return
24+
})
25+
}
26+
27+
function isApiJson(data: unknown) {
28+
return isObject(data) && Object.values(data).every((v) => v.class_type)
29+
}

src/scripts/metadata/parser.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { getFromWebmFile } from '@/scripts/metadata/ebml'
2+
import { getGltfBinaryMetadata } from '@/scripts/metadata/gltf'
3+
import { getFromIsobmffFile } from '@/scripts/metadata/isobmff'
4+
import { getDataFromJSON } from '@/scripts/metadata/json'
5+
import { getMp3Metadata } from '@/scripts/metadata/mp3'
6+
import { getOggMetadata } from '@/scripts/metadata/ogg'
7+
import { getSvgMetadata } from '@/scripts/metadata/svg'
8+
import {
9+
getAvifMetadata,
10+
getWebpMetadata,
11+
getFlacMetadata,
12+
getLatentMetadata,
13+
getPngMetadata
14+
} from '@/scripts/pnginfo'
15+
16+
export async function getWorkflowDataFromFile(
17+
file: File
18+
): Promise<Record<string, string | object> | undefined> {
19+
if (file.type === 'image/png') {
20+
return await getPngMetadata(file)
21+
}
22+
if (file.type === 'image/avif') {
23+
return await getAvifMetadata(file)
24+
}
25+
if (file.type === 'image/webp') {
26+
const pngInfo = await getWebpMetadata(file)
27+
// Support loading workflows from that webp custom node.
28+
const workflow = pngInfo?.workflow || pngInfo?.Workflow
29+
const prompt = pngInfo?.prompt || pngInfo?.Prompt
30+
return { workflow, prompt }
31+
}
32+
if (file.type === 'audio/mpeg') {
33+
return await getMp3Metadata(file)
34+
}
35+
if (file.type === 'audio/ogg') {
36+
return await getOggMetadata(file)
37+
}
38+
if (file.type === 'audio/flac' || file.type === 'audio/x-flac') {
39+
const pngInfo = await getFlacMetadata(file)
40+
const workflow = pngInfo?.workflow || pngInfo?.Workflow
41+
const prompt = pngInfo?.prompt || pngInfo?.Prompt
42+
43+
return { workflow, prompt }
44+
}
45+
if (file.type === 'video/webm') {
46+
return (await getFromWebmFile(file)) as unknown as Record<string, object>
47+
}
48+
if (
49+
file.name?.endsWith('.mp4') ||
50+
file.name?.endsWith('.mov') ||
51+
file.name?.endsWith('.m4v') ||
52+
file.type === 'video/mp4' ||
53+
file.type === 'video/quicktime' ||
54+
file.type === 'video/x-m4v'
55+
) {
56+
return (await getFromIsobmffFile(file)) as unknown as Record<string, object>
57+
}
58+
if (file.type === 'image/svg+xml' || file.name?.endsWith('.svg')) {
59+
return (await getSvgMetadata(file)) as unknown as Record<string, object>
60+
}
61+
if (file.type === 'model/gltf-binary' || file.name?.endsWith('.glb')) {
62+
return (await getGltfBinaryMetadata(file)) as unknown as Record<
63+
string,
64+
object
65+
>
66+
}
67+
if (file.name?.endsWith('.latent') || file.name?.endsWith('.safetensors')) {
68+
return await getLatentMetadata(file)
69+
}
70+
71+
if (file.type === 'application/json' || file.name?.endsWith('.json')) {
72+
return getDataFromJSON(file)
73+
}
74+
return
75+
}

src/scripts/metadata/png.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
/** @knipIgnoreUnusedButUsedByCustomNodes */
2-
export function getFromPngBuffer(buffer: ArrayBuffer) {
2+
export function getFromPngBuffer(buffer: ArrayBuffer): Record<string, string> {
33
// Get the PNG data as a Uint8Array
44
const pngData = new Uint8Array(buffer)
55
const dataView = new DataView(pngData.buffer)
66

77
// Check that the PNG signature is present
88
if (dataView.getUint32(0) !== 0x89504e47) {
99
console.error('Not a valid PNG file')
10-
return
10+
return {}
1111
}
1212

1313
// Start searching for chunks after the PNG signature

0 commit comments

Comments
 (0)