This commit is contained in:
rattatwinko
2025-05-27 17:20:56 +02:00
parent cceb0d44e8
commit a95b78bd8f
25 changed files with 965 additions and 357 deletions

View File

@@ -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
View 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
View 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])

View 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
}

View File

@@ -1,135 +1,352 @@
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;
}
} }
if (targetPanel == null) {
CameraPanel oldPanel = cameras.get(position); if (cameraPanels.size() < 4) {
if (oldPanel != null) { position = getNextAvailablePosition();
oldPanel.stopStream(); targetPanel = new CameraPanel(position);
} cameraPanels.put(position, targetPanel);
gridPanel.add(targetPanel);
CameraPanel newPanel = new CameraPanel(name);
newPanel.startStream(streamReader);
cameras.put(position, newPanel);
gridPanel.remove(position);
gridPanel.add(newPanel, position);
gridPanel.revalidate();
gridPanel.repaint();
return true;
}
public void removeCamera(int position) {
if (position >= 0 && position < MAX_CAMERAS) {
CameraPanel panel = cameras.get(position);
if (panel != null) {
panel.stopStream();
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();
} else {
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 stopAllCameras() { public void addNetworkCamera() {
for (CameraPanel panel : cameras.values()) { // Find the first inactive panel, or add a new one if less than 4 exist
if (panel != null) { CameraPanel targetPanel = null;
panel.stopStream(); 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) {
gridPanel.remove(panel);
gridPanel.revalidate();
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 CameraPanel getCameraPanel(int position) { private void startLocalCamera(int position, Webcam webcam) {
return cameras.get(position); 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);
} }
public void restartCamera(int position) { private void startNetworkCamera(int position, String urlString, String username, String password) {
CameraPanel panel = cameras.get(position); stopCamera(position);
if (panel != null && panel.isStreaming()) { CameraPanel panel = cameraPanels.get(position);
StreamReader currentStream = panel.getCurrentStream(); panel.setActive(true);
if (currentStream != null) { panel.setCameraName("Network: " + urlString);
panel.stopStream();
panel.startStream(currentStream); // 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) {
panel.setActive(false);
panel.setCameraName("Camera " + (position + 1));
}
}
public void shutdown() {
for (Future<?> future : cameraThreads.values()) {
future.cancel(true);
}
cameraThreads.clear();
executorService.shutdownNow();
try {
executorService.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} }
} }
public void saveConfiguration() { public void saveConfiguration() {
List<CameraConfig> configs = new ArrayList<>(); JFileChooser fileChooser = new JFileChooser();
for (Map.Entry<Integer, CameraPanel> entry : cameras.entrySet()) { fileChooser.setDialogTitle("Save Camera Configuration");
CameraPanel panel = entry.getValue(); fileChooser.setSelectedFile(new File("camera_config.json"));
if (panel.isStreaming()) {
CameraConfig config = new CameraConfig();
config.position = entry.getKey();
config.name = panel.getCameraName();
// Add more configuration details as needed
configs.add(config);
}
}
try (Writer writer = new FileWriter(CONFIG_FILE)) { if (fileChooser.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) {
gson.toJson(configs, writer); try (FileWriter writer = new FileWriter(fileChooser.getSelectedFile())) {
} catch (IOException e) { List<CameraConfig> configs = new ArrayList<>();
JOptionPane.showMessageDialog(null, for (Map.Entry<Integer, CameraPanel> entry : cameraPanels.entrySet()) {
"Error saving configuration: " + e.getMessage(), CameraPanel panel = entry.getValue();
"Save Error", if (panel.isActive()) {
JOptionPane.ERROR_MESSAGE); CameraConfig config = new CameraConfig();
config.position = entry.getKey();
config.name = panel.getCameraName();
configs.add(config);
}
}
gson.toJson(configs, writer);
JOptionPane.showMessageDialog(null,
"Configuration saved successfully",
"Success",
JOptionPane.INFORMATION_MESSAGE);
} catch (IOException e) {
JOptionPane.showMessageDialog(null,
"Error saving configuration: " + e.getMessage(),
"Error",
JOptionPane.ERROR_MESSAGE);
}
} }
} }
public void loadConfiguration() { public void loadConfiguration() {
try (Reader reader = new FileReader(CONFIG_FILE)) { JFileChooser fileChooser = new JFileChooser();
List<CameraConfig> configs = gson.fromJson(reader, fileChooser.setDialogTitle("Load Camera Configuration");
new TypeToken<List<CameraConfig>>(){}.getType());
if (configs != null) { if (fileChooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
stopAllCameras(); try (FileReader reader = new FileReader(fileChooser.getSelectedFile())) {
for (CameraConfig config : configs) { List<CameraConfig> configs = gson.fromJson(reader,
// Implement camera restoration based on config new TypeToken<List<CameraConfig>>(){}.getType());
// This is a placeholder for the actual implementation
// You'll need to create appropriate StreamReader instances if (configs != null) {
// Stop all current cameras
for (int position : cameraPanels.keySet()) {
stopCamera(position);
}
// Load new configuration
for (CameraConfig config : configs) {
if ("local".equals(config.type)) {
// TODO: Implement local camera restoration
} else if ("network".equals(config.type)) {
startNetworkCamera(config.position, config.url, config.username, config.password);
}
}
} }
} catch (IOException e) {
JOptionPane.showMessageDialog(null,
"Error loading configuration: " + e.getMessage(),
"Error",
JOptionPane.ERROR_MESSAGE);
} }
} catch (IOException e) {
JOptionPane.showMessageDialog(null,
"Error loading configuration: " + e.getMessage(),
"Load Error",
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;
String name; String name;

View File

@@ -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,91 +10,130 @@ 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();
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"));
File outputFile = new File(String.format("snapshot_%s_%s.jpg", cameraName.replaceAll("[^a-zA-Z0-9]", "_"), timestamp));
ImageIO.write(image, "jpg", outputFile);
JOptionPane.showMessageDialog(this,
"Snapshot saved: " + outputFile.getName(),
"Snapshot Saved",
JOptionPane.INFORMATION_MESSAGE);
} catch (Exception e) {
JOptionPane.showMessageDialog(this,
"Error saving snapshot: " + e.getMessage(),
"Error",
JOptionPane.ERROR_MESSAGE);
}
} }
public void updateFrame(Image frame) { public void setActive(boolean active) {
if (frame != null) { this.isActive = active;
videoLabel.setIcon(new ImageIcon(frame)); repaint();
} }
public boolean isActive() {
return isActive;
}
public void setCameraName(String name) {
this.cameraName = name;
setBorder(createPanelBorder(isSelected));
} }
public String getCameraName() { public String getCameraName() {
return cameraName; return cameraName;
} }
public boolean isStreaming() { public int getPosition() {
return isStreaming; return position;
} }
public StreamReader getCurrentStream() { public void takeSnapshot() {
return currentStream; if (currentFrame != null) {
try {
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));
ImageIO.write(currentFrame, "jpg", outputFile);
JOptionPane.showMessageDialog(this,
"Snapshot saved: " + outputFile.getName(),
"Snapshot Saved",
JOptionPane.INFORMATION_MESSAGE);
} catch (Exception e) {
JOptionPane.showMessageDialog(this,
"Error saving snapshot: " + e.getMessage(),
"Error",
JOptionPane.ERROR_MESSAGE);
}
}
}
@Override
protected void paintComponent(Graphics g) {
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);
}
}
private Border createPanelBorder(boolean selected) {
Border lineBorder = BorderFactory.createLineBorder(
selected ? SELECTED_BORDER_COLOR : new Color(200, 200, 200),
selected ? BORDER_THICKNESS : 1
);
Border emptyBorder = BorderFactory.createEmptyBorder(8, 8, 8, 8);
TitledBorder titleBorder = BorderFactory.createTitledBorder(
lineBorder,
cameraName,
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);
} }
} }

View File

@@ -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);

View File

@@ -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);
}); });
} }
} }

View 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);
}
}

View File

@@ -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,43 +13,99 @@ 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() {
try { while (running) {
URL url = new URL(streamUrl); try {
connection = (HttpURLConnection) url.openConnection(); connectAndStream();
connection.setConnectTimeout(5000); Thread.sleep(1000); // Wait before reconnecting
connection.setReadTimeout(5000); } 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;
}
}
}
}
}
try (BufferedInputStream in = new BufferedInputStream(connection.getInputStream())) { private void connectAndStream() throws IOException {
byte[] buffer = new byte[8192]; URL url = new URL(streamUrl);
int bytesRead; connection = (HttpURLConnection) url.openConnection();
byte[] imageBuffer = new byte[1024 * 1024]; // 1MB buffer for JPEG connection.setConnectTimeout(5000);
int imagePos = 0; connection.setReadTimeout(5000);
boolean foundHeader = false;
while (running) { // Add authentication if provided
bytesRead = in.read(buffer); if (username != null && !username.isEmpty() && password != null) {
if (bytesRead == -1) break; String auth = username + ":" + password;
String encodedAuth = java.util.Base64.getEncoder().encodeToString(auth.getBytes());
connection.setRequestProperty("Authorization", "Basic " + encodedAuth);
}
for (int i = 0; i < bytesRead; i++) { // For DroidCam compatibility
// Look for JPEG header (0xFF, 0xD8) connection.setRequestProperty("User-Agent", "Mozilla/5.0");
if (!foundHeader && i < bytesRead - 1) { connection.setRequestProperty("Accept", "multipart/x-mixed-replace;boundary=BOUNDARY");
if (buffer[i] == (byte) 0xFF && buffer[i + 1] == (byte) 0xD8) {
imagePos = 0; try (BufferedInputStream in = new BufferedInputStream(connection.getInputStream(), BUFFER_SIZE)) {
foundHeader = true; byte[] buffer = new byte[BUFFER_SIZE];
byte[] imageBuffer = new byte[MAX_IMAGE_SIZE];
int imagePos = 0;
boolean foundHeader = false;
String boundary = null;
while (running) {
int bytesRead = in.read(buffer);
if (bytesRead == -1) break;
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);
} }
} }
}
if (foundHeader) { // Look for JPEG header (0xFF, 0xD8)
if (!foundHeader && i < bytesRead - 1) {
if (buffer[i] == (byte) 0xFF && buffer[i + 1] == (byte) 0xD8) {
imagePos = 0;
foundHeader = true;
}
}
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;
}
if (imagePos >= imageBuffer.length) {
// Buffer overflow, reset
foundHeader = false; foundHeader = false;
imagePos = 0;
} }
} else {
// Buffer overflow, reset
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
} }
} }

View 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);
}
}
}

View File

@@ -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));
} }

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.