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
- View live streams from USB webcams
- Connect to network MJPEG cameras (e.g., DroidCamX)
- Take snapshots of the current view
- Simple and intuitive user interface
- Support for multiple camera sources
- 🎥 Support for local webcams and network cameras (MJPEG streams)
- 📺 2x2 grid layout for viewing up to 4 camera feeds
- 💾 Save and load camera configurations
- 🔐 Basic authentication for network cameras
- 🖱️ Click-to-select camera panels
- 🎛️ Simple and intuitive menu interface
## Requirements
- Java 11 or higher
- Maven
- USB webcam (for local camera support)
- Network camera with MJPEG stream support (for network camera support)
- Maven 3.6 or higher
- Connected webcam(s) for local camera support
- Network camera URLs (if using IP cameras)
## Building the Application
1. Clone the repository
2. Navigate to the project directory
3. Build with Maven:
1. Clone the repository:
```bash
git clone https://github.com/yourusername/jsca.git
cd jsca
```
2. Build with Maven:
```bash
mvn clean package
```
This will create an executable JAR file in the `target` directory.
## Running the Application
After building, run the application using:
Run the application using:
```bash
java -jar target/security-camera-app-1.0-SNAPSHOT-jar-with-dependencies.jar
```
## Usage
## Usage Instructions
1. Launch the application
2. Select the camera source from the dropdown:
- USB Camera: Uses your computer's webcam
- Network Camera: Connects to an MJPEG stream URL
3. Click "Start" to begin streaming
4. Use the "Snapshot" button to capture the current frame
5. Click "Stop" to end the stream
1. **Adding Cameras**:
- Select an empty camera panel by clicking on it
- Go to Camera -> Add Local Camera to add a webcam
- Go to Camera -> Add Network Camera to add an IP camera
### 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:
- DroidCamX: `http://[IP_ADDRESS]:4747/video`
- Generic IP Camera: `http://[IP_ADDRESS]/video` or `http://[IP_ADDRESS]/mjpeg`
3. **Saving/Loading Configurations**:
- File -> Save Setup to save your current camera configuration
- 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
1. No webcam detected:
- Ensure your webcam is properly connected
- Check if other applications are using the webcam
1. **No webcams detected**:
- Check if your webcam is properly connected
- Ensure no other application is using the webcam
2. Network camera not connecting:
2. **Network camera not connecting**:
- Verify the camera URL is correct
- Ensure the camera is on the same network
- Check if the camera supports MJPEG streaming
- Check if the camera is accessible from your network
- 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
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;
import com.github.sarxos.webcam.Webcam;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
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 {
private final Map<Integer, CameraPanel> cameras = new HashMap<>();
private final JPanel gridPanel;
private static final int MAX_CAMERAS = 4;
private static final String CONFIG_FILE = "camera_config.json";
private final Map<Integer, CameraPanel> cameraPanels;
private final Map<Integer, Future<?>> cameraThreads;
private final ExecutorService executorService;
private final Gson gson;
private final 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();
// Initialize empty panels
for (int i = 0; i < MAX_CAMERAS; i++) {
CameraPanel emptyPanel = new CameraPanel("Empty " + (i + 1));
cameras.put(i, emptyPanel);
gridPanel.add(emptyPanel);
this.gridPanel = gridPanel;
// Do not add any panels initially
}
public void registerPanel(int position, CameraPanel panel) {
if (position >= 0 && position < 4) {
cameraPanels.put(position, panel);
}
}
public boolean addCamera(String name, StreamReader streamReader, int position) {
if (position < 0 || position >= MAX_CAMERAS) {
return false;
public void addLocalCamera() {
// Find the first inactive panel, or add a new one if less than 4 exist
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();
}
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);
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;
}
}
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() {
for (CameraPanel panel : cameras.values()) {
if (panel != null) {
panel.stopStream();
public void addNetworkCamera() {
// Find the first inactive panel, or add a new one if less than 4 exist
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) {
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) {
return cameras.get(position);
private void startLocalCamera(int position, Webcam webcam) {
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) {
CameraPanel panel = cameras.get(position);
if (panel != null && panel.isStreaming()) {
StreamReader currentStream = panel.getCurrentStream();
if (currentStream != null) {
panel.stopStream();
panel.startStream(currentStream);
}
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) {
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() {
List<CameraConfig> configs = new ArrayList<>();
for (Map.Entry<Integer, CameraPanel> entry : cameras.entrySet()) {
CameraPanel panel = entry.getValue();
if (panel.isStreaming()) {
CameraConfig config = new CameraConfig();
config.position = entry.getKey();
config.name = panel.getCameraName();
// Add more configuration details as needed
configs.add(config);
}
}
JFileChooser fileChooser = new JFileChooser();
fileChooser.setDialogTitle("Save Camera Configuration");
fileChooser.setSelectedFile(new File("camera_config.json"));
try (Writer writer = new FileWriter(CONFIG_FILE)) {
gson.toJson(configs, writer);
} catch (IOException e) {
JOptionPane.showMessageDialog(null,
"Error saving configuration: " + e.getMessage(),
"Save Error",
JOptionPane.ERROR_MESSAGE);
if (fileChooser.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) {
try (FileWriter writer = new FileWriter(fileChooser.getSelectedFile())) {
List<CameraConfig> configs = new ArrayList<>();
for (Map.Entry<Integer, CameraPanel> entry : cameraPanels.entrySet()) {
CameraPanel panel = entry.getValue();
if (panel.isActive()) {
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() {
try (Reader reader = new FileReader(CONFIG_FILE)) {
List<CameraConfig> configs = gson.fromJson(reader,
new TypeToken<List<CameraConfig>>(){}.getType());
if (configs != null) {
stopAllCameras();
for (CameraConfig config : configs) {
// Implement camera restoration based on config
// This is a placeholder for the actual implementation
// You'll need to create appropriate StreamReader instances
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,
new TypeToken<List<CameraConfig>>(){}.getType());
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 {
int position;
String name;

View File

@@ -1,6 +1,7 @@
package com.jsca;
import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
@@ -9,91 +10,130 @@ import java.time.format.DateTimeFormatter;
import javax.imageio.ImageIO;
public class CameraPanel extends JPanel {
private final JLabel videoLabel;
private final String cameraName;
private volatile boolean isStreaming = false;
private StreamReader currentStream;
private final int position;
private BufferedImage currentFrame;
private String cameraName;
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) {
this.cameraName = cameraName;
setLayout(new BorderLayout());
// Create video display panel
videoLabel = new JLabel();
videoLabel.setPreferredSize(new Dimension(640, 480));
videoLabel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
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 CameraPanel(int position) {
this.position = position;
this.isActive = false;
this.isSelected = false;
this.cameraName = String.format("Camera %d", position + 1);
setPreferredSize(new Dimension(320, 240));
setBorder(BorderFactory.createLineBorder(new Color(220, 220, 220), 1));
setBackground(BACKGROUND_COLOR);
}
public void startStream(StreamReader streamReader) {
stopStream(); // Stop any existing stream
currentStream = streamReader;
new Thread(currentStream).start();
isStreaming = true;
public void setSelected(boolean selected) {
this.isSelected = selected;
setBorder(createPanelBorder(selected));
repaint();
}
public void stopStream() {
if (currentStream != null) {
currentStream.stop();
currentStream = null;
}
isStreaming = false;
videoLabel.setIcon(null);
public boolean isSelected() {
return isSelected;
}
private void takeSnapshot() {
if (videoLabel.getIcon() == null) return;
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(BufferedImage frame) {
this.currentFrame = frame;
repaint();
}
public void updateFrame(Image frame) {
if (frame != null) {
videoLabel.setIcon(new ImageIcon(frame));
}
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 boolean isStreaming() {
return isStreaming;
public int getPosition() {
return position;
}
public StreamReader getCurrentStream() {
return currentStream;
public void takeSnapshot() {
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() {
@Override
public void windowClosing(WindowEvent e) {
cameraManager.stopAllCameras();
cameraManager.shutdown();
}
});
@@ -58,7 +58,7 @@ public class CameraViewer extends JFrame {
loadSetup.addActionListener(e -> cameraManager.loadConfiguration());
saveSetup.addActionListener(e -> cameraManager.saveConfiguration());
exit.addActionListener(e -> {
cameraManager.stopAllCameras();
cameraManager.shutdown();
dispose();
});
@@ -74,10 +74,10 @@ public class CameraViewer extends JFrame {
JMenuItem removeCamera = new JMenuItem("Remove Camera");
JMenuItem restartCamera = new JMenuItem("Restart Camera");
addNetwork.addActionListener(e -> addNetworkCamera());
addLocal.addActionListener(e -> addLocalCamera());
removeCamera.addActionListener(e -> removeSelectedCamera());
restartCamera.addActionListener(e -> restartSelectedCamera());
addNetwork.addActionListener(e -> cameraManager.addNetworkCamera());
addLocal.addActionListener(e -> cameraManager.addLocalCamera());
removeCamera.addActionListener(e -> cameraManager.removeSelectedCamera());
restartCamera.addActionListener(e -> cameraManager.restartSelectedCamera());
cameraMenu.add(addNetwork);
cameraMenu.add(addLocal);
@@ -103,115 +103,6 @@ public class CameraViewer extends JFrame {
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() {
JOptionPane.showMessageDialog(this,
"Security Camera Viewer\nVersion 1.0\n\n" +
@@ -225,10 +116,9 @@ public class CameraViewer extends JFrame {
JOptionPane.showMessageDialog(this,
"Instructions:\n\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" +
"4. Each camera panel has its own snapshot button\n\n" +
"Network Camera URLs should be in MJPEG format\n" +
"4. Network Camera URLs should be in MJPEG format\n" +
"Example: http://camera-ip:port/video",
"Instructions",
JOptionPane.INFORMATION_MESSAGE);

View File

@@ -1,12 +1,20 @@
package com.jsca;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
public class Main {
public static void main(String[] args) {
try {
// Set system look and feel
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
e.printStackTrace();
}
SwingUtilities.invokeLater(() -> {
CameraViewer viewer = new CameraViewer();
viewer.setVisible(true);
MainApp app = new MainApp();
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.swing.SwingUtilities;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@@ -13,43 +13,99 @@ import java.util.Arrays;
public class NetworkStreamReader implements StreamReader {
private final CameraPanel panel;
private final String streamUrl;
private final String username;
private final String password;
private volatile boolean running = true;
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.streamUrl = streamUrl;
this.username = username;
this.password = password;
}
@Override
public void run() {
try {
URL url = new URL(streamUrl);
connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
while (running) {
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;
}
}
}
}
}
try (BufferedInputStream in = new BufferedInputStream(connection.getInputStream())) {
byte[] buffer = new byte[8192];
int bytesRead;
byte[] imageBuffer = new byte[1024 * 1024]; // 1MB buffer for JPEG
int imagePos = 0;
boolean foundHeader = false;
private void connectAndStream() throws IOException {
URL url = new URL(streamUrl);
connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
// Add authentication if provided
if (username != null && !username.isEmpty() && password != null) {
String auth = username + ":" + password;
String encodedAuth = java.util.Base64.getEncoder().encodeToString(auth.getBytes());
connection.setRequestProperty("Authorization", "Basic " + encodedAuth);
}
while (running) {
bytesRead = in.read(buffer);
if (bytesRead == -1) break;
// For DroidCam compatibility
connection.setRequestProperty("User-Agent", "Mozilla/5.0");
connection.setRequestProperty("Accept", "multipart/x-mixed-replace;boundary=BOUNDARY");
for (int i = 0; i < bytesRead; i++) {
// 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;
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;
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];
// Look for JPEG footer (0xFF, 0xD9)
@@ -57,29 +113,18 @@ public class NetworkStreamReader implements StreamReader {
imageBuffer[imagePos - 2] == (byte) 0xFF &&
imageBuffer[imagePos - 1] == (byte) 0xD9) {
// We have a complete JPEG image
byte[] imageData = Arrays.copyOf(imageBuffer, imagePos);
processImage(imageData);
foundHeader = false;
}
if (imagePos >= imageBuffer.length) {
// Buffer overflow, reset
processImage(Arrays.copyOf(imageBuffer, imagePos));
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 {
if (connection != null) {
connection.disconnect();
@@ -89,12 +134,14 @@ public class NetworkStreamReader implements StreamReader {
private void processImage(byte[] imageData) {
try {
Image image = ImageIO.read(new ByteArrayInputStream(imageData));
BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageData));
if (image != null) {
SwingUtilities.invokeLater(() -> panel.updateFrame(image));
SwingUtilities.invokeLater(() -> {
panel.setActive(true);
panel.updateFrame(image);
});
}
Thread.sleep(33); // ~30 FPS
} catch (IOException | InterruptedException e) {
} catch (IOException e) {
// 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 java.awt.Dimension;
import java.awt.Image;
import java.awt.image.BufferedImage;
import javax.swing.SwingUtilities;
public class WebcamStreamReader implements StreamReader {
@@ -26,7 +26,7 @@ public class WebcamStreamReader implements StreamReader {
webcam.open();
while (running && webcam.isOpen()) {
final Image image = webcam.getImage();
final BufferedImage image = webcam.getImage();
if (image != null) {
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.