swing-ui 0.1.25 → 1.0.1
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/LICENSE +5 -4
- package/README.md +144 -7
- package/SwingUI.js +2611 -0
- package/classes/Arc.js +185 -0
- package/classes/Button.js +55 -0
- package/classes/CheckBox.js +33 -0
- package/classes/CheckBoxMenuItem.js +24 -0
- package/classes/ComboBox.js +329 -0
- package/classes/ComponentContainer.js +828 -0
- package/classes/ContextMenu.js +141 -0
- package/classes/DocumentEditor.js +168 -0
- package/classes/Ellipse.js +76 -0
- package/classes/InternalWindow.js +397 -0
- package/classes/InternalWindowContainer.js +93 -0
- package/classes/Label.js +52 -0
- package/classes/Line.js +105 -0
- package/classes/List.js +452 -0
- package/classes/MediaPlayer.js +504 -0
- package/classes/Menu.js +74 -0
- package/classes/MenuBar.js +56 -0
- package/classes/MenuItem.js +92 -0
- package/classes/Panel.js +50 -0
- package/classes/PasswordField.js +47 -0
- package/classes/Polygon.js +236 -0
- package/classes/Polyline.js +240 -0
- package/classes/ProgressBar.js +62 -0
- package/classes/RadioButton.js +110 -0
- package/classes/RadioButtonMenuItem.js +106 -0
- package/classes/Rectangle.js +199 -0
- package/classes/ScrollPane.js +53 -0
- package/classes/ScrollableComponent.js +141 -0
- package/classes/Separator.js +24 -0
- package/classes/Shape.js +584 -0
- package/classes/ShapeContainer.js +231 -0
- package/classes/Slider.js +172 -0
- package/classes/Spinner.js +172 -0
- package/classes/SplitView.js +82 -0
- package/classes/Stroke.js +115 -0
- package/classes/Table.js +982 -0
- package/classes/Tabs.js +228 -0
- package/classes/TextArea.js +125 -0
- package/classes/TextComponent.js +207 -0
- package/classes/TextField.js +128 -0
- package/classes/TextShape.js +123 -0
- package/classes/ToggleButton.js +147 -0
- package/classes/ToolBar.js +120 -0
- package/classes/TrayIcon.js +54 -0
- package/classes/Tree.js +569 -0
- package/classes/UIComponent.js +2113 -0
- package/classes/Window.js +609 -0
- package/enums.js +298 -0
- package/fonts.conf +32 -0
- package/functions.js +254 -0
- package/icon.png +0 -0
- package/index.js +109 -4
- package/jar/gui-11.jar +0 -0
- package/jar/lib/flatlaf.jar +0 -0
- package/jar/lib/jna.jar +0 -0
- package/package.json +58 -33
- package/typedefs.js +973 -0
- package/types/SwingUI.d.ts +727 -0
- package/types/classes/Arc.d.ts +111 -0
- package/types/classes/Button.d.ts +38 -0
- package/types/classes/CheckBox.d.ts +22 -0
- package/types/classes/CheckBoxMenuItem.d.ts +18 -0
- package/types/classes/ComboBox.d.ts +172 -0
- package/types/classes/ComponentContainer.d.ts +131 -0
- package/types/classes/ContextMenu.d.ts +74 -0
- package/types/classes/DocumentEditor.d.ts +95 -0
- package/types/classes/Ellipse.d.ts +34 -0
- package/types/classes/InternalWindow.d.ts +163 -0
- package/types/classes/InternalWindowContainer.d.ts +36 -0
- package/types/classes/Label.d.ts +33 -0
- package/types/classes/Line.d.ts +48 -0
- package/types/classes/List.d.ts +223 -0
- package/types/classes/MediaPlayer.d.ts +381 -0
- package/types/classes/Menu.d.ts +43 -0
- package/types/classes/MenuBar.d.ts +26 -0
- package/types/classes/MenuItem.d.ts +57 -0
- package/types/classes/Panel.d.ts +28 -0
- package/types/classes/PasswordField.d.ts +22 -0
- package/types/classes/Polygon.d.ts +93 -0
- package/types/classes/Polyline.d.ts +96 -0
- package/types/classes/ProgressBar.d.ts +36 -0
- package/types/classes/RadioButton.d.ts +70 -0
- package/types/classes/RadioButtonMenuItem.d.ts +71 -0
- package/types/classes/Rectangle.d.ts +82 -0
- package/types/classes/ScrollPane.d.ts +23 -0
- package/types/classes/ScrollableComponent.d.ts +81 -0
- package/types/classes/Separator.d.ts +14 -0
- package/types/classes/Shape.d.ts +223 -0
- package/types/classes/ShapeContainer.d.ts +92 -0
- package/types/classes/Slider.d.ts +107 -0
- package/types/classes/Spinner.d.ts +129 -0
- package/types/classes/SplitView.d.ts +47 -0
- package/types/classes/Stroke.d.ts +99 -0
- package/types/classes/Table.d.ts +652 -0
- package/types/classes/Tabs.d.ts +118 -0
- package/types/classes/TextArea.d.ts +45 -0
- package/types/classes/TextComponent.d.ts +136 -0
- package/types/classes/TextField.d.ts +94 -0
- package/types/classes/TextShape.d.ts +73 -0
- package/types/classes/ToggleButton.d.ts +84 -0
- package/types/classes/ToolBar.d.ts +82 -0
- package/types/classes/TrayIcon.d.ts +43 -0
- package/types/classes/Tree.d.ts +354 -0
- package/types/classes/UIComponent.d.ts +945 -0
- package/types/classes/Window.d.ts +334 -0
- package/types/enums.d.ts +437 -0
- package/types/functions.d.ts +109 -0
- package/types/index.d.ts +188 -0
- package/types/typedefs.d.ts +1626 -0
- package/ui-process-manager.js +317 -0
- package/video-devices-linux.sh +72 -0
- package/video-devices-mac.sh +33 -0
- package/CHANGES.md +0 -3
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IPC Proxy worker
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {parentPort, workerData} from "node:worker_threads";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
|
|
8
|
+
const env = workerData.runtime === "deno" ? Deno.env.toObject() : process.env;
|
|
9
|
+
const responsePort = workerData.responsePort;
|
|
10
|
+
const sharedBuffSignal = workerData.signalBuffer;
|
|
11
|
+
|
|
12
|
+
const WorkerSignalIndex = {ExecutionControl: 0, WorkerControl: 1, DataLength: 2, ReceivedBytes: 3, SentBytes: 4};
|
|
13
|
+
const WorkerSignal = {Resume: 0, Pause: 1, SendLine: 2, Block: 3, Unblock: 4, SendBytes: 5};
|
|
14
|
+
|
|
15
|
+
const decoder = new TextDecoder("utf-8");
|
|
16
|
+
const encoder = new TextEncoder();
|
|
17
|
+
|
|
18
|
+
let jarProcess = null;
|
|
19
|
+
let stdinWriter = null; // writer needed for Deno spawned process
|
|
20
|
+
let ready = false;
|
|
21
|
+
let lineBuffer = "";
|
|
22
|
+
|
|
23
|
+
let syncMode = false;
|
|
24
|
+
let exiting = false;
|
|
25
|
+
|
|
26
|
+
if(workerData.runtime === "bun")
|
|
27
|
+
{
|
|
28
|
+
console.log = (...args) => {
|
|
29
|
+
if(typeof args[0] === "object")
|
|
30
|
+
{
|
|
31
|
+
if(args[0] instanceof Error)
|
|
32
|
+
{
|
|
33
|
+
args[0] = args[0].stack;
|
|
34
|
+
}
|
|
35
|
+
else
|
|
36
|
+
{
|
|
37
|
+
args[0] = args[0].toString();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
process.stdout.write(`${args[0]}\n`);
|
|
41
|
+
};
|
|
42
|
+
console.error = console.log;
|
|
43
|
+
}
|
|
44
|
+
else
|
|
45
|
+
{
|
|
46
|
+
// Override console.log, console.error, etc. for logging to the main thread
|
|
47
|
+
["log", "error", "warn", "info", "debug"].forEach(method => {
|
|
48
|
+
console[method] = (...args) => {
|
|
49
|
+
parentPort.postMessage({type: `worker_${method}`, data: args[0]});
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function startJar()
|
|
55
|
+
{
|
|
56
|
+
let JAR_PATH = path.join(workerData.moduleDir, "jar/gui-11.jar");
|
|
57
|
+
let JAVA_BIN = env.JAVA_HOME ? path.join(env.JAVA_HOME, workerData.os === "windows" ? "java.exe" : "java") : "java"; // build custom path from specified dir for each OS, or just use default command.
|
|
58
|
+
let JAVA_OPTS = (env.JAVA_OPTS ?? "").split(" ").filter(Boolean);
|
|
59
|
+
if(JAVA_OPTS.indexOf("-Dfile.encoding=UTF-8") === -1) JAVA_OPTS.push("-Dfile.encoding=UTF-8"); // important
|
|
60
|
+
if(workerData.debugJava) JAVA_OPTS.push("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005"); // for debugging JAR
|
|
61
|
+
if(workerData.os === "linux") JAVA_OPTS.push("--add-opens=java.desktop/sun.awt.X11=ALL-UNNAMED"); // to be able to set WM_CLASS to project name in Java GUI
|
|
62
|
+
|
|
63
|
+
const _opts = [...JAVA_OPTS, "-jar", JAR_PATH, "--root-dir", workerData.rootDir, "--module-dir", workerData.moduleDir];
|
|
64
|
+
|
|
65
|
+
if(workerData.verbose) _opts.push("--v");
|
|
66
|
+
|
|
67
|
+
try
|
|
68
|
+
{
|
|
69
|
+
if(workerData.runtime === "bun")
|
|
70
|
+
{
|
|
71
|
+
_opts.unshift(JAVA_BIN);
|
|
72
|
+
|
|
73
|
+
try
|
|
74
|
+
{
|
|
75
|
+
jarProcess = Bun.spawn({
|
|
76
|
+
cmd: _opts,
|
|
77
|
+
stdin: "pipe",
|
|
78
|
+
stdout: "pipe",
|
|
79
|
+
stderr: "pipe",
|
|
80
|
+
env: env
|
|
81
|
+
});
|
|
82
|
+
}catch(ex)
|
|
83
|
+
{
|
|
84
|
+
console.error("Process Spawn Failed: ");
|
|
85
|
+
console.error(ex);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
jarProcess.exited.then((code) => {
|
|
90
|
+
if(!exiting) console.error(`[jar-worker] GUI exited with code ${code}`);
|
|
91
|
+
ready = false;
|
|
92
|
+
parentPort.postMessage({type: "exit", code});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Read stdout
|
|
96
|
+
(async () => {
|
|
97
|
+
for await (const chunk of jarProcess.stdout)
|
|
98
|
+
{
|
|
99
|
+
lineBuffer += decoder.decode(chunk);
|
|
100
|
+
let i;
|
|
101
|
+
while((i = lineBuffer.indexOf("\n")) !== -1)
|
|
102
|
+
{
|
|
103
|
+
const line = lineBuffer.slice(0, i).trim();
|
|
104
|
+
lineBuffer = lineBuffer.slice(i + 1);
|
|
105
|
+
if(line.length > 0)
|
|
106
|
+
{
|
|
107
|
+
if(line.startsWith("eyJ")) // line.startsWith("{\"")
|
|
108
|
+
{
|
|
109
|
+
handleJarLine(line);
|
|
110
|
+
}
|
|
111
|
+
else
|
|
112
|
+
{
|
|
113
|
+
console.log(`GUI Process: ${line}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
})();
|
|
119
|
+
|
|
120
|
+
// Read stderr
|
|
121
|
+
(async () => {
|
|
122
|
+
for await (const chunk of jarProcess.stderr)
|
|
123
|
+
{
|
|
124
|
+
console.error(decoder.decode(chunk));
|
|
125
|
+
}
|
|
126
|
+
})();
|
|
127
|
+
}
|
|
128
|
+
else if(workerData.runtime === "node")
|
|
129
|
+
{
|
|
130
|
+
const {spawn} = await import("node:child_process");
|
|
131
|
+
|
|
132
|
+
jarProcess = spawn(
|
|
133
|
+
JAVA_BIN,
|
|
134
|
+
_opts,
|
|
135
|
+
{
|
|
136
|
+
stdin: "pipe",
|
|
137
|
+
stdout: "pipe",
|
|
138
|
+
stderr: "pipe",
|
|
139
|
+
env: env
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
jarProcess.stdout.setEncoding("utf8");
|
|
143
|
+
jarProcess.stderr.setEncoding("utf8");
|
|
144
|
+
|
|
145
|
+
// Receive data from JAVA process
|
|
146
|
+
jarProcess.stdout.on("data", data => {
|
|
147
|
+
lineBuffer += data.toString();
|
|
148
|
+
let i;
|
|
149
|
+
while((i = lineBuffer.indexOf("\n")) !== -1)
|
|
150
|
+
{
|
|
151
|
+
const line = lineBuffer.slice(0, i).trim();
|
|
152
|
+
lineBuffer = lineBuffer.slice(i + 1);
|
|
153
|
+
if(line.length > 0)
|
|
154
|
+
{
|
|
155
|
+
if(line.startsWith("eyJ")) //line.startsWith("{\"")
|
|
156
|
+
{
|
|
157
|
+
handleJarLine(line);
|
|
158
|
+
}
|
|
159
|
+
else
|
|
160
|
+
{
|
|
161
|
+
console.log(`GUI Process: ${line}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
jarProcess.stderr.on("data", data => {
|
|
168
|
+
console.error(data.toString());
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
jarProcess.on("close", (code) => {
|
|
172
|
+
console.error(`[jar-worker] GUI exited with code ${code ?? 0}`);
|
|
173
|
+
jarProcess = null;
|
|
174
|
+
ready = false;
|
|
175
|
+
parentPort.postMessage({type: "exit", code: code ?? 0});
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
else if(workerData.runtime === "deno")
|
|
179
|
+
{
|
|
180
|
+
const command = new Deno.Command(JAVA_BIN, {
|
|
181
|
+
args: _opts,
|
|
182
|
+
stdin: "piped",
|
|
183
|
+
stdout: "piped",
|
|
184
|
+
stderr: "piped",
|
|
185
|
+
env: env
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
jarProcess = command.spawn();
|
|
189
|
+
stdinWriter = jarProcess.stdin.getWriter();
|
|
190
|
+
|
|
191
|
+
jarProcess.status.then((status) => {
|
|
192
|
+
console.error(`[jar-worker] GUI exited with code ${status.code}`);
|
|
193
|
+
jarProcess = null;
|
|
194
|
+
ready = false;
|
|
195
|
+
parentPort.postMessage({type: "exit", code: status.code});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Read stdout
|
|
199
|
+
(async () => {
|
|
200
|
+
const reader = jarProcess.stdout.getReader();
|
|
201
|
+
while(true)
|
|
202
|
+
{
|
|
203
|
+
const {done, value} = await reader.read();
|
|
204
|
+
if(done) break;
|
|
205
|
+
|
|
206
|
+
lineBuffer += decoder.decode(value);
|
|
207
|
+
let i;
|
|
208
|
+
while((i = lineBuffer.indexOf("\n")) !== -1)
|
|
209
|
+
{
|
|
210
|
+
const line = lineBuffer.slice(0, i).trim();
|
|
211
|
+
lineBuffer = lineBuffer.slice(i + 1);
|
|
212
|
+
if(line.length > 0)
|
|
213
|
+
{
|
|
214
|
+
if(line.startsWith("eyJ")) //line.startsWith("{\"")
|
|
215
|
+
{
|
|
216
|
+
handleJarLine(line);
|
|
217
|
+
}
|
|
218
|
+
else
|
|
219
|
+
{
|
|
220
|
+
console.log(`GUI Process: ${line}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
})();
|
|
226
|
+
|
|
227
|
+
// Read stderr
|
|
228
|
+
(async () => {
|
|
229
|
+
const reader = jarProcess.stderr.getReader();
|
|
230
|
+
while(true)
|
|
231
|
+
{
|
|
232
|
+
const {done, value} = await reader.read();
|
|
233
|
+
if(done) break;
|
|
234
|
+
console.error(decoder.decode(value));
|
|
235
|
+
}
|
|
236
|
+
})();
|
|
237
|
+
}
|
|
238
|
+
}catch(ex)
|
|
239
|
+
{
|
|
240
|
+
console.error(ex);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
ready = true;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function sendLineToJar(line)
|
|
247
|
+
{
|
|
248
|
+
switch(workerData.runtime)
|
|
249
|
+
{
|
|
250
|
+
case "deno":
|
|
251
|
+
{
|
|
252
|
+
stdinWriter.write(encoder.encode(line));
|
|
253
|
+
stdinWriter.write(encoder.encode("\n"));
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
default:
|
|
257
|
+
{
|
|
258
|
+
jarProcess.stdin.write(line);
|
|
259
|
+
jarProcess.stdin.write("\n");
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function handleJarLine(line)
|
|
265
|
+
{
|
|
266
|
+
if(line.trim().length === 0) return; // ignore empty lines
|
|
267
|
+
|
|
268
|
+
line = Buffer.from(line, "base64").toString("utf8");
|
|
269
|
+
|
|
270
|
+
// send it as a response if requested
|
|
271
|
+
if(syncMode && (line.indexOf("\"response\":") > 0 || line.indexOf("%22response%22%3A") > 0)) // check if response JSON object string has response property in it "\"response\":"
|
|
272
|
+
{
|
|
273
|
+
responsePort.postMessage(line);
|
|
274
|
+
syncMode = false;
|
|
275
|
+
|
|
276
|
+
// unblock main thread
|
|
277
|
+
Atomics.store(sharedBuffSignal, WorkerSignalIndex.ExecutionControl, WorkerSignal.Unblock);
|
|
278
|
+
Atomics.notify(sharedBuffSignal, WorkerSignalIndex.ExecutionControl);
|
|
279
|
+
}
|
|
280
|
+
else
|
|
281
|
+
{
|
|
282
|
+
parentPort.postMessage({type: "data", data: line});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
parentPort.on("message", (msg) => {
|
|
287
|
+
if(!msg) return;
|
|
288
|
+
|
|
289
|
+
if("data" in msg)
|
|
290
|
+
{
|
|
291
|
+
// Enter sync mode before writing so no incoming lines are missed
|
|
292
|
+
if(msg.respond) syncMode = true;
|
|
293
|
+
sendLineToJar(msg.data);
|
|
294
|
+
}
|
|
295
|
+
// Config override: { type: "__config", jarPath?, javaBin?, javaOpts? }
|
|
296
|
+
else if(msg.type === "__config")
|
|
297
|
+
{
|
|
298
|
+
if(msg.jarPath) JAR_PATH = msg.jarPath;
|
|
299
|
+
if(msg.javaBin) JAVA_BIN = msg.javaBin;
|
|
300
|
+
if(msg.javaOpts) JAVA_OPTS = msg.javaOpts;
|
|
301
|
+
console.log(`[jar-worker] Config updated — jar: ${JAR_PATH}`);
|
|
302
|
+
}
|
|
303
|
+
// Shutdown: { type: "__shutdown" }
|
|
304
|
+
else if(msg.type === "__shutdown")
|
|
305
|
+
{
|
|
306
|
+
exiting = true;
|
|
307
|
+
console.log("[jar-worker] Shutdown requested");
|
|
308
|
+
jarProcess?.kill("SIGTERM");
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
await startJar();
|
|
313
|
+
ready = true;
|
|
314
|
+
|
|
315
|
+
// unblock main thread
|
|
316
|
+
Atomics.store(sharedBuffSignal, WorkerSignalIndex.ExecutionControl, WorkerSignal.Unblock);
|
|
317
|
+
Atomics.notify(sharedBuffSignal, WorkerSignalIndex.ExecutionControl);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Gets video devices and their formats (requires ffmpeg installed)
|
|
4
|
+
|
|
5
|
+
get_fps() {
|
|
6
|
+
local dev=$1
|
|
7
|
+
local res=$2
|
|
8
|
+
local fmt=$3
|
|
9
|
+
local fps=""
|
|
10
|
+
|
|
11
|
+
if [ "$fmt" = "yuyv422" ]; then
|
|
12
|
+
stream_line=$(ffmpeg -hide_banner -f v4l2 -video_size "$res" -pix_fmt "$fmt" -i "$dev" -t 1 -y /dev/null 2>&1 | grep "Stream #0:0")
|
|
13
|
+
else
|
|
14
|
+
stream_line=$(ffmpeg -hide_banner -f v4l2 -video_size "$res" -codec "$fmt" -i "$dev" -t 1 -y /dev/null 2>&1 | grep "Stream #0:0")
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
fps=$(echo "$stream_line" | grep -oP ', \K[0-9.]+(?= fps)')
|
|
18
|
+
echo "$fps"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
echo "["
|
|
22
|
+
|
|
23
|
+
for dev in /dev/video*; do
|
|
24
|
+
[ -e "$dev" ] || continue
|
|
25
|
+
dev_id=$(basename "$dev")
|
|
26
|
+
dev_name=$(cat "/sys/class/video4linux/$dev_id/name")
|
|
27
|
+
ffmpeg_output=$(ffmpeg -hide_banner -list_formats all -f v4l2 -i "$dev" 2>&1)
|
|
28
|
+
|
|
29
|
+
# Include both Raw and Compressed lines
|
|
30
|
+
format_lines=$(echo "$ffmpeg_output" | grep -E "^\[video4linux2.*(Raw|Compressed)")
|
|
31
|
+
|
|
32
|
+
# Remove the [ ... ] part first
|
|
33
|
+
clean_lines=$(echo "$format_lines" | sed -E 's/\[[^]]*\]//g')
|
|
34
|
+
|
|
35
|
+
[ -z "$clean_lines" ] && continue
|
|
36
|
+
|
|
37
|
+
echo " {\"id\":\"$dev\",\"name\":\"$dev_name\",\"formats\":["
|
|
38
|
+
|
|
39
|
+
while IFS= read -r line; do
|
|
40
|
+
# Determine if it's Raw or Compressed
|
|
41
|
+
format_type=""
|
|
42
|
+
if echo "$line" | grep -q "Raw"; then
|
|
43
|
+
format_type="raw"
|
|
44
|
+
elif echo "$line" | grep -q "Compressed"; then
|
|
45
|
+
format_type="compressed"
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# Extract format (2nd field after removing brackets)
|
|
49
|
+
fmt=$(echo "$line" | cut -d':' -f2 | xargs)
|
|
50
|
+
# Extract resolutions
|
|
51
|
+
resolutions=$(echo "$line" | grep -oP '\d+x\d+')
|
|
52
|
+
|
|
53
|
+
for res in $resolutions; do
|
|
54
|
+
fps=$(get_fps "$dev" "$res" "$fmt")
|
|
55
|
+
[ -z "$fps" ] && fps="?"
|
|
56
|
+
|
|
57
|
+
# Output with appropriate prefix based on format type
|
|
58
|
+
if [ "$format_type" = "raw" ]; then
|
|
59
|
+
echo " {\"pixel_format\":\"$fmt\",\"size\":\"$res\",\"min_fps\":$fps,\"max_fps\":$fps},"
|
|
60
|
+
elif [ "$format_type" = "compressed" ]; then
|
|
61
|
+
echo " {\"vcodec\":\"$fmt\",\"size\":\"$res\",\"min_fps\":$fps,\"max_fps\":$fps},"
|
|
62
|
+
else
|
|
63
|
+
echo " {\"format\":\"UNKNOWN\",\"size\":\"$res\",\"min_fps\":$fps,\"max_fps\":$fps},"
|
|
64
|
+
fi
|
|
65
|
+
done
|
|
66
|
+
done <<< "$clean_lines"
|
|
67
|
+
|
|
68
|
+
echo " ]"
|
|
69
|
+
echo " },"
|
|
70
|
+
done
|
|
71
|
+
|
|
72
|
+
echo "]"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Common resolutions to test
|
|
4
|
+
resolutions=("1920x1080" "1280x720" "1024x576" "960x540" "848x480" "640x480" "640x360" "320x240")
|
|
5
|
+
|
|
6
|
+
# Common framerates to test
|
|
7
|
+
framerates=("60" "30" "24", "15", "10", "5", "3", "2", "1")
|
|
8
|
+
|
|
9
|
+
echo "Listing macOS video devices..."
|
|
10
|
+
|
|
11
|
+
# Get device list
|
|
12
|
+
device_list=$(ffmpeg -f avfoundation -list_devices true -i "" 2>&1 | grep "AVFoundation video devices" -A 50 | grep -E "^\[.*\] \[[0-9]+\]" | sed -E 's/.*\[(.*)\] (.*)/\1|\2/')
|
|
13
|
+
|
|
14
|
+
# Loop through devices
|
|
15
|
+
echo "$device_list" | while IFS="|" read -r id name; do
|
|
16
|
+
# Skip empty lines
|
|
17
|
+
[ -z "$id" ] && continue
|
|
18
|
+
[ -z "$name" ] && continue
|
|
19
|
+
|
|
20
|
+
echo "DEVICE|video$id|$name"
|
|
21
|
+
|
|
22
|
+
for res in "${resolutions[@]}"; do
|
|
23
|
+
for fps in "${framerates[@]}"; do
|
|
24
|
+
# Try ffmpeg capture for 1 second
|
|
25
|
+
result=$(ffmpeg -f avfoundation -framerate "$fps" -video_size "$res" -i "$id" -t 1 -y /dev/null 2>&1 | grep "Stream #0:0")
|
|
26
|
+
if [ -n "$result" ]; then
|
|
27
|
+
echo "RES|avfoundation|$res|$fps"
|
|
28
|
+
fi
|
|
29
|
+
done
|
|
30
|
+
done
|
|
31
|
+
|
|
32
|
+
echo "---"
|
|
33
|
+
done
|
package/CHANGES.md
DELETED