7 Commits

Author SHA1 Message Date
40a6183529 some fixes to config import 2026-01-22 15:33:59 +01:00
c32b5d7278 refactors ; some new additions to recording which are critical for cctv softwares 2026-01-21 18:09:30 +01:00
3eaf6f0303 fix
Signed-off-by: rattatwinko <seppmutterman@gmail.com>
2026-01-20 08:37:48 +01:00
11c5aa9115 Merge branch 'experimenting'
Signed-off-by: rattatwinko <seppmutterman@gmail.com>

# Conflicts:
#	src/main/java/io/swtc/Main.java
2026-01-20 08:28:10 +01:00
55474092e3 some more optimizing!
now runs much better with lower power systems as we refactored code to firstly rule out double invokelaters, and we optimized the capture loop iteslf!

and we introduced a ShowError class which will be refactored and used quite thoroughly!

Signed-off-by: rattatwinko <seppmutterman@gmail.com>
2026-01-20 08:25:26 +01:00
b49cc8b2f0 refactors 2026-01-19 18:33:42 +01:00
41fbf62757 just some theme changes, i like this one more 2026-01-18 22:10:37 +01:00
19 changed files with 838 additions and 565 deletions

View File

@@ -1,26 +1,23 @@
package io.swtc; package io.swtc;
import javax.swing.*; import javax.swing.*;
import io.swtc.proccessing.ui.IconSetter;
import io.swtc.proccessing.ui.ShowError;
public class Main { public class Main {
public static void main(String[] args) { public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
try { System.out.println("Arg " + i + ": " + args[i]);
UIManager.setLookAndFeel(
"com.sun.java.swing.plaf.windows.WindowsLookAndFeel"
);
} catch (Exception e) {
JOptionPane.showMessageDialog(
null,
"LaF not available",
"LaF-Warning",
JOptionPane.WARNING_MESSAGE
);
} }
// For some reason we need to invoke Later for LaF to work! try {
SwingUtilities.invokeLater(() -> SwingCCTVManager.main(null)); UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
ShowError.warning(null,"LaF Warn","LaF");
}
SwingCCTVManager.main(null);
} }
} }

View File

@@ -2,25 +2,37 @@ package io.swtc;
import com.github.sarxos.webcam.Webcam; import com.github.sarxos.webcam.Webcam;
import com.github.sarxos.webcam.WebcamCompositeDriver; import com.github.sarxos.webcam.WebcamCompositeDriver;
import com.github.sarxos.webcam.WebcamDevice;
import com.github.sarxos.webcam.ds.buildin.WebcamDefaultDriver; import com.github.sarxos.webcam.ds.buildin.WebcamDefaultDriver;
import com.github.sarxos.webcam.ds.ipcam.IpCamDeviceRegistry; import com.github.sarxos.webcam.ds.ipcam.*;
import com.github.sarxos.webcam.ds.ipcam.IpCamDriver;
import com.github.sarxos.webcam.ds.ipcam.IpCamMode;
import io.swtc.networking.CameraConfig; import io.swtc.networking.CameraConfig;
import io.swtc.networking.CameraSettings; import io.swtc.networking.CameraSettings;
import io.swtc.proccessing.ui.IconSetter;
import io.swtc.proccessing.ui.ShowError;
import javax.swing.*; import javax.swing.*;
import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel; import javax.swing.table.DefaultTableModel;
import java.awt.*; import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.event.MouseAdapter; import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.io.File;
import java.net.InetSocketAddress;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URL;
import java.util.List; import java.util.List;
public class SwingCCTVManager { public class SwingCCTVManager {
private static final String TIMEOUT_MS = "1500";
private static final int REFRESH_INTERVAL = 30000;
private static final String STATUS_ONLINE = "ONLINE";
private static final String STATUS_OFFLINE = "OFFLINE";
static { static {
System.setProperty("ipcam.connection.timeout", TIMEOUT_MS);
Webcam.setDriver(new WebcamCompositeDriver() {{ Webcam.setDriver(new WebcamCompositeDriver() {{
add(new WebcamDefaultDriver()); add(new WebcamDefaultDriver());
add(new IpCamDriver()); add(new IpCamDriver());
@@ -32,206 +44,238 @@ public class SwingCCTVManager {
private final JTable deviceTable; private final JTable deviceTable;
private final DefaultTableModel tableModel; private final DefaultTableModel tableModel;
private final SwingIFrame IFrame; private final SwingIFrame IFrame;
private boolean isRefreshing = false;
public SwingCCTVManager() { public SwingCCTVManager() {
frame = new JFrame("dashboard"); frame = new JFrame("Dashboard");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(1000, 600); frame.setSize(1100, 600);
frame.setIconImage(IconSetter.getIcon());
this.IFrame = new SwingIFrame(); this.IFrame = new SwingIFrame();
this.IFrame.show(); this.IFrame.show();
String[] columns = {"Status", "Device Name", "Type", "Resolution", "Address"}; tableModel = new DefaultTableModel(new String[]{"Status", "Device Name", "Type", "Resolution", "Address"}, 0) {
tableModel = new DefaultTableModel(columns, 0) {
@Override @Override
public boolean isCellEditable(int r, int c) { public boolean isCellEditable(int r, int c) { return false; }
return false;
}
}; };
deviceTable = new JTable(tableModel); deviceTable = new JTable(tableModel);
deviceTable.setRowSelectionAllowed(true);
deviceTable.setFocusable(true);
deviceTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
setupTableAppearance(); setupTableAppearance();
setupDragAndDrop(); // Initialize DND
deviceTable.addMouseListener(new MouseAdapter() { setupListeners();
@Override
public void mousePressed(MouseEvent e) {
deviceTable.requestFocusInWindow();
if (e.getClickCount() == 2 && deviceTable.getSelectedRow() != -1) {
launchSelected();
}
if (SwingUtilities.isRightMouseButton(e)) {
showContextMenu(e);
}
}
});
JToolBar toolBar = new JToolBar(); JToolBar toolBar = new JToolBar();
JButton btnAdd = new JButton("Add IP Cam"); setupBrandedToolbar(toolBar);
JButton btnLaunch = new JButton("Launch Stream");
toolBar.add(btnAdd);
toolBar.addSeparator();
toolBar.add(btnLaunch);
frame.add(toolBar, BorderLayout.NORTH); frame.add(toolBar, BorderLayout.SOUTH);
frame.add(new JScrollPane(deviceTable), BorderLayout.CENTER); frame.add(new JScrollPane(deviceTable), BorderLayout.CENTER);
btnAdd.addActionListener(e -> showAddCameraDialog());
btnLaunch.addActionListener(e -> launchSelected());
startAutoRefresh(); startAutoRefresh();
refreshTable(); refreshTable();
} }
private void setupTableAppearance() { private void setupBrandedToolbar(JToolBar toolBar) {
deviceTable.getColumnModel().getColumn(0).setMaxWidth(80); Image ogIcon = IconSetter.getIcon();
deviceTable.setRowHeight(30); if (ogIcon != null) {
Image scaledIcon = ogIcon.getScaledInstance(32, 32, Image.SCALE_SMOOTH);
deviceTable.getColumnModel().getColumn(0) toolBar.add(new JLabel(new ImageIcon(scaledIcon)));
.setCellRenderer(new DefaultTableCellRenderer() {
@Override
public Component getTableCellRendererComponent(
JTable t, Object v, boolean s, boolean f, int r, int c) {
Component comp = super.getTableCellRendererComponent(t, v, s, f, r, c);
setHorizontalAlignment(JLabel.CENTER);
comp.setForeground("ONLINE".equals(v)
? new Color(0, 150, 0)
: Color.RED);
return comp;
}
});
}
private void startAutoRefresh() {
new Timer(5000, e -> refreshTable()).start();
}
private void refreshTable() {
int[] selectedRows = deviceTable.getSelectedRows();
tableModel.setRowCount(0);
List<Webcam> webcams = Webcam.getWebcams();
for (Webcam w : webcams) {
boolean isIp = w.getDevice().getClass().getSimpleName().contains("IpCam");
String status = w.getDevice() != null ? "ONLINE" : "OFFLINE";
tableModel.addRow(new Object[]{
status,
w.getName(),
isIp ? "IP Stream" : "USB Hardware",
w.getViewSize().width + "x" + w.getViewSize().height,
isIp ? "Network" : "Local"
});
} }
for (int r : selectedRows) { toolBar.add(new JLabel("SWT-CCTV"));
if (r < tableModel.getRowCount()) { toolBar.add(Box.createRigidArea(new Dimension(10, 0)));
deviceTable.addRowSelectionInterval(r, r); toolBar.addSeparator(new Dimension(10, 32));
JButton btnAdd = new JButton("Add IP Cam");
JButton btnImport = new JButton("Import Config"); // New Import Button
JButton btnLaunch = new JButton("Launch Stream");
btnAdd.setPreferredSize(new Dimension(100, 32));
btnImport.setPreferredSize(new Dimension(110, 32));
btnLaunch.setPreferredSize(new Dimension(120, 32));
toolBar.add(Box.createRigidArea(new Dimension(5, 0)));
toolBar.add(btnAdd);
toolBar.add(btnImport);
toolBar.addSeparator();
toolBar.add(btnLaunch);
btnAdd.addActionListener(e -> showAddCameraDialog());
btnImport.addActionListener(e -> showImportDialog());
btnLaunch.addActionListener(e -> launchSelected());
}
private void setupDragAndDrop() {
deviceTable.setDragEnabled(true);
deviceTable.setTransferHandler(new TransferHandler() {
@Override
public boolean canImport(TransferSupport support) {
return support.isDataFlavorSupported(DataFlavor.javaFileListFlavor);
} }
@Override
public boolean importData(TransferSupport support) {
if (!canImport(support)) return false;
try {
@SuppressWarnings("unchecked")
List<File> files = (List<File>) support.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);
for (File file : files) {
if (file.getName().endsWith(".json")) {
List<CameraConfig> configs = CameraSettings.loadFromFile(file);
importCameras(configs);
}
}
return true;
} catch (Exception e) {
return false;
}
}
});
}
private void showImportDialog() {
JFileChooser chooser = new JFileChooser();
chooser.setDialogTitle("Select Camera Configuration JSON");
if (chooser.showOpenDialog(frame) == JFileChooser.APPROVE_OPTION) {
File file = chooser.getSelectedFile();
List<CameraConfig> configs = CameraSettings.loadFromFile(file);
importCameras(configs);
} }
} }
private void showContextMenu(MouseEvent e) { private void importCameras(List<CameraConfig> configs) {
int row = deviceTable.rowAtPoint(e.getPoint()); if (configs.isEmpty()) {
if (row >= 0) { ShowError.warning(frame, "Import Empty", "No valid camera configurations found in file.");
deviceTable.setRowSelectionInterval(row, row); return;
} }
for (CameraConfig config : configs) {
try {
if (!IpCamDeviceRegistry.isRegistered(config.getName())) {
IpCamDeviceRegistry.register(config.getName(), config.getUrl(), IpCamMode.PUSH);
CameraSettings.save(config);
}
} catch (Exception ignored) {}
}
refreshTable();
}
JPopupMenu menu = new JPopupMenu(); private void setupTableAppearance() {
JMenuItem launch = new JMenuItem("Launch Live Stream"); deviceTable.setRowHeight(30);
JMenuItem delete = new JMenuItem("Remove Device"); deviceTable.getColumnModel().getColumn(0).setMaxWidth(80);
deviceTable.getColumnModel().getColumn(0).setCellRenderer(new DefaultTableCellRenderer() {
@Override
public Component getTableCellRendererComponent(JTable t, Object v, boolean s, boolean f, int r, int c) {
Component comp = super.getTableCellRendererComponent(t, v, s, f, r, c);
setHorizontalAlignment(JLabel.CENTER);
comp.setForeground(STATUS_ONLINE.equals(v) ? new Color(0, 150, 0) : Color.RED);
return comp;
}
});
}
launch.addActionListener(al -> launchSelected()); private synchronized void refreshTable() {
delete.addActionListener(al -> deleteSelected()); if (isRefreshing) return;
isRefreshing = true;
final int selectedRow = deviceTable.getSelectedRow();
new SwingWorker<Void, Object[]>() {
@Override
protected Void doInBackground() {
for (WebcamDevice device : new WebcamDefaultDriver().getDevices()) {
Dimension res = (device.getResolutions().length > 0) ? device.getResolutions()[0] : new Dimension(0,0);
publish(new Object[]{STATUS_ONLINE, device.getName(), "USB Hardware", res.width + "x" + res.height, "Local"});
}
for (IpCamDevice ipDevice : IpCamDeviceRegistry.getIpCameras()) {
publish(probeIpCamera(ipDevice));
}
return null;
}
@Override
protected void process(List<Object[]> chunks) {
if (!Boolean.TRUE.equals(frame.getRootPane().getClientProperty("cleared"))) {
tableModel.setRowCount(0);
frame.getRootPane().putClientProperty("cleared", true);
}
for (Object[] row : chunks) tableModel.addRow(row);
}
@Override
protected void done() {
frame.getRootPane().putClientProperty("cleared", null);
if (selectedRow != -1 && selectedRow < tableModel.getRowCount()) {
deviceTable.setRowSelectionInterval(selectedRow, selectedRow);
}
isRefreshing = false;
}
}.execute();
}
menu.add(launch); private Object[] probeIpCamera(IpCamDevice ipDevice) {
menu.addSeparator(); String status = STATUS_OFFLINE; String resText = "N/A";
menu.add(delete); try {
menu.show(deviceTable, e.getX(), e.getY()); URL url = ipDevice.getURL();
try (Socket socket = new Socket()) {
socket.connect(new InetSocketAddress(url.getHost(), url.getPort() != -1 ? url.getPort() : 80), 800);
Dimension d = ipDevice.getResolution();
if (d != null) { status = STATUS_ONLINE; resText = d.width + "x" + d.height; }
}
} catch (Exception e) { status = STATUS_OFFLINE; }
return new Object[]{status, ipDevice.getName(), "IP Stream", resText, "Network"};
} }
private void launchSelected() { private void launchSelected() {
int row = deviceTable.getSelectedRow(); int row = deviceTable.getSelectedRow();
if (row == -1) return; if (row == -1) return;
String name = (String) tableModel.getValueAt(row, 1); String name = (String) tableModel.getValueAt(row, 1);
Webcam.getWebcams().stream() if (STATUS_OFFLINE.equals(tableModel.getValueAt(row, 0))) {
.filter(w -> w.getName().equals(name)) ShowError.warning(frame, "Device Offline", "Cannot connect to '" + name + "'.");
.findFirst() return;
.ifPresent(cam ->
new Thread(() -> IFrame.addCameraInternalFrame(cam)).start()
);
}
private void deleteSelected() {
int row = deviceTable.getSelectedRow();
if (row == -1) return;
String name = (String) tableModel.getValueAt(row, 1);
if (name.toLowerCase().contains("usb")) return;
CameraSettings.delete(name);
IpCamDeviceRegistry.unregister(name);
refreshTable();
}
private static void loadSavedCameras() {
for (CameraConfig config : CameraSettings.load()) {
try {
IpCamDeviceRegistry.register(
config.getName(),
config.getUrl(),
IpCamMode.PUSH
);
} catch (MalformedURLException e) {
JOptionPane.showMessageDialog(
null,
"Malformed URL\n" + e.getMessage(),
"Malformed URL",
JOptionPane.ERROR_MESSAGE
);
}
} }
new Thread(() -> {
Webcam selected = Webcam.getWebcams().stream().filter(w -> w.getName().equals(name)).findFirst().orElse(null);
if (selected != null) IFrame.addCameraInternalFrame(selected);
}).start();
} }
private void showAddCameraDialog() { private void showAddCameraDialog() {
JPanel p = new JPanel(new GridLayout(2, 2, 5, 5)); JPanel p = new JPanel(new GridLayout(2, 2, 5, 5));
JTextField n = new JTextField(); JTextField n = new JTextField(); JTextField u = new JTextField();
JTextField u = new JTextField(); p.add(new JLabel("Name:")); p.add(n); p.add(new JLabel("URL:")); p.add(u);
if (JOptionPane.showConfirmDialog(frame, p, "Register IP Camera", JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION) {
p.add(new JLabel("Name:")); importCameras(List.of(new CameraConfig(n.getText(), u.getText())));
p.add(n);
p.add(new JLabel("URL:"));
p.add(u);
int result = JOptionPane.showConfirmDialog(
frame, p, "Register IP Camera", JOptionPane.OK_CANCEL_OPTION
);
if (result == JOptionPane.OK_OPTION) {
try {
IpCamDeviceRegistry.register(n.getText(), u.getText(), IpCamMode.PUSH);
CameraSettings.save(new CameraConfig(n.getText(), u.getText()));
refreshTable();
} catch (Exception ex) {
JOptionPane.showMessageDialog(frame, "Error: " + ex.getMessage());
}
} }
} }
public void open() { private void setupListeners() {
frame.setLocationRelativeTo(null); deviceTable.addMouseListener(new MouseAdapter() {
frame.setVisible(true); @Override
public void mousePressed(MouseEvent e) {
deviceTable.requestFocusInWindow();
if (e.getClickCount() == 2 && deviceTable.getSelectedRow() != -1) launchSelected();
if (SwingUtilities.isRightMouseButton(e)) showContextMenu(e);
}
});
} }
private void showContextMenu(MouseEvent e) {
int row = deviceTable.rowAtPoint(e.getPoint());
if (row >= 0) deviceTable.setRowSelectionInterval(row, row);
JPopupMenu menu = new JPopupMenu();
JMenuItem launch = new JMenuItem("Launch Live Stream");
launch.addActionListener(al -> launchSelected());
menu.add(launch);
menu.show(deviceTable, e.getX(), e.getY());
}
private static void loadSavedCameras() {
for (CameraConfig config : CameraSettings.load()) {
try { IpCamDeviceRegistry.register(config.getName(), config.getUrl(), IpCamMode.PUSH); } catch (Exception ignored) {}
}
}
private void startAutoRefresh() { new Timer(REFRESH_INTERVAL, e -> refreshTable()).start(); }
public void open() { frame.setLocationRelativeTo(null); frame.setVisible(true); }
public static void main(String[] args) { public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new SwingCCTVManager().open()); SwingUtilities.invokeLater(() -> new SwingCCTVManager().open());
} }
} }

View File

@@ -1,9 +1,13 @@
package io.swtc; package io.swtc;
import com.github.sarxos.webcam.Webcam; import com.github.sarxos.webcam.Webcam;
import io.swtc.proccessing.ui.iframe.*; import io.swtc.proccessing.ui.IconSetter;
import io.swtc.proccessing.ui.iframe.*; // Your custom frames
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -12,14 +16,30 @@ public class SwingIFrame {
private final DesktopPane desktopPane; private final DesktopPane desktopPane;
private final Map<JInternalFrame, EffectsPanelFrame> cameraToEffects = new HashMap<>(); private final Map<JInternalFrame, EffectsPanelFrame> cameraToEffects = new HashMap<>();
private boolean fullscreen = false;
private Rectangle windowedBounds;
private boolean blackbg = false;
private final Color bgcolor = Color.decode("#336B6A");
private final Color defDesktopBg = Color.WHITE;
private final JPopupMenu popupMenu = new JPopupMenu();
public SwingIFrame() { public SwingIFrame() {
mainFrame = new JFrame("Viewer"); mainFrame = new JFrame("Viewer");
mainFrame.setSize(1280, 720); mainFrame.setSize(1280, 720);
mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
mainFrame.setIconImage(IconSetter.getIcon());
desktopPane = new DesktopPane(cameraToEffects); desktopPane = new DesktopPane(cameraToEffects);
desktopPane.setBackground(Color.WHITE); desktopPane.setBackground(defDesktopBg);
mainFrame.add(desktopPane, BorderLayout.CENTER); mainFrame.add(desktopPane, BorderLayout.CENTER);
setupFullscreenToggle();
setupBlackBg();
initPopupMenu();
desktopPane.addMouseListener(popupListener());
} }
public void addCameraInternalFrame(Webcam webcam) { public void addCameraInternalFrame(Webcam webcam) {
@@ -43,11 +63,20 @@ public class SwingIFrame {
EffectsPanelFrame ef = cameraToEffects.remove(cameraFrame); EffectsPanelFrame ef = cameraToEffects.remove(cameraFrame);
if (ef != null) ef.dispose(); if (ef != null) ef.dispose();
desktopPane.forgetFrame(cameraFrame); desktopPane.forgetFrame(cameraFrame);
cameraFrame.dispose();
} }
}); });
desktopPane.add(cameraFrame); desktopPane.add(cameraFrame);
desktopPane.add(effectsFrame); desktopPane.add(effectsFrame);
// Attach popup menu to frames and content
MouseAdapter popup = popupListener();
cameraFrame.addMouseListener(popup);
cameraFrame.getContentPane().addMouseListener(popup);
effectsFrame.addMouseListener(popup);
effectsFrame.getContentPane().addMouseListener(popup);
cameraFrame.setVisible(true); cameraFrame.setVisible(true);
} }
@@ -58,12 +87,92 @@ public class SwingIFrame {
try { try {
effectsFrame.setSelected(true); effectsFrame.setSelected(true);
effectsFrame.toFront(); effectsFrame.toFront();
} catch (java.beans.PropertyVetoException ex) { JOptionPane.showMessageDialog(null,"Exception" + ex.getMessage() , "Exception", JOptionPane.ERROR_MESSAGE); } } catch (java.beans.PropertyVetoException ex) {
JOptionPane.showMessageDialog(null, "Focus Error: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
}
} }
} }
private MouseAdapter popupListener() {
return new MouseAdapter() {
private void showPopup(MouseEvent e) {
if (e.isPopupTrigger()) { // cross-platform trigger
popupMenu.show(e.getComponent(), e.getX(), e.getY());
}
}
@Override public void mousePressed(MouseEvent e) { showPopup(e); }
@Override public void mouseReleased(MouseEvent e) { showPopup(e); }
};
}
private void initPopupMenu() {
popupMenu.removeAll(); // clean slate
JCheckBoxMenuItem fullscreenItem = new JCheckBoxMenuItem("Fullscreen");
fullscreenItem.addActionListener(e -> toggleFullscreen());
popupMenu.addPopupMenuListener(new javax.swing.event.PopupMenuListener() {
@Override
public void popupMenuWillBecomeVisible(javax.swing.event.PopupMenuEvent e) {
fullscreenItem.setState(fullscreen);
}
@Override public void popupMenuWillBecomeInvisible(javax.swing.event.PopupMenuEvent e) {}
@Override public void popupMenuCanceled(javax.swing.event.PopupMenuEvent e) {}
});
popupMenu.add(fullscreenItem);
}
private void setupBlackBg() {
JRootPane root = mainFrame.getRootPane();
root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(KeyStroke.getKeyStroke("B"), "toggleBlackBg");
root.getActionMap().put("toggleBlackBg", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
toggleBackground();
}
});
}
private void setupFullscreenToggle() {
JRootPane root = mainFrame.getRootPane();
root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(KeyStroke.getKeyStroke("F11"), "toggleFullscreen");
root.getActionMap().put("toggleFullscreen", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
toggleFullscreen();
}
});
}
private void toggleBackground() {
desktopPane.setBackground(blackbg ? defDesktopBg : bgcolor);
blackbg = !blackbg;
desktopPane.repaint();
}
/** Toggle fullscreen mode */
private void toggleFullscreen() {
if (!fullscreen) {
windowedBounds = mainFrame.getBounds();
mainFrame.dispose();
mainFrame.setUndecorated(true);
mainFrame.setExtendedState(JFrame.MAXIMIZED_BOTH);
mainFrame.setVisible(true);
} else {
mainFrame.dispose();
mainFrame.setUndecorated(false);
mainFrame.setExtendedState(JFrame.NORMAL);
mainFrame.setBounds(windowedBounds);
mainFrame.setVisible(true);
}
fullscreen = !fullscreen;
}
public void show() { public void show() {
mainFrame.setLocationRelativeTo(null); mainFrame.setLocationRelativeTo(null);
mainFrame.setVisible(true); mainFrame.setVisible(true);
} }
} }

View File

@@ -5,6 +5,7 @@ public class CameraConfig {
public String url; public String url;
// Default constructor for Jackson // Default constructor for Jackson
public CameraConfig() {}
public CameraConfig(String name, String url) { public CameraConfig(String name, String url) {
this.name = name; this.name = name;

View File

@@ -7,24 +7,32 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
/*
* Some JSON stuff for camera config saving
* */
public class CameraSettings { public class CameraSettings {
private static final File storage_file = new File("network_cameras.json"); private static final File STORAGE_FILE = new File("network_cameras.json");
private static final ObjectMapper mapper = new ObjectMapper(); private static final ObjectMapper MAPPER = new ObjectMapper();
public static List<CameraConfig> load() { public static List<CameraConfig> load() {
if (!storage_file.exists() || storage_file.length() == 0) return new ArrayList<>(); return loadFromFile(STORAGE_FILE);
}
public static List<CameraConfig> loadFromFile(File file) {
if (!file.exists() || file.length() == 0) return new ArrayList<>();
try { try {
return mapper.readValue(storage_file, new TypeReference<List<CameraConfig>>() {}); return MAPPER.readValue(file, new TypeReference<List<CameraConfig>>() {});
} catch (IOException e) { } catch (IOException e) {
System.err.println("Error reading camera config: " + e.getMessage());
return new ArrayList<>(); return new ArrayList<>();
} }
} }
/**
* Saves a single camera. Prevents duplicates by name.
*/
public static void save(CameraConfig newCam) { public static void save(CameraConfig newCam) {
List<CameraConfig> current = load(); List<CameraConfig> current = load();
// Prevent duplicate names in the local storage
current.removeIf(cam -> cam.getName().equalsIgnoreCase(newCam.getName()));
current.add(newCam); current.add(newCam);
write(current); write(current);
} }
@@ -37,7 +45,7 @@ public class CameraSettings {
private static void write(List<CameraConfig> list) { private static void write(List<CameraConfig> list) {
try { try {
mapper.writerWithDefaultPrettyPrinter().writeValue(storage_file, list); MAPPER.writerWithDefaultPrettyPrinter().writeValue(STORAGE_FILE, list);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }

View File

@@ -3,17 +3,19 @@ package io.swtc.proccessing;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function; import java.util.function.Function;
/** /**
* Enhanced CameraPanel with support for custom image processors * Enhanced CameraPanel with support for custom image processors
*/ */
public class CameraPanel extends JPanel { public class CameraPanel extends JPanel {
private BufferedImage currentImage;
private BufferedImage processedImage;
// Custom processor for advanced effects private volatile BufferedImage sourceImage;
private volatile BufferedImage processedImage;
private Function<BufferedImage, BufferedImage> imageProcessor; private Function<BufferedImage, BufferedImage> imageProcessor;
private final AtomicBoolean repaintScheduled = new AtomicBoolean(false);
public CameraPanel() { public CameraPanel() {
setBackground(Color.BLACK); setBackground(Color.BLACK);
@@ -21,78 +23,47 @@ public class CameraPanel extends JPanel {
} }
public void setImage(BufferedImage img) { public void setImage(BufferedImage img) {
this.currentImage = img; sourceImage = img;
processImage();
repaint(); if (repaintScheduled.compareAndSet(false, true)) {
SwingUtilities.invokeLater(() -> {
repaintScheduled.set(false);
repaint();
});
}
} }
public void setImageProcessor(Function<BufferedImage, BufferedImage> processor) { public void setImageProcessor(Function<BufferedImage, BufferedImage> processor) {
this.imageProcessor = processor; this.imageProcessor = processor;
processImage();
repaint();
}
private void processImage() {
if (currentImage == null) {
processedImage = null;
return;
}
BufferedImage result = currentImage;
// Apply basic transforms first
result = applyBasicEffects(result);
// Apply custom processor if set
if (imageProcessor != null) {
result = imageProcessor.apply(result);
}
processedImage = result;
}
private BufferedImage applyBasicEffects(BufferedImage img) {
int width = img.getWidth();
int height = img.getHeight();
BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
// Handle mirror/flip/rotate
int srcX = x;
int srcY = y;
// Ensure coordinates are in bounds
srcX = Math.max(0, Math.min(width - 1, srcX));
srcY = Math.max(0, Math.min(height - 1, srcY));
int rgb = img.getRGB(srcX, srcY);
int r = (rgb >> 16) & 0xFF;
int g = (rgb >> 8) & 0xFF;
int b = rgb & 0xFF;
result.setRGB(x, y, (r << 16) | (g << 8) | b);
}
}
return result;
} }
@Override @Override
protected void paintComponent(Graphics g) { protected void paintComponent(Graphics g) {
super.paintComponent(g); super.paintComponent(g);
if (processedImage != null) { BufferedImage src = sourceImage;
Graphics2D g2d = (Graphics2D) g; if (src == null) return;
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); BufferedImage img = src;
g2d.drawImage(processedImage, 0, 0, getWidth(), getHeight(), null); if (imageProcessor != null) {
// we only apply the proccessor if it is present
// that is due too CPU Usage ; This project was built for a DUAL Core CPU, not a quad or even 6 Core CPU
img = imageProcessor.apply(src);
} }
processedImage = img;
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(
RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR
);
g2d.drawImage(img, 0, 0, getWidth(), getHeight(), null);
} }
public BufferedImage getCurrentProcessedImage() { public BufferedImage getCurrentProcessedImage() {
return processedImage; return processedImage;
} }
}
}

View File

@@ -2,78 +2,84 @@ package io.swtc.proccessing;
import com.github.sarxos.webcam.Webcam; import com.github.sarxos.webcam.Webcam;
import com.github.sarxos.webcam.WebcamException; import com.github.sarxos.webcam.WebcamException;
import com.github.sarxos.webcam.WebcamResolution;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;
import java.util.function.Consumer; import java.util.function.Consumer;
import javax.swing.*; import javax.swing.*;
import io.swtc.proccessing.ui.ShowError;
public class WebcamCaptureLoop { public class WebcamCaptureLoop {
private final Webcam webcam; private final Webcam webcam;
private final Consumer<BufferedImage> onFrameCaptured; private final Consumer<BufferedImage> onFrameCaptured;
private volatile boolean running = false; private volatile boolean running = false;
private final AtomicBoolean cleanedUp = new AtomicBoolean(false); private final AtomicBoolean cleanedUp = new AtomicBoolean(false);
private final int targetframes = 20;
public WebcamCaptureLoop(Webcam webcam, Consumer<BufferedImage> onFrameCaptured) { public WebcamCaptureLoop(Webcam webcam, Consumer<BufferedImage> onFrameCaptured) {
this.webcam = webcam; this.webcam = webcam;
this.onFrameCaptured = onFrameCaptured; this.onFrameCaptured = onFrameCaptured;
// this is some of the most stupid shit ive seen in years, this is needed for opening the stream. Dimension vga = WebcamResolution.VGA.getSize();
// the webcam package may not know the res before its opened and well this is before we open it. that is below in the thread. if (isSizeSupported(webcam, vga)) { // we dont do stupid shit anymore! cause we are smarter than before...
// so this freaks out and doesnt want to cooperate. webcam.setViewSize(vga);
if (!webcam.getName().toLowerCase().contains("ipcam")) { } else {
Dimension[] sizes = webcam.getViewSizes(); Dimension[] dimensions = webcam.getViewSizes();
webcam.setViewSize(sizes[sizes.length - 1]); webcam.setViewSize(dimensions[dimensions.length - 1]);
} }
} }
private boolean isSizeSupported(Webcam webcam, Dimension vga) {
for (Dimension d: webcam.getViewSizes()) {
if (
d.width == vga.width && d.height == vga.height
) return true; // this should return 640x480 :)
}
return false;
}
public void start() { public void start() {
if (running) return; if (running) return;
running = true; running = true;
Thread captureThread = new Thread(() -> { Thread captureThread = new Thread(() -> {
// this is where we open it. if the res isnt known then it fucks up.
try { try {
webcam.open(); if (!webcam.isOpen()) webcam.open(); // open if not
} catch (WebcamException e) {
JOptionPane.showMessageDialog( long frameDurationLimitns = 1_000_000_000L / targetframes; // we use NanoSeconds cause its more accurate
while (running) {
long startTime = System.nanoTime();
BufferedImage img = webcam.getImage();
if (img != null)
// there is no need for a invoke later swing wise!
onFrameCaptured.accept(img);
long timespent = System.nanoTime() - startTime;
long sleeptime = frameDurationLimitns - timespent;
if (sleeptime > 0) {
LockSupport.parkNanos(sleeptime);
} else {
// do a yield to prevent ui freezes
Thread.yield();
}
}
} catch (Exception e) {
ShowError.warning(
null, null,
"WebcamException" + e.getMessage(), "Exception" + e,
"WebcamException", "Exception"
JOptionPane.ERROR_MESSAGE
); );
} finally { } finally {
webcam.open(); cleanup();
} }
}, "cam_cap_thread");
while (running) {
BufferedImage img = webcam.getImage();
if (img != null) {
//System.err.println(img);
onFrameCaptured.accept(img);
}
try {
Thread.sleep(15);
} catch (InterruptedException e) {
break;
}
}
try {
webcam.close();
} catch (IllegalStateException e) {
// show a error message from javax.swing.awt.JOptionPane
JOptionPane.showMessageDialog(
null,
"IllegalStateException@"+e,
"IllegalStateException",
JOptionPane.ERROR_MESSAGE
);
}
});
captureThread.setName("cam_cap_thread");
captureThread.start(); captureThread.start();
} }
@@ -113,6 +119,5 @@ public class WebcamCaptureLoop {
*/ */
public void stop() { public void stop() {
running = false; running = false;
cleanup();
} }
} }

View File

@@ -0,0 +1,40 @@
package io.swtc.proccessing.ui;
import javax.swing.*;
import java.awt.*;
import java.net.URL;
import java.util.Objects;
public class IconSetter {
private static Image ICON_IMAGE;
private static ImageIcon ICON_ICON;
private static Image effects_icon;
public static Image getIcon() {
if (ICON_IMAGE == null) {
URL url = IconSetter.class.getResource("/icons/artwork.png");
if (url == null) throw new RuntimeException("Icon not found: /icons/artwork.png");
ICON_IMAGE = Toolkit.getDefaultToolkit().getImage(url);
}
return ICON_IMAGE;
}
public static Image getEffectIcon() {
if (Objects.isNull(effects_icon)) {
URL url = IconSetter.class.getResource("/icons/effectsframe.png");
if (Objects.isNull(url)) ShowError.error(null,"Error","Icon not found");
effects_icon = Toolkit.getDefaultToolkit().getImage(url);
}
return effects_icon;
}
public static ImageIcon getIconAsImageIcon() {
if (ICON_ICON == null) {
URL url = IconSetter.class.getResource("/icons/artwork.png");
if (url == null) throw new RuntimeException("Icon not found: /icons/artwork.png");
ICON_ICON = new ImageIcon(url); // separate variable for ImageIcon
}
return ICON_ICON;
}
}

View File

@@ -0,0 +1,38 @@
package io.swtc.proccessing.ui;
import javax.swing.*;
import java.awt.*;
public final class ShowError {
private ShowError() {
// we dont instantiate cause it causes some errors
}
public static void error(Component parent, String title, String message) {
JOptionPane.showMessageDialog(
parent,
title,
message,
JOptionPane.ERROR_MESSAGE
);
}
public static void warning(Component parent, String title, String message) {
JOptionPane.showMessageDialog(
parent,
title,
message,
JOptionPane.WARNING_MESSAGE
);
}
public static void info(Component parent, String title, String message) {
JOptionPane.showMessageDialog(
parent,
message,
title,
JOptionPane.INFORMATION_MESSAGE
);
}
}

View File

@@ -3,12 +3,16 @@ package io.swtc.proccessing.ui.iframe;
import com.github.sarxos.webcam.Webcam; import com.github.sarxos.webcam.Webcam;
import io.swtc.proccessing.WebcamCaptureLoop; import io.swtc.proccessing.WebcamCaptureLoop;
import io.swtc.proccessing.CameraPanel; import io.swtc.proccessing.CameraPanel;
import io.swtc.proccessing.ui.IconSetter;
import io.swtc.proccessing.ui.ShowError;
import io.swtc.recording.RecordingManager;
import io.swtc.recording.VideoRecorder; import io.swtc.recording.VideoRecorder;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.EmptyBorder; import javax.swing.border.EmptyBorder;
import java.awt.*; import java.awt.*;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.awt.image.RescaleOp;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -16,15 +20,19 @@ import java.util.function.Consumer;
public class CameraInternalFrame extends JInternalFrame { public class CameraInternalFrame extends JInternalFrame {
private final WebcamCaptureLoop captureLoop; private final WebcamCaptureLoop captureLoop;
private final CameraPanel cameraPanel; private final CameraPanel cameraPanel;
private final VideoRecorder videoRecorder; // Instance of the recorder private final VideoRecorder videoRecorder;
private JButton recordBtn; private final RecordingManager recordingManager;
public CameraInternalFrame(Webcam webcam, Consumer<CameraInternalFrame> onOpenEffects) { public CameraInternalFrame(Webcam webcam, Consumer<CameraInternalFrame> onOpenEffects) {
super(webcam.getName(), true, true, true, true); super(webcam.getName(), true, true, true, true);
this.cameraPanel = new CameraPanel();
this.videoRecorder = new VideoRecorder(); // Initialize recorder
// Initialize capture loop Image ico = IconSetter.getIcon();
setFrameIcon(new ImageIcon(ico));
this.cameraPanel = new CameraPanel();
this.videoRecorder = new VideoRecorder();
this.recordingManager = new RecordingManager(cameraPanel); // Initialize recorder
this.captureLoop = new WebcamCaptureLoop(webcam, img -> this.captureLoop = new WebcamCaptureLoop(webcam, img ->
SwingUtilities.invokeLater(() -> cameraPanel.setImage(img)) SwingUtilities.invokeLater(() -> cameraPanel.setImage(img))
); );
@@ -36,11 +44,15 @@ public class CameraInternalFrame extends JInternalFrame {
private void setupUI(Consumer<CameraInternalFrame> onOpenEffects) { private void setupUI(Consumer<CameraInternalFrame> onOpenEffects) {
JTabbedPane tabbedPane = new JTabbedPane(); JTabbedPane tabbedPane = new JTabbedPane();
tabbedPane.addTab("View", cameraPanel); tabbedPane.addTab("View", cameraPanel);
tabbedPane.addTab("Capture", new RecordingPane(cameraPanel, videoRecorder)); tabbedPane.addTab("Capture", new JPanel());
tabbedPane.addTab("Effects", new JPanel()); tabbedPane.addTab("Effects", new JPanel());
tabbedPane.addChangeListener(e -> { tabbedPane.addChangeListener(e -> {
if (tabbedPane.getSelectedIndex() == 2) { int index = tabbedPane.getSelectedIndex();
if (index == 1) { // the tab index for capture is 1
tabbedPane.setSelectedIndex(0);
openRecordingFrame();
} else if (index == 2) {
tabbedPane.setSelectedIndex(0); tabbedPane.setSelectedIndex(0);
onOpenEffects.accept(this); onOpenEffects.accept(this);
} }
@@ -50,79 +62,25 @@ public class CameraInternalFrame extends JInternalFrame {
setSize(600, 500); setSize(600, 500);
} }
private JPanel createCapturePanel() { private void openRecordingFrame() {
JPanel panel = new JPanel(new GridLayout(2, 1, 10, 10)); // Changed to Grid for better button layout RecordingFrame rf = new RecordingFrame(this.getTitle(),cameraPanel, videoRecorder);
panel.setBorder(new EmptyBorder(15, 15, 15, 15)); JDesktopPane desktopPane = getDesktopPane();
if (desktopPane != null) {
JButton screenshotBtn = new JButton("Take Screenshot"); desktopPane.add(rf);
screenshotBtn.addActionListener(e -> saveSnapshot()); rf.setVisible(true);
recordBtn = new JButton("Start Recording");
recordBtn.addActionListener(e -> toggleRecording());
panel.add(screenshotBtn);
panel.add(recordBtn);
// Wrap in a wrapper to prevent buttons from stretching too much
JPanel wrapper = new JPanel(new BorderLayout());
wrapper.add(panel, BorderLayout.NORTH);
return wrapper;
}
private void toggleRecording() {
if (!videoRecorder.isRecording()) {
startVideo();
} else {
stopVideo();
} }
}
private void startVideo() {
try { try {
File file = new File(System.getProperty("user.home"), "vid_" + System.currentTimeMillis() + ".mp4"); rf.setSelected(true);
videoRecorder.startRecording(cameraPanel, file); } catch (java.beans.PropertyVetoException veto) {
ShowError.error(null,"VetoException"+veto.getMessage(),"veto");
recordBtn.setText("Stop Recording");
recordBtn.setForeground(Color.RED);
} catch (IOException ex) {
showError("Failed to start recording", ex);
} }
} }
private void stopVideo() {
try {
File savedFile = videoRecorder.stopRecording();
recordBtn.setText("Start Recording");
recordBtn.setForeground(Color.BLACK);
JOptionPane.showMessageDialog(this, "Video saved to: " + savedFile.getAbsolutePath());
} catch (IOException ex) {
showError("Failed to stop recording safely", ex);
}
}
private void saveSnapshot() {
BufferedImage img = cameraPanel.getCurrentProcessedImage();
if (img != null) {
try {
File file = new File(System.getProperty("user.home"), "snap_" + System.currentTimeMillis() + ".png");
ImageIO.write(img, "PNG", file);
} catch (Exception ex) {
showError("Snapshot failed", ex);
}
}
}
private void showError(String title, Exception ex) {
JOptionPane.showMessageDialog(this, title + "\n" + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
}
public CameraPanel getCameraPanel() { return cameraPanel; } public CameraPanel getCameraPanel() { return cameraPanel; }
@Override @Override
public void dispose() { public void dispose() {
// Safety check: stop recording if the window is closed
if (videoRecorder.isRecording()) { if (videoRecorder.isRecording()) {
try { videoRecorder.stopRecording(); } catch (IOException ignored) {} try { videoRecorder.stopRecording(); } catch (IOException ignored) {}
} }

View File

@@ -56,11 +56,11 @@ public class DesktopPane extends JDesktopPane {
int ctrlOffset = Math.min(Math.abs(x2 - x1) / 2, 150); int ctrlOffset = Math.min(Math.abs(x2 - x1) / 2, 150);
CubicCurve2D curve = new CubicCurve2D.Double(x1, y1, x1 + ctrlOffset, y1, x2 - ctrlOffset, y2, x2, y2); CubicCurve2D curve = new CubicCurve2D.Double(x1, y1, x1 + ctrlOffset, y1, x2 - ctrlOffset, y2, x2, y2);
g2d.setStroke(new BasicStroke(3f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); //g2d.setStroke(new BasicStroke(3f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g2d.draw(curve); //g2d.draw(curve);
// Terminals // Terminals
g2d.fillOval(x1 - 5, y1 - 5, 10, 10); //g2d.fillOval(x1 - 5, y1 - 5, 10, 10);
g2d.fillOval(x2 - 5, y2 - 5, 10, 10); //g2d.fillOval(x2 - 5, y2 - 5, 10, 10);
} }
} }

View File

@@ -2,11 +2,18 @@ package io.swtc.proccessing.ui.iframe;
import io.swtc.proccessing.CameraPanel; import io.swtc.proccessing.CameraPanel;
import io.swtc.proccessing.FilterPanel; import io.swtc.proccessing.FilterPanel;
import io.swtc.proccessing.ui.IconSetter;
import javax.swing.*; import javax.swing.*;
import java.awt.*;
public class EffectsPanelFrame extends JInternalFrame { public class EffectsPanelFrame extends JInternalFrame {
public EffectsPanelFrame(String title, CameraPanel cameraPanel) { public EffectsPanelFrame(String title, CameraPanel cameraPanel) {
super(title, true, true, true, true); super(title, true, true, true, true);
Image ico = IconSetter.getIcon();
setFrameIcon(new ImageIcon(IconSetter.getEffectIcon()));
setDefaultCloseOperation(HIDE_ON_CLOSE); setDefaultCloseOperation(HIDE_ON_CLOSE);
add(new FilterPanel(cameraPanel)); add(new FilterPanel(cameraPanel));
setSize(350, 600); setSize(350, 600);

View File

@@ -0,0 +1,178 @@
package io.swtc.proccessing.ui.iframe;
import io.swtc.proccessing.CameraPanel;
import io.swtc.proccessing.ui.IconSetter;
import io.swtc.proccessing.ui.ShowError;
import io.swtc.recording.VideoRecorder;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class RecordingFrame extends JInternalFrame {
private final VideoRecorder videoRecorder;
private final CameraPanel cameraPanel;
private JButton recordBtn;
private JLabel statusLabel;
private JLabel statsLabel;
private File outputDirectory = new File(System.getProperty("user.home"));
private File currentFile;
private final Timer statsTimer;
private long startTime;
// Cache for string building to avoid object allocation every second
private final StringBuilder sb = new StringBuilder(32);
public RecordingFrame(String cameraName, CameraPanel cameraPanel, VideoRecorder videoRecorder) {
super(cameraName + " Capture", true, true, false, true);
Image ico = IconSetter.getIcon();
setFrameIcon(new ImageIcon(ico));
this.cameraPanel = cameraPanel;
this.videoRecorder = videoRecorder;
initializeUI();
this.statsTimer = new Timer(1000, e -> updateStats());
this.statsTimer.setCoalesce(true);
pack();
}
private void initializeUI() {
JPanel mainContent = new JPanel();
mainContent.setLayout(new BoxLayout(mainContent, BoxLayout.Y_AXIS));
mainContent.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
mainContent.add(createStoragePanel());
mainContent.add(Box.createVerticalStrut(10));
mainContent.add(createActionPanel());
mainContent.add(Box.createVerticalStrut(10));
mainContent.add(createStatsPanel());
getContentPane().add(mainContent);
}
private JPanel createStatsPanel() {
JPanel panel = new JPanel(new GridLayout(2, 1));
panel.setBorder(BorderFactory.createTitledBorder("Session Info"));
statusLabel = new JLabel("Status: Idle");
statsLabel = new JLabel("Length: 00:00 | Size: 0.00 MB");
statsLabel.setFont(new Font("Monospaced", Font.PLAIN, 12));
statsLabel.setPreferredSize(new Dimension(220, 20));
panel.add(statusLabel);
panel.add(statsLabel);
return panel;
}
private void updateStats() {
if (!videoRecorder.isRecording() || currentFile == null) return;
long elapsedSecs = (System.currentTimeMillis() - startTime) / 1000;
long minutes = elapsedSecs / 60;
long seconds = elapsedSecs % 60;
double sizeInMb = currentFile.length() / 1048576.0;
sb.setLength(0);
sb.append("Length: ")
.append(minutes < 10 ? "0" : "").append(minutes).append(":")
.append(seconds < 10 ? "0" : "").append(seconds)
.append(" | Size: ")
.append(Math.round(sizeInMb * 100.0) / 100.0)
.append(" MB");
statsLabel.setText(sb.toString());
}
private void toggleRecording() {
if (!videoRecorder.isRecording()) {
startRec();
} else {
stopRec();
}
}
private void startRec() {
try {
currentFile = new File(outputDirectory, "vid_" + System.currentTimeMillis() + ".mp4");
videoRecorder.startRecording(cameraPanel, currentFile);
startTime = System.currentTimeMillis();
statsTimer.start(); // Only run timer when needed
recordBtn.setText("Stop Recording");
statusLabel.setText("Currently Recording");
} catch (IOException ex) {
ShowError.error(null,"Error starting Recording" + ex.getMessage(), "Error");
}
}
private void stopRec() {
try {
videoRecorder.stopRecording();
statsTimer.stop();
recordBtn.setText("Started Recording");
recordBtn.setForeground(null);
statusLabel.setText("Finished recording");
} catch (IOException ex) {
ShowError.error(null,"RecordStop Error"+ex.getMessage(), "Error");
}
}
private JPanel createStoragePanel() {
JPanel panel = new JPanel(new BorderLayout(5, 5));
panel.setBorder(BorderFactory.createTitledBorder("Storage"));
JTextField pathField = new JTextField(outputDirectory.getAbsolutePath());
pathField.setEditable(false);
JButton browseBtn = new JButton("...");
browseBtn.addActionListener(e -> {
JFileChooser chooser = new JFileChooser(outputDirectory);
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
outputDirectory = chooser.getSelectedFile();
pathField.setText(outputDirectory.getAbsolutePath());
}
});
panel.add(pathField, BorderLayout.CENTER);
panel.add(browseBtn, BorderLayout.EAST);
return panel;
}
private JPanel createActionPanel() {
JPanel panel = new JPanel(new GridLayout(1, 2, 5, 5));
recordBtn = new JButton("Start Recording");
recordBtn.addActionListener(e -> toggleRecording());
JButton snapBtn = new JButton("Snapshot");
snapBtn.addActionListener(e -> takeSnapshot());
panel.add(recordBtn);
panel.add(snapBtn);
return panel;
}
private void takeSnapshot() {
BufferedImage img = cameraPanel.getCurrentProcessedImage();
if (img != null) {
try {
File file = new File(outputDirectory, "snap_" + System.currentTimeMillis() + ".png");
ImageIO.write(img, "PNG", file);
statusLabel.setText("Saved a Snapshot");
} catch (IOException ex) {
ShowError.error(null,"Snapshot failed"+ex.getMessage(),"Snapshot Error");
}
}
}
}

View File

@@ -1,126 +0,0 @@
package io.swtc.proccessing.ui.iframe;
import io.swtc.proccessing.CameraPanel;
import io.swtc.recording.VideoRecorder;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.border.TitledBorder;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class RecordingPane extends JPanel {
private final VideoRecorder videoRecorder;
private final CameraPanel cameraPanel;
private JTextField pathField;
private JButton recordBtn;
private JLabel statusLabel;
private File outputDirectory;
public RecordingPane(CameraPanel cameraPanel, VideoRecorder videoRecorder) {
this.cameraPanel = cameraPanel;
this.videoRecorder = videoRecorder;
this.outputDirectory = new File(System.getProperty("user.home"));
setLayout(new GridBagLayout());
JPanel contentPanel = new JPanel();
contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
contentPanel.setPreferredSize(new Dimension(400, 250));
// Add the functional sections
contentPanel.add(createStoragePanel());
contentPanel.add(Box.createVerticalStrut(15));
contentPanel.add(createActionPanel());
contentPanel.add(Box.createVerticalStrut(15));
contentPanel.add(createStatusPanel());
add(contentPanel);
}
private JPanel createStoragePanel() {
JPanel panel = new JPanel(new BorderLayout(5, 5));
panel.setBorder(BorderFactory.createTitledBorder(
BorderFactory.createEtchedBorder(), "Storage Settings", TitledBorder.LEFT, TitledBorder.TOP));
pathField = new JTextField(outputDirectory.getAbsolutePath());
pathField.setEditable(false);
JButton browseBtn = new JButton("Browse...");
browseBtn.addActionListener(e -> {
JFileChooser chooser = new JFileChooser(outputDirectory);
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
outputDirectory = chooser.getSelectedFile();
pathField.setText(outputDirectory.getAbsolutePath());
}
});
panel.add(pathField, BorderLayout.CENTER);
panel.add(browseBtn, BorderLayout.EAST);
return panel;
}
private JPanel createActionPanel() {
JPanel panel = new JPanel(new GridLayout(1, 2, 10, 10));
recordBtn = new JButton("start recording");
recordBtn.addActionListener(e -> toggleRecording());
JButton snapBtn = new JButton("take snapshot");
snapBtn.addActionListener(e -> takeSnapshot());
panel.add(recordBtn);
panel.add(snapBtn);
return panel;
}
private JPanel createStatusPanel() {
JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER));
statusLabel = new JLabel("");
statusLabel.setForeground(Color.DARK_GRAY);
panel.add(statusLabel);
return panel;
}
private void toggleRecording() {
if (!videoRecorder.isRecording()) {
try {
File file = new File(outputDirectory, "vid_" + System.currentTimeMillis() + ".mp4");
videoRecorder.startRecording(cameraPanel, file);
recordBtn.setText("stop recording");
statusLabel.setText("recording");
} catch (IOException ex) {
showError("Start Error", ex);
}
} else {
try {
File saved = videoRecorder.stopRecording();
recordBtn.setText("Start Recording");
statusLabel.setText("Status: Saved " + saved.getName());
} catch (IOException ex) {
showError("Stop Error", ex);
}
}
}
private void takeSnapshot() {
BufferedImage img = cameraPanel.getCurrentProcessedImage();
if (img != null) {
try {
File file = new File(outputDirectory, "snap_" + System.currentTimeMillis() + ".png");
ImageIO.write(img, "PNG", file);
statusLabel.setText("captured");
} catch (IOException ex) {
showError("Snapshot Error", ex);
}
}
}
private void showError(String title, Exception ex) {
JOptionPane.showMessageDialog(this, ex.getMessage(), title, JOptionPane.ERROR_MESSAGE);
}
}

View File

@@ -0,0 +1,70 @@
package io.swtc.recording;
import io.swtc.proccessing.CameraPanel;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.function.Consumer;
public class RecordingManager {
private final VideoRecorder recorder;
private final CameraPanel cameraPanel;
// Reuse a single buffer to avoid constant memory allocation (GC pressure)
private BufferedImage reuseBuffer;
public RecordingManager(CameraPanel cameraPanel) {
this.cameraPanel = cameraPanel;
this.recorder = new VideoRecorder();
}
public BufferedImage fastConvertToRGB(BufferedImage source) {
if (source.getType() == BufferedImage.TYPE_INT_RGB) {
return source;
}
// Initialize or resize reuseBuffer only when necessary
if (reuseBuffer == null ||
reuseBuffer.getWidth() != source.getWidth() ||
reuseBuffer.getHeight() != source.getHeight()) {
reuseBuffer = new BufferedImage(source.getWidth(), source.getHeight(), BufferedImage.TYPE_INT_RGB);
}
// Graphics2D.drawImage is much faster than manual setRGB loops
Graphics2D g = reuseBuffer.createGraphics();
g.drawImage(source, 0, 0, null);
g.dispose();
return reuseBuffer;
}
public void toggleRecording(File outputFile, Runnable onStart, Consumer<File> onStop, Consumer<Exception> onError) {
if (!recorder.isRecording()) {
new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() throws Exception {
recorder.startRecording(cameraPanel, outputFile);
return null;
}
@Override
protected void done() {
try { get(); if (onStart != null) onStart.run(); }
catch (Exception e) { if (onError != null) onError.accept(e); }
}
}.execute();
} else {
new SwingWorker<File, Void>() {
@Override
protected File doInBackground() throws Exception {
return recorder.stopRecording();
}
@Override
protected void done() {
try { File f = get(); if (onStop != null) onStop.accept(f); }
catch (Exception e) { if (onError != null) onError.accept(e); }
}
}.execute();
}
}
}

View File

@@ -6,109 +6,82 @@ import org.jcodec.common.io.NIOUtils;
import org.jcodec.common.io.SeekableByteChannel; import org.jcodec.common.io.SeekableByteChannel;
import org.jcodec.common.model.Rational; import org.jcodec.common.model.Rational;
import javax.swing.*; import java.awt.*;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.*;
/*
*
* This is not that interesting but surely improtant for any security based people,
* here we record using jcodec, which is more efficient than me writing my own recorder,
* which i am certainly not doing.
*
* Anyways we dont do shit properly anyway.
*
* */
public class VideoRecorder { public class VideoRecorder {
private boolean recording = false; private volatile boolean recording = false;
private Timer captureTimer;
private File outputFile; private File outputFile;
private AWTSequenceEncoder encoder; private AWTSequenceEncoder encoder;
private SeekableByteChannel channel; private SeekableByteChannel channel;
private static final int FPS = 30; private static final int FPS = 30;
private LinkedBlockingQueue<BufferedImage> frameQueue;
private ExecutorService workerThread;
public void startRecording(CameraPanel panel, File output) throws IOException { public void startRecording(CameraPanel panel, File output) throws IOException {
this.outputFile = output; this.outputFile = output;
this.channel = NIOUtils.writableFileChannel(output.getAbsolutePath());
this.encoder = new AWTSequenceEncoder(channel, Rational.R(FPS, 1));
this.frameQueue = new LinkedBlockingQueue<>(60);
this.recording = true; this.recording = true;
channel = NIOUtils.writableFileChannel(String.valueOf(outputFile)); workerThread = Executors.newSingleThreadExecutor();
encoder = new AWTSequenceEncoder(channel, Rational.R(FPS, 1)); workerThread.submit(this::processQueue);
captureTimer = new Timer(1000 / FPS, e -> { new Thread(() -> {
try { while (recording) {
BufferedImage img = panel.getCurrentProcessedImage();
if (img != null) {
BufferedImage rgbImage = convertToRGB(img);
encoder.encodeImage(rgbImage);
}
} catch (IOException ex) {
JOptionPane.showMessageDialog(
null,
"IOException\n" + ex,
"IOException in Recorder",
JOptionPane.ERROR_MESSAGE
);
try { try {
stopRecording(); BufferedImage img = panel.getCurrentProcessedImage();
} catch (IOException stopEx) { if (img != null) {
JOptionPane.showMessageDialog( BufferedImage copy = snapshotToRGB(img);
null, frameQueue.offer(copy);
"IOException@stopEx\n" + stopEx, }
"IOException in Recorder@stopEx", Thread.sleep(1000 / FPS);
JOptionPane.ERROR_MESSAGE } catch (InterruptedException e) {
); Thread.currentThread().interrupt();
} }
} }
}); }).start();
captureTimer.start(); }
private void processQueue() {
while (recording || !frameQueue.isEmpty()) {
try {
BufferedImage img = frameQueue.poll(500, TimeUnit.MILLISECONDS);
if (img != null) {
encoder.encodeImage(img);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private BufferedImage snapshotToRGB(BufferedImage source) {
BufferedImage rgb = new BufferedImage(source.getWidth(), source.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D g = rgb.createGraphics();
g.drawImage(source, 0, 0, null);
g.dispose();
return rgb;
} }
public File stopRecording() throws IOException { public File stopRecording() throws IOException {
recording = false; recording = false;
workerThread.shutdown();
/* some helper methods, i swear its always the same? */ try {
if (captureTimer != null) { workerThread.awaitTermination(5, TimeUnit.SECONDS);
captureTimer.stop(); if (encoder != null) encoder.finish();
if (channel != null) channel.close();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} }
if (encoder != null) {
encoder.finish();
}
if (channel != null) {
channel.close();
}
return outputFile; return outputFile;
} }
public boolean isRecording() { public boolean isRecording() { return recording; }
return recording;
}
// maybe i should move this to a component, some useless conversion right here,
// (performance wise)
// yes, capitain?
private BufferedImage convertToRGB(BufferedImage source) {
if (source.getType() == BufferedImage.TYPE_INT_RGB) {
return source;
}
BufferedImage rgb = new BufferedImage(
source.getWidth(),
source.getHeight(),
BufferedImage.TYPE_INT_RGB
);
for (int y = 0; y < source.getHeight(); y++) {
for (int x = 0; x < source.getWidth(); x++) {
rgb.setRGB(x, y, source.getRGB(x, y) & 0xFFFFFF);
}
}
return rgb;
}
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB