shit
This commit is contained in:
89
README.md
89
README.md
@@ -1,65 +1,88 @@
|
|||||||
# Java Security Camera App
|
# Java Security Camera Application (JSCA)
|
||||||
|
|
||||||
A Java desktop application for viewing live streams from both USB webcams and network MJPEG cameras.
|
A Java-based desktop application for managing and viewing multiple camera feeds simultaneously. Supports both local webcams and network cameras.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- View live streams from USB webcams
|
- 🎥 Support for local webcams and network cameras (MJPEG streams)
|
||||||
- Connect to network MJPEG cameras (e.g., DroidCamX)
|
- 📺 2x2 grid layout for viewing up to 4 camera feeds
|
||||||
- Take snapshots of the current view
|
- 💾 Save and load camera configurations
|
||||||
- Simple and intuitive user interface
|
- 🔐 Basic authentication for network cameras
|
||||||
- Support for multiple camera sources
|
- 🖱️ Click-to-select camera panels
|
||||||
|
- 🎛️ Simple and intuitive menu interface
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Java 11 or higher
|
- Java 11 or higher
|
||||||
- Maven
|
- Maven 3.6 or higher
|
||||||
- USB webcam (for local camera support)
|
- Connected webcam(s) for local camera support
|
||||||
- Network camera with MJPEG stream support (for network camera support)
|
- Network camera URLs (if using IP cameras)
|
||||||
|
|
||||||
## Building the Application
|
## Building the Application
|
||||||
|
|
||||||
1. Clone the repository
|
1. Clone the repository:
|
||||||
2. Navigate to the project directory
|
```bash
|
||||||
3. Build with Maven:
|
git clone https://github.com/yourusername/jsca.git
|
||||||
|
cd jsca
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Build with Maven:
|
||||||
```bash
|
```bash
|
||||||
mvn clean package
|
mvn clean package
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This will create an executable JAR file in the `target` directory.
|
||||||
|
|
||||||
## Running the Application
|
## Running the Application
|
||||||
|
|
||||||
After building, run the application using:
|
Run the application using:
|
||||||
```bash
|
```bash
|
||||||
java -jar target/security-camera-app-1.0-SNAPSHOT-jar-with-dependencies.jar
|
java -jar target/security-camera-app-1.0-SNAPSHOT-jar-with-dependencies.jar
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage Instructions
|
||||||
|
|
||||||
1. Launch the application
|
1. **Adding Cameras**:
|
||||||
2. Select the camera source from the dropdown:
|
- Select an empty camera panel by clicking on it
|
||||||
- USB Camera: Uses your computer's webcam
|
- Go to Camera -> Add Local Camera to add a webcam
|
||||||
- Network Camera: Connects to an MJPEG stream URL
|
- Go to Camera -> Add Network Camera to add an IP camera
|
||||||
3. Click "Start" to begin streaming
|
|
||||||
4. Use the "Snapshot" button to capture the current frame
|
|
||||||
5. Click "Stop" to end the stream
|
|
||||||
|
|
||||||
### Using with Network Cameras
|
2. **Managing Cameras**:
|
||||||
|
- Click on a camera panel to select it
|
||||||
|
- Use Camera -> Remove Camera to stop and remove the selected camera
|
||||||
|
- Use Camera -> Restart Camera to restart the selected camera feed
|
||||||
|
|
||||||
For network cameras, you'll need to provide the MJPEG stream URL. Common formats include:
|
3. **Saving/Loading Configurations**:
|
||||||
- DroidCamX: `http://[IP_ADDRESS]:4747/video`
|
- File -> Save Setup to save your current camera configuration
|
||||||
- Generic IP Camera: `http://[IP_ADDRESS]/video` or `http://[IP_ADDRESS]/mjpeg`
|
- File -> Load Setup to restore a previously saved configuration
|
||||||
|
|
||||||
|
## Network Camera URLs
|
||||||
|
|
||||||
|
For network cameras, you'll need the MJPEG stream URL. Common formats include:
|
||||||
|
|
||||||
|
- Generic IP Camera: `http://camera-ip:port/video`
|
||||||
|
- DroidCam: `http://phone-ip:4747/video`
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
1. No webcam detected:
|
1. **No webcams detected**:
|
||||||
- Ensure your webcam is properly connected
|
- Check if your webcam is properly connected
|
||||||
- Check if other applications are using the webcam
|
- Ensure no other application is using the webcam
|
||||||
|
|
||||||
2. Network camera not connecting:
|
2. **Network camera not connecting**:
|
||||||
- Verify the camera URL is correct
|
- Verify the camera URL is correct
|
||||||
- Ensure the camera is on the same network
|
- Check if the camera is accessible from your network
|
||||||
- Check if the camera supports MJPEG streaming
|
- Ensure proper credentials are provided if required
|
||||||
|
|
||||||
|
3. **Performance issues**:
|
||||||
|
- Try reducing the number of active cameras
|
||||||
|
- Check your network bandwidth for IP cameras
|
||||||
|
- Ensure your computer meets the minimum requirements
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is open source and available under the MIT License.
|
This project is licensed under the MIT License - see the LICENSE file for details.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions are welcome! Please feel free to submit a Pull Request.
|
||||||
83
scripts/image_filters.js
Normal file
83
scripts/image_filters.js
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
// Image filter script for JSCA
|
||||||
|
// This script demonstrates how to apply basic image filters to the camera feed
|
||||||
|
|
||||||
|
function applyGrayscale(image) {
|
||||||
|
var width = image.getWidth();
|
||||||
|
var height = image.getHeight();
|
||||||
|
var result = new java.awt.image.BufferedImage(
|
||||||
|
width, height,
|
||||||
|
java.awt.image.BufferedImage.TYPE_BYTE_GRAY
|
||||||
|
);
|
||||||
|
|
||||||
|
var g = result.getGraphics();
|
||||||
|
g.drawImage(image, 0, 0, null);
|
||||||
|
g.dispose();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applySepia(image) {
|
||||||
|
var width = image.getWidth();
|
||||||
|
var height = image.getHeight();
|
||||||
|
var result = new java.awt.image.BufferedImage(
|
||||||
|
width, height,
|
||||||
|
java.awt.image.BufferedImage.TYPE_INT_RGB
|
||||||
|
);
|
||||||
|
|
||||||
|
for (var y = 0; y < height; y++) {
|
||||||
|
for (var x = 0; x < width; x++) {
|
||||||
|
var rgb = image.getRGB(x, y);
|
||||||
|
var r = (rgb >> 16) & 0xFF;
|
||||||
|
var g = (rgb >> 8) & 0xFF;
|
||||||
|
var b = rgb & 0xFF;
|
||||||
|
|
||||||
|
var tr = Math.min(255, (r * 0.393 + g * 0.769 + b * 0.189));
|
||||||
|
var tg = Math.min(255, (r * 0.349 + g * 0.686 + b * 0.168));
|
||||||
|
var tb = Math.min(255, (r * 0.272 + g * 0.534 + b * 0.131));
|
||||||
|
|
||||||
|
result.setRGB(x, y, (tr << 16) | (tg << 8) | tb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyInvert(image) {
|
||||||
|
var width = image.getWidth();
|
||||||
|
var height = image.getHeight();
|
||||||
|
var result = new java.awt.image.BufferedImage(
|
||||||
|
width, height,
|
||||||
|
java.awt.image.BufferedImage.TYPE_INT_RGB
|
||||||
|
);
|
||||||
|
|
||||||
|
for (var y = 0; y < height; y++) {
|
||||||
|
for (var x = 0; x < width; x++) {
|
||||||
|
var rgb = image.getRGB(x, y);
|
||||||
|
result.setRGB(x, y, ~rgb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the selected filter to the input image
|
||||||
|
var filterName = "grayscale"; // Change this to "sepia" or "invert" for different effects
|
||||||
|
var result;
|
||||||
|
|
||||||
|
switch (filterName) {
|
||||||
|
case "grayscale":
|
||||||
|
result = applyGrayscale(inputImage);
|
||||||
|
break;
|
||||||
|
case "sepia":
|
||||||
|
result = applySepia(inputImage);
|
||||||
|
break;
|
||||||
|
case "invert":
|
||||||
|
result = applyInvert(inputImage);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
print("Unknown filter: " + filterName);
|
||||||
|
result = inputImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the filtered image
|
||||||
|
result;
|
||||||
53
scripts/motion_detection.py
Executable file
53
scripts/motion_detection.py
Executable file
@@ -0,0 +1,53 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
def detect_motion(image_path):
|
||||||
|
# Read the input image
|
||||||
|
frame = cv2.imread(image_path)
|
||||||
|
if frame is None:
|
||||||
|
print("Error: Could not read image file")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Convert to grayscale
|
||||||
|
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
||||||
|
|
||||||
|
# Apply Gaussian blur to reduce noise
|
||||||
|
blur = cv2.GaussianBlur(gray, (21, 21), 0)
|
||||||
|
|
||||||
|
# Threshold the image to detect significant changes
|
||||||
|
_, thresh = cv2.threshold(blur, 20, 255, cv2.THRESH_BINARY)
|
||||||
|
|
||||||
|
# Find contours in the thresholded image
|
||||||
|
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||||
|
|
||||||
|
# Filter contours based on area to remove noise
|
||||||
|
min_area = 500
|
||||||
|
motion_detected = False
|
||||||
|
|
||||||
|
for contour in contours:
|
||||||
|
if cv2.contourArea(contour) > min_area:
|
||||||
|
motion_detected = True
|
||||||
|
# Draw rectangle around motion area
|
||||||
|
(x, y, w, h) = cv2.boundingRect(contour)
|
||||||
|
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
|
||||||
|
|
||||||
|
# Save the output image with motion detection boxes
|
||||||
|
output_path = image_path.replace('.jpg', '_motion.jpg')
|
||||||
|
cv2.imwrite(output_path, frame)
|
||||||
|
|
||||||
|
# Return result
|
||||||
|
if motion_detected:
|
||||||
|
print("Motion detected!")
|
||||||
|
print(f"Output saved to: {output_path}")
|
||||||
|
else:
|
||||||
|
print("No motion detected")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) != 2:
|
||||||
|
print("Usage: python motion_detection.py <image_path>")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
detect_motion(sys.argv[1])
|
||||||
11
src/main/java/com/jsca/CameraConfig.java
Normal file
11
src/main/java/com/jsca/CameraConfig.java
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package com.jsca;
|
||||||
|
|
||||||
|
public class CameraConfig {
|
||||||
|
public int position;
|
||||||
|
public String name;
|
||||||
|
public String type; // "local" or "network"
|
||||||
|
public String url; // for network cameras
|
||||||
|
public String username;
|
||||||
|
public String password;
|
||||||
|
public int deviceIndex; // for local cameras
|
||||||
|
}
|
||||||
@@ -1,134 +1,351 @@
|
|||||||
package com.jsca;
|
package com.jsca;
|
||||||
|
|
||||||
|
import com.github.sarxos.webcam.Webcam;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.*;
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
public class CameraManager {
|
public class CameraManager {
|
||||||
private final Map<Integer, CameraPanel> cameras = new HashMap<>();
|
private final Map<Integer, CameraPanel> cameraPanels;
|
||||||
private final JPanel gridPanel;
|
private final Map<Integer, Future<?>> cameraThreads;
|
||||||
private static final int MAX_CAMERAS = 4;
|
private final ExecutorService executorService;
|
||||||
private static final String CONFIG_FILE = "camera_config.json";
|
|
||||||
private final Gson gson;
|
private final Gson gson;
|
||||||
|
private final JPanel gridPanel;
|
||||||
|
|
||||||
public CameraManager(JPanel gridPanel) {
|
public CameraManager(JPanel gridPanel) {
|
||||||
this.gridPanel = gridPanel;
|
this.cameraPanels = new HashMap<>();
|
||||||
|
this.cameraThreads = new HashMap<>();
|
||||||
|
this.executorService = Executors.newCachedThreadPool();
|
||||||
this.gson = new GsonBuilder().setPrettyPrinting().create();
|
this.gson = new GsonBuilder().setPrettyPrinting().create();
|
||||||
|
this.gridPanel = gridPanel;
|
||||||
|
// Do not add any panels initially
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize empty panels
|
public void registerPanel(int position, CameraPanel panel) {
|
||||||
for (int i = 0; i < MAX_CAMERAS; i++) {
|
if (position >= 0 && position < 4) {
|
||||||
CameraPanel emptyPanel = new CameraPanel("Empty " + (i + 1));
|
cameraPanels.put(position, panel);
|
||||||
cameras.put(i, emptyPanel);
|
|
||||||
gridPanel.add(emptyPanel);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean addCamera(String name, StreamReader streamReader, int position) {
|
public void addLocalCamera() {
|
||||||
if (position < 0 || position >= MAX_CAMERAS) {
|
// Find the first inactive panel, or add a new one if less than 4 exist
|
||||||
return false;
|
CameraPanel targetPanel = null;
|
||||||
|
int position = -1;
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
CameraPanel panel = cameraPanels.get(i);
|
||||||
|
if (panel != null && !panel.isActive()) {
|
||||||
|
targetPanel = panel;
|
||||||
|
position = i;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
CameraPanel oldPanel = cameras.get(position);
|
|
||||||
if (oldPanel != null) {
|
|
||||||
oldPanel.stopStream();
|
|
||||||
}
|
}
|
||||||
|
if (targetPanel == null) {
|
||||||
CameraPanel newPanel = new CameraPanel(name);
|
if (cameraPanels.size() < 4) {
|
||||||
newPanel.startStream(streamReader);
|
position = getNextAvailablePosition();
|
||||||
|
targetPanel = new CameraPanel(position);
|
||||||
cameras.put(position, newPanel);
|
cameraPanels.put(position, targetPanel);
|
||||||
gridPanel.remove(position);
|
gridPanel.add(targetPanel);
|
||||||
gridPanel.add(newPanel, position);
|
|
||||||
gridPanel.revalidate();
|
gridPanel.revalidate();
|
||||||
gridPanel.repaint();
|
gridPanel.repaint();
|
||||||
|
} else {
|
||||||
return true;
|
JOptionPane.showMessageDialog(null, "All camera panels are active", "Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Webcam[] webcams = Webcam.getWebcams().toArray(new Webcam[0]);
|
||||||
|
if (webcams.length == 0) {
|
||||||
|
JOptionPane.showMessageDialog(null, "No webcams found", "Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String[] options = new String[webcams.length];
|
||||||
|
for (int i = 0; i < webcams.length; i++) {
|
||||||
|
options[i] = webcams[i].getName();
|
||||||
|
}
|
||||||
|
String selected = (String) JOptionPane.showInputDialog(null,
|
||||||
|
"Select webcam:", "Add Local Camera",
|
||||||
|
JOptionPane.QUESTION_MESSAGE, null,
|
||||||
|
options, options[0]);
|
||||||
|
if (selected != null) {
|
||||||
|
int index = -1;
|
||||||
|
for (int i = 0; i < options.length; i++) {
|
||||||
|
if (options[i].equals(selected)) {
|
||||||
|
index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (index >= 0) {
|
||||||
|
startLocalCamera(position, webcams[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeCamera(int position) {
|
public void addNetworkCamera() {
|
||||||
if (position >= 0 && position < MAX_CAMERAS) {
|
// Find the first inactive panel, or add a new one if less than 4 exist
|
||||||
CameraPanel panel = cameras.get(position);
|
CameraPanel targetPanel = null;
|
||||||
|
int position = -1;
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
CameraPanel panel = cameraPanels.get(i);
|
||||||
|
if (panel != null && !panel.isActive()) {
|
||||||
|
targetPanel = panel;
|
||||||
|
position = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (targetPanel == null) {
|
||||||
|
if (cameraPanels.size() < 4) {
|
||||||
|
position = getNextAvailablePosition();
|
||||||
|
targetPanel = new CameraPanel(position);
|
||||||
|
cameraPanels.put(position, targetPanel);
|
||||||
|
gridPanel.add(targetPanel);
|
||||||
|
gridPanel.revalidate();
|
||||||
|
gridPanel.repaint();
|
||||||
|
} else {
|
||||||
|
JOptionPane.showMessageDialog(null, "All camera panels are active", "Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JPanel form = new JPanel(new GridLayout(0, 2, 5, 5));
|
||||||
|
JTextField urlField = new JTextField(20);
|
||||||
|
JTextField userField = new JTextField(20);
|
||||||
|
JPasswordField passField = new JPasswordField(20);
|
||||||
|
JCheckBox isDroidcamBox = new JCheckBox();
|
||||||
|
form.add(new JLabel("Camera URL:"));
|
||||||
|
form.add(urlField);
|
||||||
|
form.add(new JLabel("Is DroidCam:"));
|
||||||
|
form.add(isDroidcamBox);
|
||||||
|
form.add(new JLabel("Username:"));
|
||||||
|
form.add(userField);
|
||||||
|
form.add(new JLabel("Password:"));
|
||||||
|
form.add(passField);
|
||||||
|
isDroidcamBox.addActionListener(e -> {
|
||||||
|
if (isDroidcamBox.isSelected() && urlField.getText().isEmpty()) {
|
||||||
|
urlField.setText("http://");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
int result = JOptionPane.showConfirmDialog(null, form,
|
||||||
|
"Add Network Camera", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
|
||||||
|
if (result == JOptionPane.OK_OPTION) {
|
||||||
|
String url = urlField.getText().trim();
|
||||||
|
String username = userField.getText().trim();
|
||||||
|
String password = new String(passField.getPassword()).trim();
|
||||||
|
if (url.isEmpty()) {
|
||||||
|
JOptionPane.showMessageDialog(null,
|
||||||
|
"Please enter a camera URL",
|
||||||
|
"Error",
|
||||||
|
JOptionPane.ERROR_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isDroidcamBox.isSelected()) {
|
||||||
|
url = url.replaceAll("/$", "");
|
||||||
|
if (!url.endsWith("/video")) {
|
||||||
|
url += "/video";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
startNetworkCamera(position, url,
|
||||||
|
username.isEmpty() ? null : username,
|
||||||
|
password.isEmpty() ? null : password);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeSelectedCamera() {
|
||||||
|
// Remove the first camera (if any)
|
||||||
|
if (cameraPanels.isEmpty()) {
|
||||||
|
JOptionPane.showMessageDialog(null, "No cameras to remove", "Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int firstKey = cameraPanels.keySet().iterator().next();
|
||||||
|
stopCamera(firstKey);
|
||||||
|
CameraPanel panel = cameraPanels.remove(firstKey);
|
||||||
if (panel != null) {
|
if (panel != null) {
|
||||||
panel.stopStream();
|
gridPanel.remove(panel);
|
||||||
CameraPanel emptyPanel = new CameraPanel("Empty " + (position + 1));
|
|
||||||
cameras.put(position, emptyPanel);
|
|
||||||
gridPanel.remove(position);
|
|
||||||
gridPanel.add(emptyPanel, position);
|
|
||||||
gridPanel.revalidate();
|
gridPanel.revalidate();
|
||||||
gridPanel.repaint();
|
gridPanel.repaint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void restartSelectedCamera() {
|
||||||
|
// Restart the first camera (if any)
|
||||||
|
if (cameraPanels.isEmpty()) {
|
||||||
|
JOptionPane.showMessageDialog(null, "No cameras to restart", "Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int firstKey = cameraPanels.keySet().iterator().next();
|
||||||
|
CameraPanel panel = cameraPanels.get(firstKey);
|
||||||
|
if (panel != null && panel.isActive()) {
|
||||||
|
stopCamera(firstKey);
|
||||||
|
// If the camera was local or network, restart accordingly
|
||||||
|
String name = panel.getCameraName();
|
||||||
|
if (name.startsWith("Local: ")) {
|
||||||
|
// Try to find the webcam by name
|
||||||
|
String webcamName = name.substring(7);
|
||||||
|
for (com.github.sarxos.webcam.Webcam webcam : com.github.sarxos.webcam.Webcam.getWebcams()) {
|
||||||
|
if (webcam.getName().equals(webcamName)) {
|
||||||
|
startLocalCamera(firstKey, webcam);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (name.startsWith("Network: ")) {
|
||||||
|
// Try to restart the network camera (URL only, no auth)
|
||||||
|
String url = name.substring(9);
|
||||||
|
startNetworkCamera(firstKey, url, null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stopAllCameras() {
|
private void startLocalCamera(int position, Webcam webcam) {
|
||||||
for (CameraPanel panel : cameras.values()) {
|
stopCamera(position);
|
||||||
|
CameraPanel panel = cameraPanels.get(position);
|
||||||
|
panel.setActive(true);
|
||||||
|
panel.setCameraName("Local: " + webcam.getName());
|
||||||
|
|
||||||
|
Future<?> future = executorService.submit(() -> {
|
||||||
|
try {
|
||||||
|
webcam.open();
|
||||||
|
while (!Thread.interrupted()) {
|
||||||
|
BufferedImage image = webcam.getImage();
|
||||||
|
if (image != null) {
|
||||||
|
SwingUtilities.invokeLater(() -> panel.updateFrame(image));
|
||||||
|
}
|
||||||
|
Thread.sleep(33); // ~30 FPS
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
webcam.close();
|
||||||
|
SwingUtilities.invokeLater(() -> panel.setActive(false));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cameraThreads.put(position, future);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startNetworkCamera(int position, String urlString, String username, String password) {
|
||||||
|
stopCamera(position);
|
||||||
|
CameraPanel panel = cameraPanels.get(position);
|
||||||
|
panel.setActive(true);
|
||||||
|
panel.setCameraName("Network: " + urlString);
|
||||||
|
|
||||||
|
// For DroidCam, append video feed path if not present
|
||||||
|
if (urlString.contains("droidcam") && !urlString.endsWith("/video")) {
|
||||||
|
urlString = urlString.replaceAll("/$", "") + "/video";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and start the network stream reader
|
||||||
|
NetworkStreamReader streamReader = new NetworkStreamReader(panel, urlString, username, password);
|
||||||
|
Future<?> future = executorService.submit(streamReader);
|
||||||
|
cameraThreads.put(position, future);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopCamera(int position) {
|
||||||
|
Future<?> future = cameraThreads.get(position);
|
||||||
|
if (future != null) {
|
||||||
|
future.cancel(true);
|
||||||
|
cameraThreads.remove(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
CameraPanel panel = cameraPanels.get(position);
|
||||||
if (panel != null) {
|
if (panel != null) {
|
||||||
panel.stopStream();
|
panel.setActive(false);
|
||||||
}
|
panel.setCameraName("Camera " + (position + 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public CameraPanel getCameraPanel(int position) {
|
public void shutdown() {
|
||||||
return cameras.get(position);
|
for (Future<?> future : cameraThreads.values()) {
|
||||||
}
|
future.cancel(true);
|
||||||
|
|
||||||
public void restartCamera(int position) {
|
|
||||||
CameraPanel panel = cameras.get(position);
|
|
||||||
if (panel != null && panel.isStreaming()) {
|
|
||||||
StreamReader currentStream = panel.getCurrentStream();
|
|
||||||
if (currentStream != null) {
|
|
||||||
panel.stopStream();
|
|
||||||
panel.startStream(currentStream);
|
|
||||||
}
|
}
|
||||||
|
cameraThreads.clear();
|
||||||
|
executorService.shutdownNow();
|
||||||
|
try {
|
||||||
|
executorService.awaitTermination(5, TimeUnit.SECONDS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveConfiguration() {
|
public void saveConfiguration() {
|
||||||
|
JFileChooser fileChooser = new JFileChooser();
|
||||||
|
fileChooser.setDialogTitle("Save Camera Configuration");
|
||||||
|
fileChooser.setSelectedFile(new File("camera_config.json"));
|
||||||
|
|
||||||
|
if (fileChooser.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) {
|
||||||
|
try (FileWriter writer = new FileWriter(fileChooser.getSelectedFile())) {
|
||||||
List<CameraConfig> configs = new ArrayList<>();
|
List<CameraConfig> configs = new ArrayList<>();
|
||||||
for (Map.Entry<Integer, CameraPanel> entry : cameras.entrySet()) {
|
for (Map.Entry<Integer, CameraPanel> entry : cameraPanels.entrySet()) {
|
||||||
CameraPanel panel = entry.getValue();
|
CameraPanel panel = entry.getValue();
|
||||||
if (panel.isStreaming()) {
|
if (panel.isActive()) {
|
||||||
CameraConfig config = new CameraConfig();
|
CameraConfig config = new CameraConfig();
|
||||||
config.position = entry.getKey();
|
config.position = entry.getKey();
|
||||||
config.name = panel.getCameraName();
|
config.name = panel.getCameraName();
|
||||||
// Add more configuration details as needed
|
|
||||||
configs.add(config);
|
configs.add(config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try (Writer writer = new FileWriter(CONFIG_FILE)) {
|
|
||||||
gson.toJson(configs, writer);
|
gson.toJson(configs, writer);
|
||||||
|
JOptionPane.showMessageDialog(null,
|
||||||
|
"Configuration saved successfully",
|
||||||
|
"Success",
|
||||||
|
JOptionPane.INFORMATION_MESSAGE);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
JOptionPane.showMessageDialog(null,
|
JOptionPane.showMessageDialog(null,
|
||||||
"Error saving configuration: " + e.getMessage(),
|
"Error saving configuration: " + e.getMessage(),
|
||||||
"Save Error",
|
"Error",
|
||||||
JOptionPane.ERROR_MESSAGE);
|
JOptionPane.ERROR_MESSAGE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void loadConfiguration() {
|
public void loadConfiguration() {
|
||||||
try (Reader reader = new FileReader(CONFIG_FILE)) {
|
JFileChooser fileChooser = new JFileChooser();
|
||||||
|
fileChooser.setDialogTitle("Load Camera Configuration");
|
||||||
|
|
||||||
|
if (fileChooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
|
||||||
|
try (FileReader reader = new FileReader(fileChooser.getSelectedFile())) {
|
||||||
List<CameraConfig> configs = gson.fromJson(reader,
|
List<CameraConfig> configs = gson.fromJson(reader,
|
||||||
new TypeToken<List<CameraConfig>>(){}.getType());
|
new TypeToken<List<CameraConfig>>(){}.getType());
|
||||||
|
|
||||||
if (configs != null) {
|
if (configs != null) {
|
||||||
stopAllCameras();
|
// Stop all current cameras
|
||||||
|
for (int position : cameraPanels.keySet()) {
|
||||||
|
stopCamera(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load new configuration
|
||||||
for (CameraConfig config : configs) {
|
for (CameraConfig config : configs) {
|
||||||
// Implement camera restoration based on config
|
if ("local".equals(config.type)) {
|
||||||
// This is a placeholder for the actual implementation
|
// TODO: Implement local camera restoration
|
||||||
// You'll need to create appropriate StreamReader instances
|
} else if ("network".equals(config.type)) {
|
||||||
|
startNetworkCamera(config.position, config.url, config.username, config.password);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
JOptionPane.showMessageDialog(null,
|
JOptionPane.showMessageDialog(null,
|
||||||
"Error loading configuration: " + e.getMessage(),
|
"Error loading configuration: " + e.getMessage(),
|
||||||
"Load Error",
|
"Error",
|
||||||
JOptionPane.ERROR_MESSAGE);
|
JOptionPane.ERROR_MESSAGE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getNextAvailablePosition() {
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
if (!cameraPanels.containsKey(i)) return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
private static class CameraConfig {
|
private static class CameraConfig {
|
||||||
int position;
|
int position;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.jsca;
|
package com.jsca;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
import javax.swing.border.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -9,64 +10,72 @@ import java.time.format.DateTimeFormatter;
|
|||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
public class CameraPanel extends JPanel {
|
public class CameraPanel extends JPanel {
|
||||||
private final JLabel videoLabel;
|
private final int position;
|
||||||
private final String cameraName;
|
private BufferedImage currentFrame;
|
||||||
private volatile boolean isStreaming = false;
|
private String cameraName;
|
||||||
private StreamReader currentStream;
|
private boolean isActive;
|
||||||
|
private boolean isSelected;
|
||||||
|
private static final Color SELECTED_BORDER_COLOR = new Color(0, 120, 215);
|
||||||
|
private static final Color BACKGROUND_COLOR = Color.WHITE;
|
||||||
|
private static final Color PLACEHOLDER_COLOR = new Color(245, 245, 245);
|
||||||
|
private static final Color TEXT_COLOR = new Color(100, 100, 100);
|
||||||
|
private static final int BORDER_THICKNESS = 2;
|
||||||
|
|
||||||
public CameraPanel(String cameraName) {
|
public CameraPanel(int position) {
|
||||||
this.cameraName = cameraName;
|
this.position = position;
|
||||||
setLayout(new BorderLayout());
|
this.isActive = false;
|
||||||
|
this.isSelected = false;
|
||||||
// Create video display panel
|
this.cameraName = String.format("Camera %d", position + 1);
|
||||||
videoLabel = new JLabel();
|
setPreferredSize(new Dimension(320, 240));
|
||||||
videoLabel.setPreferredSize(new Dimension(640, 480));
|
setBorder(BorderFactory.createLineBorder(new Color(220, 220, 220), 1));
|
||||||
videoLabel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
|
setBackground(BACKGROUND_COLOR);
|
||||||
add(videoLabel, BorderLayout.CENTER);
|
|
||||||
|
|
||||||
// Create control panel
|
|
||||||
JPanel controlPanel = new JPanel();
|
|
||||||
controlPanel.setLayout(new FlowLayout());
|
|
||||||
|
|
||||||
JButton snapshotButton = new JButton("Snapshot");
|
|
||||||
snapshotButton.addActionListener(e -> takeSnapshot());
|
|
||||||
controlPanel.add(snapshotButton);
|
|
||||||
|
|
||||||
add(controlPanel, BorderLayout.SOUTH);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startStream(StreamReader streamReader) {
|
public void setSelected(boolean selected) {
|
||||||
stopStream(); // Stop any existing stream
|
this.isSelected = selected;
|
||||||
currentStream = streamReader;
|
setBorder(createPanelBorder(selected));
|
||||||
new Thread(currentStream).start();
|
repaint();
|
||||||
isStreaming = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stopStream() {
|
public boolean isSelected() {
|
||||||
if (currentStream != null) {
|
return isSelected;
|
||||||
currentStream.stop();
|
|
||||||
currentStream = null;
|
|
||||||
}
|
|
||||||
isStreaming = false;
|
|
||||||
videoLabel.setIcon(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void takeSnapshot() {
|
public void updateFrame(BufferedImage frame) {
|
||||||
if (videoLabel.getIcon() == null) return;
|
this.currentFrame = frame;
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActive(boolean active) {
|
||||||
|
this.isActive = active;
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isActive() {
|
||||||
|
return isActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCameraName(String name) {
|
||||||
|
this.cameraName = name;
|
||||||
|
setBorder(createPanelBorder(isSelected));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCameraName() {
|
||||||
|
return cameraName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPosition() {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void takeSnapshot() {
|
||||||
|
if (currentFrame != null) {
|
||||||
try {
|
try {
|
||||||
BufferedImage image = new BufferedImage(
|
|
||||||
videoLabel.getWidth(),
|
|
||||||
videoLabel.getHeight(),
|
|
||||||
BufferedImage.TYPE_INT_RGB
|
|
||||||
);
|
|
||||||
Graphics g = image.getGraphics();
|
|
||||||
videoLabel.paint(g);
|
|
||||||
g.dispose();
|
|
||||||
|
|
||||||
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
|
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
|
||||||
File outputFile = new File(String.format("snapshot_%s_%s.jpg", cameraName.replaceAll("[^a-zA-Z0-9]", "_"), timestamp));
|
File outputFile = new File(String.format("snapshot_%s_%s.jpg",
|
||||||
ImageIO.write(image, "jpg", outputFile);
|
cameraName.replaceAll("[^a-zA-Z0-9]", "_"),
|
||||||
|
timestamp));
|
||||||
|
ImageIO.write(currentFrame, "jpg", outputFile);
|
||||||
JOptionPane.showMessageDialog(this,
|
JOptionPane.showMessageDialog(this,
|
||||||
"Snapshot saved: " + outputFile.getName(),
|
"Snapshot saved: " + outputFile.getName(),
|
||||||
"Snapshot Saved",
|
"Snapshot Saved",
|
||||||
@@ -78,22 +87,53 @@ public class CameraPanel extends JPanel {
|
|||||||
JOptionPane.ERROR_MESSAGE);
|
JOptionPane.ERROR_MESSAGE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void updateFrame(Image frame) {
|
@Override
|
||||||
if (frame != null) {
|
protected void paintComponent(Graphics g) {
|
||||||
videoLabel.setIcon(new ImageIcon(frame));
|
super.paintComponent(g);
|
||||||
|
Graphics2D g2d = (Graphics2D) g;
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||||
|
if (currentFrame != null && isActive) {
|
||||||
|
double scale = Math.min(
|
||||||
|
(double) getWidth() / currentFrame.getWidth(),
|
||||||
|
(double) getHeight() / currentFrame.getHeight()
|
||||||
|
);
|
||||||
|
scale = Math.min(scale, 1.0); // Don't scale up, only down
|
||||||
|
int scaledWidth = (int) (currentFrame.getWidth() * scale);
|
||||||
|
int scaledHeight = (int) (currentFrame.getHeight() * scale);
|
||||||
|
int x = (getWidth() - scaledWidth) / 2;
|
||||||
|
int y = (getHeight() - scaledHeight) / 2;
|
||||||
|
g2d.drawImage(currentFrame, x, y, scaledWidth, scaledHeight, null);
|
||||||
|
} else {
|
||||||
|
g2d.setColor(BACKGROUND_COLOR);
|
||||||
|
g2d.fillRect(0, 0, getWidth(), getHeight());
|
||||||
|
g2d.setColor(Color.BLACK);
|
||||||
|
g2d.setFont(new Font("Arial", Font.BOLD, 32));
|
||||||
|
String text = cameraName;
|
||||||
|
FontMetrics fm = g2d.getFontMetrics();
|
||||||
|
int textX = (getWidth() - fm.stringWidth(text)) / 2;
|
||||||
|
int textY = (getHeight() + fm.getAscent()) / 2 - 10;
|
||||||
|
g2d.drawString(text, textX, textY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCameraName() {
|
private Border createPanelBorder(boolean selected) {
|
||||||
return cameraName;
|
Border lineBorder = BorderFactory.createLineBorder(
|
||||||
}
|
selected ? SELECTED_BORDER_COLOR : new Color(200, 200, 200),
|
||||||
|
selected ? BORDER_THICKNESS : 1
|
||||||
public boolean isStreaming() {
|
);
|
||||||
return isStreaming;
|
Border emptyBorder = BorderFactory.createEmptyBorder(8, 8, 8, 8);
|
||||||
}
|
TitledBorder titleBorder = BorderFactory.createTitledBorder(
|
||||||
|
lineBorder,
|
||||||
public StreamReader getCurrentStream() {
|
cameraName,
|
||||||
return currentStream;
|
TitledBorder.LEFT,
|
||||||
|
TitledBorder.TOP,
|
||||||
|
new Font("Arial", Font.PLAIN, 12),
|
||||||
|
selected ? SELECTED_BORDER_COLOR : TEXT_COLOR
|
||||||
|
);
|
||||||
|
titleBorder.setTitlePosition(TitledBorder.ABOVE_TOP);
|
||||||
|
return BorderFactory.createCompoundBorder(emptyBorder, titleBorder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,7 @@ public class CameraViewer extends JFrame {
|
|||||||
addWindowListener(new WindowAdapter() {
|
addWindowListener(new WindowAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void windowClosing(WindowEvent e) {
|
public void windowClosing(WindowEvent e) {
|
||||||
cameraManager.stopAllCameras();
|
cameraManager.shutdown();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ public class CameraViewer extends JFrame {
|
|||||||
loadSetup.addActionListener(e -> cameraManager.loadConfiguration());
|
loadSetup.addActionListener(e -> cameraManager.loadConfiguration());
|
||||||
saveSetup.addActionListener(e -> cameraManager.saveConfiguration());
|
saveSetup.addActionListener(e -> cameraManager.saveConfiguration());
|
||||||
exit.addActionListener(e -> {
|
exit.addActionListener(e -> {
|
||||||
cameraManager.stopAllCameras();
|
cameraManager.shutdown();
|
||||||
dispose();
|
dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -74,10 +74,10 @@ public class CameraViewer extends JFrame {
|
|||||||
JMenuItem removeCamera = new JMenuItem("Remove Camera");
|
JMenuItem removeCamera = new JMenuItem("Remove Camera");
|
||||||
JMenuItem restartCamera = new JMenuItem("Restart Camera");
|
JMenuItem restartCamera = new JMenuItem("Restart Camera");
|
||||||
|
|
||||||
addNetwork.addActionListener(e -> addNetworkCamera());
|
addNetwork.addActionListener(e -> cameraManager.addNetworkCamera());
|
||||||
addLocal.addActionListener(e -> addLocalCamera());
|
addLocal.addActionListener(e -> cameraManager.addLocalCamera());
|
||||||
removeCamera.addActionListener(e -> removeSelectedCamera());
|
removeCamera.addActionListener(e -> cameraManager.removeSelectedCamera());
|
||||||
restartCamera.addActionListener(e -> restartSelectedCamera());
|
restartCamera.addActionListener(e -> cameraManager.restartSelectedCamera());
|
||||||
|
|
||||||
cameraMenu.add(addNetwork);
|
cameraMenu.add(addNetwork);
|
||||||
cameraMenu.add(addLocal);
|
cameraMenu.add(addLocal);
|
||||||
@@ -103,115 +103,6 @@ public class CameraViewer extends JFrame {
|
|||||||
return menuBar;
|
return menuBar;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addNetworkCamera() {
|
|
||||||
JPanel panel = new JPanel(new GridLayout(0, 2, 5, 5));
|
|
||||||
JTextField urlField = new JTextField();
|
|
||||||
JTextField usernameField = new JTextField();
|
|
||||||
JPasswordField passwordField = new JPasswordField();
|
|
||||||
SpinnerNumberModel positionModel = new SpinnerNumberModel(0, 0, 3, 1);
|
|
||||||
JSpinner positionSpinner = new JSpinner(positionModel);
|
|
||||||
|
|
||||||
panel.add(new JLabel("URL:"));
|
|
||||||
panel.add(urlField);
|
|
||||||
panel.add(new JLabel("Username:"));
|
|
||||||
panel.add(usernameField);
|
|
||||||
panel.add(new JLabel("Password:"));
|
|
||||||
panel.add(passwordField);
|
|
||||||
panel.add(new JLabel("Position (0-3):"));
|
|
||||||
panel.add(positionSpinner);
|
|
||||||
|
|
||||||
int result = JOptionPane.showConfirmDialog(this, panel,
|
|
||||||
"Add Network Camera", JOptionPane.OK_CANCEL_OPTION);
|
|
||||||
|
|
||||||
if (result == JOptionPane.OK_OPTION) {
|
|
||||||
String url = urlField.getText();
|
|
||||||
int position = (Integer) positionSpinner.getValue();
|
|
||||||
|
|
||||||
if (!url.trim().isEmpty()) {
|
|
||||||
String name = "Network Camera " + (position + 1);
|
|
||||||
NetworkStreamReader reader = new NetworkStreamReader(
|
|
||||||
cameraManager.getCameraPanel(position),
|
|
||||||
url
|
|
||||||
);
|
|
||||||
cameraManager.addCamera(name, reader, position);
|
|
||||||
updateStatus("Added network camera at position " + position);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addLocalCamera() {
|
|
||||||
java.util.List<Webcam> webcams = Webcam.getWebcams();
|
|
||||||
if (webcams.isEmpty()) {
|
|
||||||
JOptionPane.showMessageDialog(this,
|
|
||||||
"No webcams detected",
|
|
||||||
"Error",
|
|
||||||
JOptionPane.ERROR_MESSAGE);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
JPanel panel = new JPanel(new GridLayout(0, 2, 5, 5));
|
|
||||||
String[] options = webcams.stream()
|
|
||||||
.map(Webcam::getName)
|
|
||||||
.toArray(String[]::new);
|
|
||||||
JComboBox<String> webcamBox = new JComboBox<>(options);
|
|
||||||
SpinnerNumberModel positionModel = new SpinnerNumberModel(0, 0, 3, 1);
|
|
||||||
JSpinner positionSpinner = new JSpinner(positionModel);
|
|
||||||
|
|
||||||
panel.add(new JLabel("Select Webcam:"));
|
|
||||||
panel.add(webcamBox);
|
|
||||||
panel.add(new JLabel("Position (0-3):"));
|
|
||||||
panel.add(positionSpinner);
|
|
||||||
|
|
||||||
int result = JOptionPane.showConfirmDialog(this, panel,
|
|
||||||
"Add Local Camera", JOptionPane.OK_CANCEL_OPTION);
|
|
||||||
|
|
||||||
if (result == JOptionPane.OK_OPTION) {
|
|
||||||
String selected = (String) webcamBox.getSelectedItem();
|
|
||||||
int position = (Integer) positionSpinner.getValue();
|
|
||||||
|
|
||||||
if (selected != null) {
|
|
||||||
String name = "Local Camera " + (position + 1) + " (" + selected + ")";
|
|
||||||
WebcamStreamReader reader = new WebcamStreamReader(
|
|
||||||
cameraManager.getCameraPanel(position)
|
|
||||||
);
|
|
||||||
cameraManager.addCamera(name, reader, position);
|
|
||||||
updateStatus("Added local camera at position " + position);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void removeSelectedCamera() {
|
|
||||||
SpinnerNumberModel positionModel = new SpinnerNumberModel(0, 0, 3, 1);
|
|
||||||
JSpinner positionSpinner = new JSpinner(positionModel);
|
|
||||||
|
|
||||||
int result = JOptionPane.showConfirmDialog(this,
|
|
||||||
positionSpinner,
|
|
||||||
"Select Camera Position to Remove (0-3)",
|
|
||||||
JOptionPane.OK_CANCEL_OPTION);
|
|
||||||
|
|
||||||
if (result == JOptionPane.OK_OPTION) {
|
|
||||||
int position = (Integer) positionSpinner.getValue();
|
|
||||||
cameraManager.removeCamera(position);
|
|
||||||
updateStatus("Removed camera at position " + position);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void restartSelectedCamera() {
|
|
||||||
SpinnerNumberModel positionModel = new SpinnerNumberModel(0, 0, 3, 1);
|
|
||||||
JSpinner positionSpinner = new JSpinner(positionModel);
|
|
||||||
|
|
||||||
int result = JOptionPane.showConfirmDialog(this,
|
|
||||||
positionSpinner,
|
|
||||||
"Select Camera Position to Restart (0-3)",
|
|
||||||
JOptionPane.OK_CANCEL_OPTION);
|
|
||||||
|
|
||||||
if (result == JOptionPane.OK_OPTION) {
|
|
||||||
int position = (Integer) positionSpinner.getValue();
|
|
||||||
cameraManager.restartCamera(position);
|
|
||||||
updateStatus("Restarted camera at position " + position);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showAboutDialog() {
|
private void showAboutDialog() {
|
||||||
JOptionPane.showMessageDialog(this,
|
JOptionPane.showMessageDialog(this,
|
||||||
"Security Camera Viewer\nVersion 1.0\n\n" +
|
"Security Camera Viewer\nVersion 1.0\n\n" +
|
||||||
@@ -225,10 +116,9 @@ public class CameraViewer extends JFrame {
|
|||||||
JOptionPane.showMessageDialog(this,
|
JOptionPane.showMessageDialog(this,
|
||||||
"Instructions:\n\n" +
|
"Instructions:\n\n" +
|
||||||
"1. Use the Camera menu to add network or local cameras\n" +
|
"1. Use the Camera menu to add network or local cameras\n" +
|
||||||
"2. Select a position (0-3) for each camera\n" +
|
"2. Select a camera panel by clicking on it\n" +
|
||||||
"3. Use the File menu to save/load your camera setup\n" +
|
"3. Use the File menu to save/load your camera setup\n" +
|
||||||
"4. Each camera panel has its own snapshot button\n\n" +
|
"4. Network Camera URLs should be in MJPEG format\n" +
|
||||||
"Network Camera URLs should be in MJPEG format\n" +
|
|
||||||
"Example: http://camera-ip:port/video",
|
"Example: http://camera-ip:port/video",
|
||||||
"Instructions",
|
"Instructions",
|
||||||
JOptionPane.INFORMATION_MESSAGE);
|
JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
package com.jsca;
|
package com.jsca;
|
||||||
|
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
|
import javax.swing.UIManager;
|
||||||
|
|
||||||
public class Main {
|
public class Main {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
try {
|
||||||
|
// Set system look and feel
|
||||||
|
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
CameraViewer viewer = new CameraViewer();
|
MainApp app = new MainApp();
|
||||||
viewer.setVisible(true);
|
app.setVisible(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
124
src/main/java/com/jsca/MainApp.java
Normal file
124
src/main/java/com/jsca/MainApp.java
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
package com.jsca;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.WindowAdapter;
|
||||||
|
import java.awt.event.WindowEvent;
|
||||||
|
|
||||||
|
public class MainApp extends JFrame {
|
||||||
|
private final CameraManager cameraManager;
|
||||||
|
private final JPanel cameraGrid;
|
||||||
|
|
||||||
|
public MainApp() {
|
||||||
|
setTitle("Security Camera");
|
||||||
|
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||||
|
setSize(1280, 800);
|
||||||
|
setLocationRelativeTo(null);
|
||||||
|
setBackground(Color.WHITE);
|
||||||
|
|
||||||
|
// Initialize components with proper spacing and white theme
|
||||||
|
cameraGrid = new JPanel(new GridLayout(2, 2, 15, 15));
|
||||||
|
cameraGrid.setBackground(Color.WHITE);
|
||||||
|
cameraGrid.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
|
||||||
|
|
||||||
|
// Create a container panel with white background
|
||||||
|
JPanel containerPanel = new JPanel(new BorderLayout());
|
||||||
|
containerPanel.setBackground(Color.WHITE);
|
||||||
|
containerPanel.add(cameraGrid, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
cameraManager = new CameraManager(cameraGrid);
|
||||||
|
|
||||||
|
// Setup menu bar with modern look
|
||||||
|
JMenuBar menuBar = createMenuBar();
|
||||||
|
menuBar.setBackground(Color.WHITE);
|
||||||
|
setJMenuBar(menuBar);
|
||||||
|
|
||||||
|
// Setup main content
|
||||||
|
setContentPane(containerPanel);
|
||||||
|
|
||||||
|
// Initialize camera panels in order (left to right, top to bottom)
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
CameraPanel panel = new CameraPanel(i);
|
||||||
|
cameraGrid.add(panel);
|
||||||
|
cameraManager.registerPanel(i, panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle window closing
|
||||||
|
addWindowListener(new WindowAdapter() {
|
||||||
|
@Override
|
||||||
|
public void windowClosing(WindowEvent e) {
|
||||||
|
cameraManager.shutdown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private JMenuBar createMenuBar() {
|
||||||
|
JMenuBar menuBar = new JMenuBar();
|
||||||
|
menuBar.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
|
||||||
|
|
||||||
|
// File Menu
|
||||||
|
JMenu fileMenu = createMenu("File");
|
||||||
|
fileMenu.add(createMenuItem("Load Setup", e -> cameraManager.loadConfiguration()));
|
||||||
|
fileMenu.add(createMenuItem("Save Setup", e -> cameraManager.saveConfiguration()));
|
||||||
|
fileMenu.addSeparator();
|
||||||
|
fileMenu.add(createMenuItem("Exit", e -> {
|
||||||
|
cameraManager.shutdown();
|
||||||
|
dispose();
|
||||||
|
System.exit(0);
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Camera Menu
|
||||||
|
JMenu cameraMenu = createMenu("Camera");
|
||||||
|
cameraMenu.add(createMenuItem("Add Network Camera", e -> cameraManager.addNetworkCamera()));
|
||||||
|
cameraMenu.add(createMenuItem("Add Local Camera", e -> cameraManager.addLocalCamera()));
|
||||||
|
cameraMenu.addSeparator();
|
||||||
|
cameraMenu.add(createMenuItem("Remove Camera", e -> cameraManager.removeSelectedCamera()));
|
||||||
|
cameraMenu.add(createMenuItem("Restart Camera", e -> cameraManager.restartSelectedCamera()));
|
||||||
|
|
||||||
|
// Help Menu
|
||||||
|
JMenu helpMenu = createMenu("Help");
|
||||||
|
helpMenu.add(createMenuItem("About", e -> showAboutDialog()));
|
||||||
|
helpMenu.add(createMenuItem("Instructions", e -> showInstructions()));
|
||||||
|
|
||||||
|
menuBar.add(fileMenu);
|
||||||
|
menuBar.add(cameraMenu);
|
||||||
|
menuBar.add(helpMenu);
|
||||||
|
|
||||||
|
return menuBar;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JMenu createMenu(String title) {
|
||||||
|
JMenu menu = new JMenu(title);
|
||||||
|
menu.setFont(new Font("Arial", Font.PLAIN, 12));
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JMenuItem createMenuItem(String title, java.awt.event.ActionListener listener) {
|
||||||
|
JMenuItem item = new JMenuItem(title);
|
||||||
|
item.setFont(new Font("Arial", Font.PLAIN, 12));
|
||||||
|
if (listener != null) {
|
||||||
|
item.addActionListener(listener);
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showAboutDialog() {
|
||||||
|
JOptionPane.showMessageDialog(this,
|
||||||
|
"Security Camera Application\nVersion 1.0\n\n" +
|
||||||
|
"A multi-camera security monitoring application\n" +
|
||||||
|
"Supports both network and local USB cameras",
|
||||||
|
"About",
|
||||||
|
JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showInstructions() {
|
||||||
|
JOptionPane.showMessageDialog(this,
|
||||||
|
"Instructions:\n\n" +
|
||||||
|
"1. Use the Camera menu to add network or local cameras\n" +
|
||||||
|
"2. Use the File menu to save/load your camera setup\n" +
|
||||||
|
"3. Cameras are arranged left to right, top to bottom\n" +
|
||||||
|
"4. No selection is needed; cameras fill the next available slot.",
|
||||||
|
"Instructions",
|
||||||
|
JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ package com.jsca;
|
|||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
import java.awt.Image;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -13,34 +13,89 @@ import java.util.Arrays;
|
|||||||
public class NetworkStreamReader implements StreamReader {
|
public class NetworkStreamReader implements StreamReader {
|
||||||
private final CameraPanel panel;
|
private final CameraPanel panel;
|
||||||
private final String streamUrl;
|
private final String streamUrl;
|
||||||
|
private final String username;
|
||||||
|
private final String password;
|
||||||
private volatile boolean running = true;
|
private volatile boolean running = true;
|
||||||
private HttpURLConnection connection;
|
private HttpURLConnection connection;
|
||||||
|
private static final String BOUNDARY_MARKER = "--";
|
||||||
|
private static final int MAX_HEADER_SIZE = 8192;
|
||||||
|
private static final int BUFFER_SIZE = 8192;
|
||||||
|
private static final int MAX_IMAGE_SIZE = 1024 * 1024; // 1MB
|
||||||
|
|
||||||
public NetworkStreamReader(CameraPanel panel, String streamUrl) {
|
public NetworkStreamReader(CameraPanel panel, String streamUrl, String username, String password) {
|
||||||
this.panel = panel;
|
this.panel = panel;
|
||||||
this.streamUrl = streamUrl;
|
this.streamUrl = streamUrl;
|
||||||
|
this.username = username;
|
||||||
|
this.password = password;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
while (running) {
|
||||||
try {
|
try {
|
||||||
|
connectAndStream();
|
||||||
|
Thread.sleep(1000); // Wait before reconnecting
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
break;
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (running) {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
panel.setActive(false);
|
||||||
|
panel.updateFrame(null);
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
Thread.sleep(5000); // Wait longer on error
|
||||||
|
} catch (InterruptedException ie) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void connectAndStream() throws IOException {
|
||||||
URL url = new URL(streamUrl);
|
URL url = new URL(streamUrl);
|
||||||
connection = (HttpURLConnection) url.openConnection();
|
connection = (HttpURLConnection) url.openConnection();
|
||||||
connection.setConnectTimeout(5000);
|
connection.setConnectTimeout(5000);
|
||||||
connection.setReadTimeout(5000);
|
connection.setReadTimeout(5000);
|
||||||
|
|
||||||
try (BufferedInputStream in = new BufferedInputStream(connection.getInputStream())) {
|
// Add authentication if provided
|
||||||
byte[] buffer = new byte[8192];
|
if (username != null && !username.isEmpty() && password != null) {
|
||||||
int bytesRead;
|
String auth = username + ":" + password;
|
||||||
byte[] imageBuffer = new byte[1024 * 1024]; // 1MB buffer for JPEG
|
String encodedAuth = java.util.Base64.getEncoder().encodeToString(auth.getBytes());
|
||||||
|
connection.setRequestProperty("Authorization", "Basic " + encodedAuth);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For DroidCam compatibility
|
||||||
|
connection.setRequestProperty("User-Agent", "Mozilla/5.0");
|
||||||
|
connection.setRequestProperty("Accept", "multipart/x-mixed-replace;boundary=BOUNDARY");
|
||||||
|
|
||||||
|
try (BufferedInputStream in = new BufferedInputStream(connection.getInputStream(), BUFFER_SIZE)) {
|
||||||
|
byte[] buffer = new byte[BUFFER_SIZE];
|
||||||
|
byte[] imageBuffer = new byte[MAX_IMAGE_SIZE];
|
||||||
int imagePos = 0;
|
int imagePos = 0;
|
||||||
boolean foundHeader = false;
|
boolean foundHeader = false;
|
||||||
|
String boundary = null;
|
||||||
|
|
||||||
while (running) {
|
while (running) {
|
||||||
bytesRead = in.read(buffer);
|
int bytesRead = in.read(buffer);
|
||||||
if (bytesRead == -1) break;
|
if (bytesRead == -1) break;
|
||||||
|
|
||||||
for (int i = 0; i < bytesRead; i++) {
|
for (int i = 0; i < bytesRead; i++) {
|
||||||
|
// Look for boundary if not found yet
|
||||||
|
if (boundary == null && i < bytesRead - 1) {
|
||||||
|
String content = new String(buffer, 0, Math.min(bytesRead, MAX_HEADER_SIZE));
|
||||||
|
int boundaryIndex = content.indexOf("boundary=");
|
||||||
|
if (boundaryIndex != -1) {
|
||||||
|
int endIndex = content.indexOf("\r\n", boundaryIndex);
|
||||||
|
if (endIndex != -1) {
|
||||||
|
boundary = content.substring(boundaryIndex + 9, endIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Look for JPEG header (0xFF, 0xD8)
|
// Look for JPEG header (0xFF, 0xD8)
|
||||||
if (!foundHeader && i < bytesRead - 1) {
|
if (!foundHeader && i < bytesRead - 1) {
|
||||||
if (buffer[i] == (byte) 0xFF && buffer[i + 1] == (byte) 0xD8) {
|
if (buffer[i] == (byte) 0xFF && buffer[i + 1] == (byte) 0xD8) {
|
||||||
@@ -50,6 +105,7 @@ public class NetworkStreamReader implements StreamReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (foundHeader) {
|
if (foundHeader) {
|
||||||
|
if (imagePos < MAX_IMAGE_SIZE) {
|
||||||
imageBuffer[imagePos++] = buffer[i];
|
imageBuffer[imagePos++] = buffer[i];
|
||||||
|
|
||||||
// Look for JPEG footer (0xFF, 0xD9)
|
// Look for JPEG footer (0xFF, 0xD9)
|
||||||
@@ -57,29 +113,18 @@ public class NetworkStreamReader implements StreamReader {
|
|||||||
imageBuffer[imagePos - 2] == (byte) 0xFF &&
|
imageBuffer[imagePos - 2] == (byte) 0xFF &&
|
||||||
imageBuffer[imagePos - 1] == (byte) 0xD9) {
|
imageBuffer[imagePos - 1] == (byte) 0xD9) {
|
||||||
|
|
||||||
// We have a complete JPEG image
|
processImage(Arrays.copyOf(imageBuffer, imagePos));
|
||||||
byte[] imageData = Arrays.copyOf(imageBuffer, imagePos);
|
|
||||||
processImage(imageData);
|
|
||||||
foundHeader = false;
|
foundHeader = false;
|
||||||
|
imagePos = 0;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
if (imagePos >= imageBuffer.length) {
|
|
||||||
// Buffer overflow, reset
|
// Buffer overflow, reset
|
||||||
foundHeader = false;
|
foundHeader = false;
|
||||||
|
imagePos = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
if (running) {
|
|
||||||
SwingUtilities.invokeLater(() -> {
|
|
||||||
javax.swing.JOptionPane.showMessageDialog(panel,
|
|
||||||
"Error reading from network camera: " + e.getMessage(),
|
|
||||||
"Network Camera Error",
|
|
||||||
javax.swing.JOptionPane.ERROR_MESSAGE);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
if (connection != null) {
|
if (connection != null) {
|
||||||
connection.disconnect();
|
connection.disconnect();
|
||||||
@@ -89,12 +134,14 @@ public class NetworkStreamReader implements StreamReader {
|
|||||||
|
|
||||||
private void processImage(byte[] imageData) {
|
private void processImage(byte[] imageData) {
|
||||||
try {
|
try {
|
||||||
Image image = ImageIO.read(new ByteArrayInputStream(imageData));
|
BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageData));
|
||||||
if (image != null) {
|
if (image != null) {
|
||||||
SwingUtilities.invokeLater(() -> panel.updateFrame(image));
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
panel.setActive(true);
|
||||||
|
panel.updateFrame(image);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
Thread.sleep(33); // ~30 FPS
|
} catch (IOException e) {
|
||||||
} catch (IOException | InterruptedException e) {
|
|
||||||
// Ignore individual frame errors
|
// Ignore individual frame errors
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
112
src/main/java/com/jsca/ScriptRunner.java
Normal file
112
src/main/java/com/jsca/ScriptRunner.java
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
package com.jsca;
|
||||||
|
|
||||||
|
import javax.script.ScriptEngine;
|
||||||
|
import javax.script.ScriptEngineManager;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
public class ScriptRunner {
|
||||||
|
private static final String TEMP_DIR = System.getProperty("java.io.tmpdir");
|
||||||
|
private final ScriptEngine jsEngine;
|
||||||
|
|
||||||
|
public ScriptRunner() {
|
||||||
|
ScriptEngineManager manager = new ScriptEngineManager();
|
||||||
|
jsEngine = manager.getEngineByName("nashorn");
|
||||||
|
if (jsEngine == null) {
|
||||||
|
System.out.println("Warning: JavaScript engine not available. JavaScript scripts will not work.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletableFuture<String> runPythonScript(String scriptPath, BufferedImage inputImage) {
|
||||||
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
|
try {
|
||||||
|
// Save input image to temp file
|
||||||
|
File inputFile = new File(TEMP_DIR, "input.jpg");
|
||||||
|
ImageIO.write(inputImage, "jpg", inputFile);
|
||||||
|
|
||||||
|
// Create process to run Python script
|
||||||
|
ProcessBuilder pb = new ProcessBuilder(
|
||||||
|
"python3",
|
||||||
|
scriptPath,
|
||||||
|
inputFile.getAbsolutePath()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Redirect error stream to output stream
|
||||||
|
pb.redirectErrorStream(true);
|
||||||
|
|
||||||
|
// Start process and wait for completion
|
||||||
|
Process process = pb.start();
|
||||||
|
String output = new String(process.getInputStream().readAllBytes());
|
||||||
|
int exitCode = process.waitFor();
|
||||||
|
|
||||||
|
// Clean up temp file
|
||||||
|
inputFile.delete();
|
||||||
|
|
||||||
|
if (exitCode != 0) {
|
||||||
|
throw new RuntimeException("Python script failed with exit code " + exitCode + "\n" + output);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Error running Python script", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletableFuture<Object> runJavaScript(String script, BufferedImage inputImage) {
|
||||||
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
|
try {
|
||||||
|
if (jsEngine == null) {
|
||||||
|
throw new RuntimeException("JavaScript engine not available");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the input image available to the script
|
||||||
|
jsEngine.put("inputImage", inputImage);
|
||||||
|
|
||||||
|
// Run the script
|
||||||
|
return jsEngine.eval(script);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Error running JavaScript", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletableFuture<Object> runJavaScriptFile(String scriptPath) {
|
||||||
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
|
try {
|
||||||
|
if (jsEngine == null) {
|
||||||
|
throw new RuntimeException("JavaScript engine not available");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the script file
|
||||||
|
String script = Files.readString(Path.of(scriptPath));
|
||||||
|
|
||||||
|
// Run the script
|
||||||
|
return jsEngine.eval(script);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Error running JavaScript file", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveScript(String script, String filename) {
|
||||||
|
try {
|
||||||
|
File scriptsDir = new File("scripts");
|
||||||
|
if (!scriptsDir.exists()) {
|
||||||
|
scriptsDir.mkdir();
|
||||||
|
}
|
||||||
|
|
||||||
|
File scriptFile = new File(scriptsDir, filename);
|
||||||
|
try (FileWriter writer = new FileWriter(scriptFile)) {
|
||||||
|
writer.write(script);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Error saving script", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ package com.jsca;
|
|||||||
|
|
||||||
import com.github.sarxos.webcam.Webcam;
|
import com.github.sarxos.webcam.Webcam;
|
||||||
import java.awt.Dimension;
|
import java.awt.Dimension;
|
||||||
import java.awt.Image;
|
import java.awt.image.BufferedImage;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
|
|
||||||
public class WebcamStreamReader implements StreamReader {
|
public class WebcamStreamReader implements StreamReader {
|
||||||
@@ -26,7 +26,7 @@ public class WebcamStreamReader implements StreamReader {
|
|||||||
webcam.open();
|
webcam.open();
|
||||||
|
|
||||||
while (running && webcam.isOpen()) {
|
while (running && webcam.isOpen()) {
|
||||||
final Image image = webcam.getImage();
|
final BufferedImage image = webcam.getImage();
|
||||||
if (image != null) {
|
if (image != null) {
|
||||||
SwingUtilities.invokeLater(() -> panel.updateFrame(image));
|
SwingUtilities.invokeLater(() -> panel.updateFrame(image));
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
target/classes/com/jsca/CameraConfig.class
Normal file
BIN
target/classes/com/jsca/CameraConfig.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
target/classes/com/jsca/MainApp$1.class
Normal file
BIN
target/classes/com/jsca/MainApp$1.class
Normal file
Binary file not shown.
BIN
target/classes/com/jsca/MainApp.class
Normal file
BIN
target/classes/com/jsca/MainApp.class
Normal file
Binary file not shown.
Binary file not shown.
BIN
target/classes/com/jsca/ScriptRunner.class
Normal file
BIN
target/classes/com/jsca/ScriptRunner.class
Normal file
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user