some fixes

This commit is contained in:
2026-01-04 18:13:21 +01:00
parent 908a4c8bab
commit ddba427205
10 changed files with 328 additions and 39 deletions

3
.gitignore vendored
View File

@@ -42,3 +42,6 @@ build/
.mvn
.idea
## This is for our app, cause it likes to store stuff ##
network_cameras.json

32
pom.xml
View File

@@ -28,6 +28,11 @@
<version>0.3.12</version>
</dependency>
<dependency>
<groupId>com.github.sarxos</groupId>
<artifactId>webcam-capture-driver-ipcam</artifactId>
<version>0.3.12</version>
</dependency>
<!-- for gl we use lwjgl -->
@@ -62,5 +67,32 @@
<version>6.1.0-M1</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<!-- For serializing network cameras we need json, well we dont need it but its certainly a quality
of life feature -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.20.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.jcodec/jcodec -->
<!--
Saving into Files
-->
<dependency>
<groupId>org.jcodec</groupId>
<artifactId>jcodec</artifactId>
<version>0.2.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.jcodec/jcodec-javase -->
<dependency>
<groupId>org.jcodec</groupId>
<artifactId>jcodec-javase</artifactId>
<version>0.2.5</version>
</dependency>
</dependencies>
</project>

View File

@@ -8,10 +8,16 @@ If you want to build this project on yourself, you will need IntelliJ (or any ot
- SWT
- _lwjgl (with opengl)_ → This is important for our goals of rendering on the GPU.
- junit for testing stuff
- jcodec, in the future we will be recording using this
- Jackson (fasterxml) → serializing the config for network cams
### Future Plans:
They arent too big, i want one thing more and that is some more utilities in the camera window.
Also some Network streaming but i am too lazy to do that
### Future Plans:
- [x] basic network cam interfacing
- [ ] better multiplexer (or whatever the viewport is called in cctv)
### Author(s):
- rattatwinko

View File

@@ -1,74 +1,192 @@
package io.swtc;
import com.github.sarxos.webcam.Webcam;
import com.github.sarxos.webcam.WebcamCompositeDriver;
import com.github.sarxos.webcam.ds.buildin.WebcamDefaultDriver;
import com.github.sarxos.webcam.ds.ipcam.IpCamDeviceRegistry;
import com.github.sarxos.webcam.ds.ipcam.IpCamDriver;
import com.github.sarxos.webcam.ds.ipcam.IpCamMode;
import io.swtc.networking.CameraConfig;
import io.swtc.networking.CameraSettings;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.*;
import java.net.MalformedURLException;
import java.util.List;
public class CCTVManager {
static {
Webcam.setDriver(new WebcamCompositeDriver() {{
add(new WebcamDefaultDriver());
add(new IpCamDriver());
}});
for (CameraConfig config : CameraSettings.load()) {
try {
IpCamDeviceRegistry.register(config.getName(), config.getUrl(), IpCamMode.PUSH);
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display, SWT.SHELL_TRIM | SWT.ON_TOP);
Shell shell = new Shell(display);
shell.setText("Dashboard");
shell.setSize(900, 600);
renderUI(shell, display);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) display.sleep();
}
display.dispose();
}
private static void renderUI(Shell shell, Display display) {
// Clear existing children if refreshing
for (Control child : shell.getChildren()) child.dispose();
List<Webcam> webcams = Webcam.getWebcams();
int columnCount = webcams.size() > 3 ? 3 : Math.max(1, webcams.size());
int columnCount = Math.min(3, Math.max(1, webcams.size()));
GridLayout mainLayout = new GridLayout(columnCount, true);
mainLayout.marginWidth = 20;
mainLayout.marginHeight = 20;
mainLayout.horizontalSpacing = 15;
mainLayout.verticalSpacing = 15;
shell.setLayout(mainLayout);
// Header (Spans across all columns)
Label title = new Label(shell, SWT.NONE);
title.setText("Connected Devices");
title.setFont(new Font(display, "Segoe UI", 14, SWT.BOLD));
GridData titleData = new GridData(SWT.FILL, SWT.CENTER, true, false, columnCount, 1);
title.setLayoutData(titleData);
// Header Section
Composite header = new Composite(shell, SWT.NONE);
header.setLayout(new GridLayout(2, false));
header.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, columnCount, 1));
Label title = new Label(header, SWT.NONE);
title.setText("Connected Devices (" + webcams.size() + ")");
title.setFont(new Font(display, "Segoe UI", 16, SWT.BOLD));
title.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
Button addBtn = new Button(header, SWT.PUSH);
addBtn.setText("+ Add IP Camera");
addBtn.addListener(SWT.Selection, e -> showAddCameraDialog(shell, display));
if (webcams.isEmpty()) {
Label error = new Label(shell, SWT.NONE);
error.setText("no available cameras!");
Label note = new Label(shell, SWT.NONE);
note.setText("No cameras detected. Add an IP camera to begin.");
} else {
for (Webcam webcam : webcams) {
createCameraCard(shell, display, webcam);
}
}
shell.pack();
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
shell.layout(true, true);
}
private static void createCameraCard(Composite parent, Display display, Webcam webcam) {
// We check if its a IP Cam by getting the class, if it is then we show the delete button
boolean isIpCam = webcam.getDevice().getClass().getSimpleName().contains("IpCam");
Group card = new Group(parent, SWT.NONE);
card.setText(webcam.getName());
card.setLayout(new GridLayout(1, false));
card.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
card.setLayout(new GridLayout(2, false));
card.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
Button btn = new Button(card, SWT.PUSH);
btn.setText("View");
btn.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
Label info = new Label(card, SWT.WRAP);
info.setText("Type: " + (isIpCam ? "Network IP" : "Local USB"));
info.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
btn.addListener(SWT.Selection, e -> {
/**
* This is where magic happens! We start the Window Here! As arguments, we give our display and webcam index!
* */
System.out.println("Starting: " + webcam.getName());
CameraWindow window = new CameraWindow(display,webcam);
window.open();
Button viewBtn = new Button(card, SWT.PUSH);
viewBtn.setText("Launch");
viewBtn.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
viewBtn.addListener(SWT.Selection, e -> new CameraWindow(display, webcam).open());
Button deleteBtn = new Button(card, SWT.PUSH);
deleteBtn.setText("Delete");
deleteBtn.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
// this is where we do that, this is where the ipcam class is used
deleteBtn.setEnabled(isIpCam);
deleteBtn.addListener(SWT.Selection, e -> {
MessageBox mb = new MessageBox(parent.getShell(), SWT.ICON_QUESTION | SWT.YES | SWT.NO);
mb.setText("Confirm");
mb.setMessage("Remove " + webcam.getName() + "?");
if (mb.open() == SWT.YES) {
CameraSettings.delete(webcam.getName());
IpCamDeviceRegistry.unregister(webcam.getName());
renderUI(parent.getShell(), display);
}
});
}
private static void showAddCameraDialog(Shell parent, Display display) {
Shell dialog = new Shell(parent, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL);
dialog.setText("Register New IP Camera");
dialog.setLayout(new GridLayout(2, false));
new Label(dialog, SWT.NONE).setText("Name:");
Text nameIn = new Text(dialog, SWT.BORDER);
nameIn.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
new Label(dialog, SWT.NONE).setText("MJPEG URL:");
Text urlIn = new Text(dialog, SWT.BORDER);
urlIn.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
urlIn.setMessage("http://10.0.0.x/mjpeg");
// Error message label
Label errorLabel = new Label(dialog, SWT.NONE);
errorLabel.setForeground(display.getSystemColor(SWT.COLOR_RED));
errorLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
errorLabel.setVisible(false);
Button save = new Button(dialog, SWT.PUSH);
save.setText("Add Camera");
save.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
// error handling for stoopid people
save.addListener(SWT.Selection, e -> {
String name = nameIn.getText().trim();
String urlString = urlIn.getText().trim();
if (name.isEmpty() || urlString.isEmpty()) {
errorLabel.setText("Error: All fields are required.");
errorLabel.setVisible(true);
dialog.pack();
return;
}
try {
if (!urlString.toLowerCase().startsWith("http://") && !urlString.toLowerCase().startsWith("https://")) {
throw new MalformedURLException("URL must start with http:// or https://");
}
java.net.URL validatedUrl = new java.net.URL(urlString);
IpCamDeviceRegistry.register(name, validatedUrl.toExternalForm(), IpCamMode.PUSH);
CameraSettings.save(new CameraConfig(name, validatedUrl.toExternalForm()));
dialog.close();
renderUI(parent, display);
} catch (MalformedURLException ex) {
errorLabel.setText("Invalid URL: " + ex.getMessage());
errorLabel.setVisible(true);
dialog.pack();
} catch (Exception ex) {
errorLabel.setText("Registration failed: " + ex.getMessage());
errorLabel.setVisible(true);
dialog.pack();
}
});
dialog.pack();
dialog.open();
}
}

View File

@@ -27,10 +27,15 @@ public class CameraWindow {
data.doubleBuffer = true;
this.renderer = new CameraRenderer(shell, data);
// this was inefficient before
this.captureLoop = new WebcamCaptureLoop(webcam, (BufferedImage img) -> {
if (!display.isDisposed() && !shell.isDisposed()) {
// terribly uneficcient. very stupid tbh
display.syncExec(() -> renderer.render(img));
display.asyncExec(() -> {
if (!shell.isDisposed()) {
// render the image to the shell using gl
renderer.render(img);
}
});
}
});

View File

@@ -0,0 +1,23 @@
package io.swtc.networking;
public class CameraConfig {
public String name;
public String url;
// Default constructor for Jackson
public CameraConfig() {}
public CameraConfig(String name, String url) {
this.name = name;
this.url = url;
}
public String getName() {
return name;
}
public String getUrl() {
return url;
}
}

View File

@@ -0,0 +1,45 @@
package io.swtc.networking;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/*
* Some JSON stuff for camera config saving
* */
public class CameraSettings {
private static final File storage_file = new File("network_cameras.json");
private static final ObjectMapper mapper = new ObjectMapper();
public static List<CameraConfig> load() {
if (!storage_file.exists() || storage_file.length() == 0) return new ArrayList<>();
try {
return mapper.readValue(storage_file, new TypeReference<List<CameraConfig>>() {});
} catch (IOException e) {
return new ArrayList<>();
}
}
public static void save(CameraConfig newCam) {
List<CameraConfig> current = load();
current.add(newCam);
write(current);
}
public static void delete(String name) {
List<CameraConfig> current = load();
current.removeIf(cam -> cam.getName().equals(name));
write(current);
}
private static void write(List<CameraConfig> list) {
try {
mapper.writerWithDefaultPrettyPrinter().writeValue(storage_file, list);
} catch (IOException e) {
e.printStackTrace();
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

View File

@@ -0,0 +1,51 @@
"""
A short test script which tests network cams
"""
import time
import io
from http.server import BaseHTTPRequestHandler, HTTPServer
from PIL import Image, ImageSequence
GIF_PATH = "bk.gif"
FPS = 15
class MJPEGHandler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header(
"Content-Type",
"multipart/x-mixed-replace; boundary=frame"
)
self.end_headers()
gif = Image.open(GIF_PATH)
frames = [
frame.convert("RGB")
for frame in ImageSequence.Iterator(gif)
]
try:
while True:
for frame_img in frames:
buf = io.BytesIO()
frame_img.save(buf, format="JPEG", quality=80)
frame = buf.getvalue()
self.wfile.write(b"--frame\r\n")
self.wfile.write(b"Content-Type: image/jpeg\r\n")
self.wfile.write(
f"Content-Length: {len(frame)}\r\n\r\n".encode()
)
self.wfile.write(frame)
self.wfile.write(b"\r\n")
time.sleep(1 / FPS)
except BrokenPipeError:
pass # client disconnected
if __name__ == "__main__":
print("MJPEG GIF stream running at http://localhost:8080/")
HTTPServer(("", 8080), MJPEGHandler).serve_forever()

View File

@@ -14,9 +14,13 @@ public class WebcamCaptureLoop {
this.webcam = webcam;
this.onFrameCaptured = onFrameCaptured;
// Configure webcam
Dimension[] sizes = webcam.getViewSizes();
webcam.setViewSize(sizes[sizes.length - 1]);
// this is some of the most stupid shit ive seen in years, this is needed for opening the stream.
// the webcam package may not know the res before its opened and well this is before we open it. that is below in the thread.
// so this freaks out and doesnt want to cooperate.
if (!webcam.getName().toLowerCase().contains("ipcam")) {
Dimension[] sizes = webcam.getViewSizes();
webcam.setViewSize(sizes[sizes.length - 1]);
}
}
public void start() {
@@ -24,11 +28,13 @@ public class WebcamCaptureLoop {
running = true;
Thread captureThread = new Thread(() -> {
// this is where we open it. if the res isnt known then it fucks up.
webcam.open();
while (running) {
BufferedImage img = webcam.getImage();
if (img != null) {
//System.err.println(img);
onFrameCaptured.accept(img);
}