some fixes
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -42,3 +42,6 @@ build/
|
||||
|
||||
.mvn
|
||||
.idea
|
||||
|
||||
## This is for our app, cause it likes to store stuff ##
|
||||
network_cameras.json
|
||||
32
pom.xml
32
pom.xml
@@ -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>
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
23
src/main/java/io/swtc/networking/CameraConfig.java
Normal file
23
src/main/java/io/swtc/networking/CameraConfig.java
Normal 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;
|
||||
}
|
||||
}
|
||||
45
src/main/java/io/swtc/networking/CameraSettings.java
Normal file
45
src/main/java/io/swtc/networking/CameraSettings.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
src/main/java/io/swtc/networking/bk.gif
Normal file
BIN
src/main/java/io/swtc/networking/bk.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
51
src/main/java/io/swtc/networking/server.py
Normal file
51
src/main/java/io/swtc/networking/server.py
Normal 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()
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user